Where the work is recorded

Status doesn't live in a chat.
It propagates.

Every meaningful thing the ring does is recorded somewhere durable, then radiated outward to the places humans look: the shared database is the system of record; Loop carries the human-readable status; Azure DevOps holds the backlog truth; Teams carries the pings. This page traces each surface — and is candid about which links are wired today and which are still aspirational.

DB = system of record Loop · ADO · Teams = radiated status No external write without sign-off

The shape of it

One source of truth, four surfaces

The Postgres database is the only place that is the truth. Loop, ADO and Teams are projections of it — convenient windows for humans. Nothing is "logged to Loop" as a primary record; Loop reflects a row that already exists in the database.

Status propagation from the database to four surfaces The shared Postgres database sits at the centre as the system of record. From it, status radiates to four surfaces: Microsoft Loop for human-readable status, Azure DevOps via a write-back queue, Teams for acknowledgements, and the agent_runs table as the internal audit trail. haleon_aiac Postgres · ~29 base tables system of record Microsoft Loop human-readable status CoS-owned · delivered_to_loop Azure DevOps backlog truth enqueue + drain wired (ado-scribe, Batch 2) Teams DMs · acks · briefs agent_runs internal audit trail
Status flows out of the database to each surface. The database never reads back from Loop or Teams.

Surface 1 — the database

The system of record wired

Every brief, decision, risk, comms draft and prep doc is a row in the haleon schema before it is anything else. Status is a column, not a message: a comms draft sits at awaiting_signoff; an ADR's status is one of proposed → accepted → superseded/rejected (the finer Reviewer/Compliance lifecycle stages are a procedural workflow, not values of adrs.status — see Open Issues); a write-back op carries an explicit status enum. Because the row exists first, the audit trail is a side-effect of doing the work, not a separate logging step.

Why a database and not a log file? A log is append-only narration; a row is queryable state. The ring can ask "what is still awaiting_signoff?" or "which ADRs are proposed but not accepted?" — questions a chat transcript can't answer.

Surface 2 — Loop

Human-readable status to Loop partial

Loop is where Charlie's wider audience reads status in prose. It is a Chief-of-Staff function, tied to briefs and ceremony prep documents — not a standalone "Loop logger" agent. The database backs the link directly:

briefs.delivered_to_loop
boolean — set true once the CoS posts a brief into the Loop page
meeting_preps.loop_page_url
the Loop page a prep doc was published to
pd_sync_preps.loop_page_url
same, for PD-sync prep
steerco_preps.loop_page_url
same, for SteerCo prep

So "status to Loop" means the CoS writing brief/prep content and flagging delivery — not a separate heartbeat. The exact firing cadence lives in the daily-brief and prep skill files; confirm there before relying on it.

Two operational caveats. (1) The Loop sandbox page in use for the current shakedown is reached by an access-bearing tokenised link; it is configured per-environment and is deliberately not reproduced anywhere in this documentation — get it from Charlie or the CoS handoff ledger. (2) Charlie's corporate network blocks Loop's Fluid Framework sync, so Loop writes may need a non-corporate network or phone tether to save.

Surface 3 — Azure DevOps

The ADO write-back queue enqueue wired drain built

The ring keeps ADO up to date through an enqueue-then-drain pattern rather than writing to the board directly: any agent that wants to change a work item appends an operation to a queue table at pending_approval; Charlie's approval flips it to queued; the ado-scribe drainer (built Batch 2) consumes only queued rows and applies them to ADO via the wit_* MCP tools, advancing each row through the CAS-guarded sp_update_ado_writeback state machine.

The enqueue path fully built & proven

A write travels: DAB entity AdoWritebackIn → view haleon.v_ado_writeback_inINSTEAD OF INSERT trigger → haleon.sp_create_ado_writeback(…)INSERT into the queue at status='pending_approval' (gate-by-default) with ON CONFLICT (idempotency_key) DO NOTHING.

haleon.ado_writeback_queue
-- one row per pending ADO change (idempotency_key UNIQUE)
id                  uuid        -- gen_random_uuid()
idempotency_key     text        -- UNIQUE → ON CONFLICT DO NOTHING
target_work_item_id bigint
operation           text        -- e.g. add-comment, set-state
payload             jsonb       -- the change body; left UNCHANGED on status-only updates
status              ado_writeback_status   -- enum, DEFAULT 'pending_approval'
                                -- = {pending_approval, queued, in_flight, committed, failed}
