Writeback Protocol

Every agent that does work on Colibri must write back. Writeback is what makes a task’s result legible to the pipeline. Without writeback, there is no signal on the intelligence axis and no anchor for the legitimacy axis. A task without writeback is indistinguishable from a task that was never done.

Applies to: T1 Sigma, T2 PM, T3 Executor. Leaf sub-agents do not write back directly; their parent writes back on their behalf.


1. Minimum Writeback (all tasks)

Every task, regardless of grade, produces two records:

1.1 Task update

task_update(
  id=<task-id>,
  status="done",
  progress=100
)

Fields:

  • id — the task ID from mcp_tasks
  • statusdone on success, cancelled with justification, blocked if permanent blocker
  • progress — always 100 on final update (intermediate updates use lower values)

1.2 Thought record

thought_record(
  type="reflection",
  task_id=<β task id>,
  agent_id=<author identifier>,
  content="<multi-line content — see §2>",
  session_id=<value from audit_session_start, OR omit for non-proof-grade>
)

Fields (matched against the live Zod schema at src/domains/trail/repository.ts:139-145):

  • type — one of: plan, analysis, decision, reflection. For task completion, always reflection. The field name is type, not thought_type — older drafts used the latter; the live schema does not.
  • task_id — the β task ID; required and non-empty.
  • agent_id — author identifier; required and non-empty.
  • content — multi-line structured text; exact fields depend on the tier (see §2). Must be non-empty — the ζ schema enforces content.min(1) at validation time (see src/domains/trail/schema.ts:85 and the fix-thought-content-nonempty contract). Empty content is rejected by createThoughtRecord BEFORE the row reaches the database, so the β writeback gate cannot be satisfied with a zero-content thought.
  • session_idoptional in general, required during a proof-grade session. When present, binds the row to an audit_sessions row so merkle_finalize can collect it. When omitted, the column stores NULL — fine for non-proof-grade β writebacks, but a thought_record stored with session_id = NULL is invisible to merkle_finalize’s WHERE session_id = ? query (src/tools/merkle.ts:296-300). See §3 + §8 anti-pattern #8.

2. Content Structure by Tier

2.1 Executor (T3) reflection content

task_id: <id from mcp_tasks>
branch: feature/<slug>
worktree: .worktrees/claude/<slug>
commit_final: <final SHA on branch>
commits:
  audit: <SHA>
  contract: <SHA>
  packet: <SHA>
  impl: <SHA>
  verify: <SHA>
tests: <commands run and their results>
summary: <one paragraph: what was done, why, outcome>
blockers: <residual risks or follow-ups; "none" if none>

2.2 PM (T2) wave-gate reflection content

wave: <N>
name: <wave_name>
tasks_assigned: <count>
tasks_done: <count>
tasks_cancelled: <count>
tests_run: <commands>
writeback_all_verified: <true|false>
new_learnings: <bullets>
blockers_to_next_wave: <any>

2.3 PM (T2) round-end reflection content

round: rNN
theme: <theme>
waves_completed: <N>
tasks_total: <count>
tasks_done: <count>
tasks_cancelled: <count>
tasks_blocked: <count>
writeback_complete: true
ready_for_seal: true
notes: <handoff notes for Sigma Phase B>

2.4 Sigma (T1) phase-A reflection content

round: rNN
theme: <theme>
manifest_path: .agents/spawns/rNN-<theme>/manifest.md
slices: <count>
waves_in_use: <list>
dependencies: <N edges>
risks: <bullets>
human_authorized: true

2.5 Sigma (T1) phase-B reflection content

round: rNN
theme: <theme>
waves_completed: <N>
audit_chain: green
merkle_root: <hash or "n/a for non-proof-grade">
seal_document: docs/session-seal-sN.md
learnings: <bullets>
next_round_seed: <optional pointer>
requested_human_merge: true

3. Proof-Grade Writeback

A task is proof-grade if any of these is true:

  • The task is in the legitimacy axis (η θ ι κ λ μ π ξ)
  • The task is explicitly marked proof_grade: true in its metadata
  • The task changes consensus, governance, rule engine, or identity code
  • The human has flagged the round as proof-grade

Proof-grade tasks add a Merkle leaf to the chain. The extra steps are:

3.1 Start audit session

audit_session_start(
  intent="<free-text purpose of this session>",
  task_id="<optional β task id>"
)