attempts            int         -- incremented on *→in_flight; cap=5
last_attempt_at     timestamptz
last_error          text        -- cleared on failed→queued retry as hygiene
committed_at        timestamptz
approved_by         text        -- set on pending_approval→queued (Charlie); RAISE if NULL
approved_at         timestamptz
enqueued_by_agent   text
-- INDEX idx_ado_q_status (status, enqueued_at)  ← drives drainer FIFO
Continuous path
skills enqueue ops as they work (e.g. decision-capture on a ratified ADR with linked work items)
Batch path
the Friday ado-bulk-triage sweep enqueues recommended state changes in bulk, all at pending_approval
Target
the configured azure_devops MCP project — single source of truth for org/project/area path (ado-bulk-triage and ado-scribe no longer hardcode a target, ISS-05 resolved). Currently smccormick0886/Haleon-AIAQ; the pointer is environment config.

The drain path built Batch 2

The ado-scribe drainer is live as an on-demand CoS skill; its 15-minute cadence is the declared intent for core-tick step 4 and will activate when Charlie enables the recurring ring-tick timer. One tick acquires the single-holder drainer_lease (re-entrant same-holder CAS; renew at TTL/2; abort immediately on lost re-acquire), reclassifies failed rows (transient → re-queue capped at MAX_ATTEMPTS=5; permanent → dead-letter; rescue via sp_reset_ado_writeback), reconciles stale in_flight against ADO using an invisible <!-- idk:KEY --> marker (required for every op — field-set AND add-comment — so an external setter can't produce a false commit), selects queued with per-target serialisation, claims queued → in_flight → committed/failed via CAS-guarded UPDATE. A lost CAS race returns no row = silent skip, never a double-write.

Charlie's approval is now DB-backed, not "absence of a drainer." A row lands at pending_approval; Charlie's approval flips it to queued (sets approved_by + approved_at; sp_update_ado_writeback RAISEs if approved_by is NULL on that transition). The drainer consumes only queued — it cannot touch pending_approval. The gate is auditable, not implicit (ISS-11 D3 resolved).

Surface 4 — Teams & the internal audit

Acks, briefs, and the agent_runs trail

Teams partial

Teams carries the pings: the twice-daily 5-3-2-1 brief lands as a DM, and Charlie acks rotations over Teams DM. The exact relay/bot implementation is owned outside the Steward's view and should be confirmed with Charlie before documenting its mechanics.

agent_runs built

The internal audit trail: one required row per meaningful turn, with a 7-member class discriminator (CHECK constraint agent_runs_class_check: {turn, rotation, snapshot, ado_drain, audit, correction, scout_sweep}) plus detail_ref and a (session_id, session_turn_seq) gap-detection key (partial UNIQUE). Multi-writer — every role writes its own runs; the Steward audits (gap-scan, open-run scan via v_open_agent_runs, cross-ref check for class IN ('rotation','snapshot'), proxy-authorship scan on created_by_agent <> agent_name). Corrections are appends (class='correction' pointing at the bad row via detail_ref), never in-place rewrites. Seq allocation is auto-allocated via sp_next_session_turn_seq under pg_advisory_xact_lock.

Surface 5 — Scouts & the cadence substrate

Scout audit footprint & the 3-greens DB gate built recurring schedule deferred

When ring-tick's recurring cadence is enabled (Charlie's posture call), each scout-sweep tick spawns up to five scouts under a shared scout_lease. Every scout writes its own two-phase agent_runs row with class='scout_sweep', enqueues observations to signals through the idempotent sp_create_signal (K1: never NULL, never 23505; partial unique index signals_source_extid_uq is the DB-side backstop), and advances its watermark via ScoutWatermarkIn only after every enqueue in the unit-of-progress succeeds.