Returns {session_id, intent, task_id, started_at}. The returned session_id is the binding key for every thought_record in this proof chain — pass it through to every subsequent thought_record call (see §3.4 + the §1.2 schema note).

Older drafts of this protocol used name/scope arg names — those were never on the live schema (src/tools/merkle.ts:156-160). The Phase 0 surface accepts intent (required) + task_id? + session_id? (test-only seam).

3.2 Do the work

(The normal 5-step chain or the tier’s normal phase work.)

3.3 Verify chain

audit_verify_chain(
  task_id="<β task id — same one used on thought_record calls>"
)

This must return green (i.e. valid: true). A red result means the thought chain is broken and the proof cannot be finalized. Pause and report.

The schema accepts task_id? (src/domains/trail/verifier.ts:81-83) and partitions per-task when omitted (R93 B2). Older drafts used session_id=… as the arg name — that field is not on the schema. Verify by the task whose chain you are about to finalize.

3.4 Write the final thought record

thought_record(
  type="reflection",
  task_id="<β task id>",
  agent_id="<author identifier>",
  content="<as in §2>",
  session_id="<value returned by audit_session_start in §3.1>"
)

Two binding rules apply in this step:

  1. Ordering — this thought_record MUST come BEFORE merkle_finalize. The Merkle tree is built over all thought records for the session; if merkle_finalize runs first, the final reflection is not anchored, and the proof is incomplete.
  2. session_id propagation — the session_id arg is mandatory in this step even though the underlying schema marks it optional. merkle_finalize collects rows via WHERE session_id = ? (src/tools/merkle.ts:296-300); a thought_record written with session_id = NULL is invisible to that query and the session will fail finalization with ERR_NO_RECORDS. See §8 anti-pattern #8.

3.5 Finalize the Merkle tree

merkle_finalize(
  session_id=<session>
)

3.6 Capture the root

merkle_root(
  session_id=<session>
)

The returned hash is the anchor. Record it in the seal document (Sigma) or in the writeback content (executor/PM).


4. Ordering Rule (hard constraint)

The canonical proof-grade order is:

1. audit_session_start
2. <work happens; normal chain steps 1-5 for executor, normal phase work for PM/Sigma>
3. audit_verify_chain       ← must be green
4. thought_record           ← FINAL reflection, MUST come before merkle_finalize
5. merkle_finalize
6. merkle_root              ← capture the anchor hash

Any reordering breaks the proof.

Common violation: running merkle_finalize first, then writing a reflection “to wrap up”. The reflection is not in the tree; the proof is incomplete. This has happened in earlier rounds. Don’t do it again.


5. Verification After Writeback

The parent tier is responsible for verifying a child’s writeback.

5.1 PM verifying an executor

1. Fetch task by ID from mcp_tasks
2. Confirm status="done" and progress=100
3. Fetch the reflection thought_record by task_id
4. Verify every field in §2.1 is present
5. Confirm the commit SHAs reference real commits on the feature branch
6. (Proof-grade only) Fetch the Merkle leaf for the task and confirm it exists
7. If any step fails, return the task to the executor with specific fix requirements

5.2 Sigma verifying PM

1. Fetch all tasks in the wave and confirm all have status=done or justified cancelled
2. Confirm every task has a reflection thought_record
3. Fetch the wave-gate thought_record from PM
4. Verify fields in §2.2
5. (Proof-grade only) Run audit_verify_chain across the wave; must be green
6. On pass, record a "gate" thought and authorize next wave
7. On fail, pause and return to PM with specific fix requirements

5.3 Human verifying Sigma

1. Read the Phase B thought_record
2. Read docs/session-seal-sN.md for the round entry
3. (Proof-grade only) Verify the Merkle root in the seal document
4. Review the PR
5. Merge or decline

6. Failure Handling

6.1 Transient failures

  • Executor’s test fails → executor fixes and retries
  • PM’s verification fails → PM returns task to executor with specific fix requirements
  • Sigma’s gate fails → Sigma returns wave to PM with specific fix requirements

6.2 Permanent failures

  • A task cannot be completed → mark status=cancelled with justification in the thought_record
  • A wave cannot close → PM returns to Sigma with thought_type="blocker" and urgency annotation
  • A round cannot seal → Sigma pauses and reports to human

6.3 Merkle failures

  • audit_verify_chain red → chain is broken. Do not finalize. Investigate and fix the root cause (usually a missing thought or an out-of-order call)
  • merkle_finalize error → tree cannot be built. Pause; escalate to human. Never force-finalize with partial data

7. Minimum Fields (checklist)

Every writeback must include at least:

[ ] task_id (or round_id for Sigma)
[ ] session_id
[ ] thought_type (reflection for final, plan/gate/promotion/blocker/deferred for others)
[ ] content (with tier-appropriate structure from §2)
[ ] commit SHA (for T3) or wave/round summary (for T2/T1)
[ ] tests command and result (for T3)
[ ] summary paragraph
[ ] blockers (or "none")

If any of these is missing, the writeback is incomplete. The parent tier will reject it.


8. Anti-Patterns

  1. Writing the reflection after merkle_finalize — ordering violation; proof incomplete.
  2. Marking status=done without a thought_record — invisible completion; rejected by parent tier.
  3. Bundling multiple tasks into one thought_record — each task gets its own record. One-to-one.
  4. Skipping verification of a child’s writeback — responsibility violation; gate fails silently.
  5. “I’ll fill in the thought_record later” — writeback is synchronous. No later.
  6. Using thought_type="reflection" for non-final thoughts — use plan, gate, promotion, blocker, deferred as appropriate.
  7. Re-running merkle_finalize on a session that already finalized — idempotency is not guaranteed; don’t test it.
  8. Writing a thought_record without session_id between audit_session_start and merkle_finalize — the row stores session_id = NULL and is invisible to merkle_finalize’s WHERE session_id = ? query. The session fails finalization with ERR_NO_RECORDS even though the records exist (task-bound). Closes R89 parked investigation. (R93 B5)

9. Minimal Example (executor, non-proof-grade)

# Executor has just finished the 5-step chain on task P0.1.1

task_update(id="P0.1.1", status="done", progress=100)

thought_record(
  session_id="r74",
  thought_type="reflection",
  content="""task_id: P0.1.1
branch: feature/p0.1.1-infrastructure
worktree: .worktrees/claude/p0.1.1-infrastructure
commit_final: a1b2c3d4
commits:
  audit: 11111111
  contract: 22222222
  packet: 33333333
  impl: 44444444
  verify: 55555555
tests: npm test — all green (3 suites, 12 tests)
summary: Set up package.json with ESM, configured tsconfig.json, added Jest ESM runner, created .env.example with 85 documented keys. No source files yet (that's P0.2).
blockers: none"""
)

10. Minimal Example (executor, proof-grade)

# Executor working on a legitimacy-axis task

# 1. Open the audit session. The returned session_id is the binding key.
session = audit_session_start(intent="implement P3.1.1 VRF leader selection",
                              task_id="P3.1.1")
# returns { session_id: "audit-xyz-…", intent, task_id, started_at }

# ... 5-step chain runs; each thought_record below threads session_id ...

thought_record(type="plan",     task_id="P3.1.1", agent_id="claude-code-t3-p3-1-1",
               content="<plan content>", session_id=session.session_id)
thought_record(type="analysis", task_id="P3.1.1", agent_id="claude-code-t3-p3-1-1",
               content="<analysis content>", session_id=session.session_id)
thought_record(type="decision", task_id="P3.1.1", agent_id="claude-code-t3-p3-1-1",
               content="<decision content>", session_id=session.session_id)

# 2. Verify the chain (by task_id, NOT session_id).
audit_verify_chain(task_id="P3.1.1")
# returns { valid: true, broken_count: 0, record_count: 3 } — green

# 3. Final reflection MUST come before merkle_finalize AND must carry session_id.
thought_record(
  type="reflection",
  task_id="P3.1.1",
  agent_id="claude-code-t3-p3-1-1",
  content="""task_id: P3.1.1
...
summary: Implemented θ Consensus VRF leader selection per spec §3.
blockers: none""",
  session_id=session.session_id
)

# 4. Finalize the Merkle root for this session.
merkle_finalize(session_id=session.session_id)
# returns { session_id, root, record_count: 4, finalized_at }

merkle_root(session_id=session.session_id)
# returns the same root, idempotently

task_update(id="P3.1.1", status="done", progress=100)

Note: in the proof-grade case, task_update is the last call after all audit/merkle steps, because the executor wants the Merkle leaf anchored before the task is publicly marked done.


Part of the R73 unification corpus. Governed by colibri-system.md.


Back to top

Colibri — documentation-first MCP runtime. Apache 2.0 + Commons Clause.

This site uses Just the Docs, a documentation theme for Jekyll.