3-greens DB gate
sp_check_scout_enabled() reads scout_enable_flags and returns enabled = cost_breaker_live AND drain_proven AND scout_proven. Every scout pre-checks via ScoutEnabledCheckIn at step 0 and aborts if red — audit row closes success=true, skipped_reason='scout-gate-red:<flag>'. Programmatic; not prose.
Three independent leases
tick_lease (Job A core-tick), drainer_lease (nested under A, owned by ado-scribe), scout_lease (Job B). Re-entrant same-holder CAS; per-instance token (never a constant); TTL-expiry steal; abort-on-lost-renewal.
Dual cost circuit-breakers
sp_check_cost_breaker(today, baseline, band) reads today's UTC-day rollup (calls sp_rollup_cost_day(today, 2) first — B3 fix, no more structurally-cold $0) AND sp_check_cost_breaker_rolling(24, baseline, band) sums overlapping cells across the rolling 24h window (m7 fix — UK-evening UTC-day-boundary split). EITHER tripping disables the sweep. Baseline = $50/day ±20% → $60/day ceiling (Charlie's tunable starting guess; no hard budget for this engagement).
Open-run watchdog
The Steward reads OpenAgentRun (DAB read-view over v_open_agent_runscompleted_at IS NULL AND spawned_at < now() - interval '15 min') and writes class='audit' findings with triggered_by = 'open:'||session_id||':'||session_turn_seq. An open run is a degradation signal — the spawn row exists (gap-scan green) but completion never landed.
Per-source watermarks
scout_watermark holds last_scanned_at + last_watermark_ref per source key (signal-scout, backlog-sentinel, pipeline-watcher, reuse-scout, intake-triage). Reuse Scout and Intake Triage use a single shared watermark per source for v1; per-handler sub-keys are a deferred K-extension.

Recurring schedule status. All three greens are earned in DB-substrate terms (cost breaker arms, drain proven manually, Signal Scout proven Unit J 30 May 2026). But sp_check_scout_enabled() still returns enabled=false because Charlie hasn't flipped the three ScoutEnableFlagsIn booleans (one at a time, for per-green audit attribution) — and enabling the recurring 15-min / 2h timer is a separate second posture call. Until both, the ring is on-demand only.

Be honest about the gaps

What is not wired yet

The ADO write-back drain is built (Batch 2). The ado-scribe skill is a lease-guarded drainer that reads queued rows, applies them to ADO via the wit_* MCP tools and advances their status through CAS-guarded sp_update_ado_writeback (legal arc: queued→in_flight→committed|failed; failed→queued retry capped at 5; committed is terminal). Dead-lettered rows can be rescued back to pending_approval via sp_reset_ado_writeback. Manual today; 15-min cadence wires in via core-tick when Charlie's posture call enables the recurring schedule.

The Steward's audit trail is now DB-backed (Batch 3, D4). Dedicated rotation_log and agent_snapshots tables exist and are wired: the rotate-role skill (workflow.json-driven; old rotate-clawpilot-role is a deprecated shim) writes a rotation record + snapshot on each handover, and an agent_runs spine row (class='rotation') cross-references them via detail_ref in detail-row-first order (Step 9b, HS-4): the detail rows are INSERTed first, ids captured, then the spine row INSERTs with detail_ref already populated. Completion-fill (sp_update_agent_run) never touches detail_ref — it is immutable post-INSERT; corrections are appends (class='correction'), never in-place rewrites. plan.md remains the human-readable ledger; the DB is the system-of-record.

Cost telemetry is now wired (Batch 3, D4; HS-5/HS-6/HS-10 hardened, Batch 5). sp_rollup_cost_telemetry aggregates agent_runs token counts per agent into cost_telemetry on idempotent whole-UTC-day grid cells (overlap-proof; non-grid windows RAISE 22023; serialised by pg_advisory_xact_lock). The rollup excludes class IN ('audit','correction') AND any row that has been corrected (a later class='correction' row points at it via detail_ref), so writing corrections is structurally safe — they do not inflate cost cells. 'scout_sweep' rows are included in cost cells like 'turn'. core-tick step 5 calls the rollup on a rolling 2-day window (today-1 AND today) every tick to recapture late-arriving completions. estimated_cost_usd uses published list-price per-model rates; real contract rates are unavailable for this engagement, so these are accepted as the permanent basis — a directional trend signal, not an invoice.

The ring-tick is built and staged; the recurring schedule is deferred to Charlie. The tick mechanics exist and are verified — core-tick (health/drain/cost-rollup, 15-min, on tick_lease) and scout-sweep (5 scouts, 2-hourly, on scout_lease, caged behind the 3-greens DB gate + dual cost circuit-breakers + spawn ceiling + watermark + wall-clock caps) — wired in the ring-tick skill. The recurring schedule itself is intentionally off: enabling it is a Charlie posture call gated on three greens (breaker live, drain proven, Signal Scout proven — all earned in DB-substrate terms as of 30 May 2026 but the booleans aren't flipped). Until then the ring is invoked on demand — see Roadmap.

See how the database and MCP layer are configured →