P0.7 — ζ Decision Trail — Agent Prompts
Copy-paste-ready prompts for agents tackling each task in this group. Canonical spec: task-breakdown.md §P0.7 Master bootstrap prompt: agent-bootstrap.md
Group summary
| Task ID | Title | Depends on | Effort | Unblocks |
|---|---|---|---|---|
| P0.7.1 | Hash-Chained Record Schema | P0.2.2 | S | P0.7.2 |
| P0.7.2 | Thought Record CRUD | P0.7.1 | M | P0.7.3, writeback enforcement |
| P0.7.3 | Chain Verification Tool | P0.7.2 | S | P0.8.x, audit-grade tasks |
P0.7.1 — Hash-Chained Record Schema
Spec source: task-breakdown.md §P0.7.1
Extraction reference: docs/reference/extractions/zeta-decision-trail-extraction.md
Worktree: feature/p0-7-1-hash-schema
Branch command: git worktree add .worktrees/claude/p0-7-1-hash-schema -b feature/p0-7-1-hash-schema origin/main
Estimated effort: S (Small — 1-2 hours)
Depends on: P0.2.2 (Database for record storage)
Unblocks: P0.7.2 (CRUD uses schema)
Files to create
src/domains/trail/schema.ts— Record Zod schema + SHA-256 hashingtests/domains/trail/schema.test.ts— Schema validation + hash determinism tests
Acceptance criteria
- Record schema:
{ id, type, task_id, agent_id, content, timestamp, prev_hash, hash } typeenum:plan | analysis | decision | reflection(4 valid types)hash = SHA-256(canonical_JSON({id, type, task_id, content, timestamp, prev_hash}))- Canonical JSON: sorted keys, no whitespace (deterministic)
- First record:
prev_hash = "0000...0000"(64 zeros) - Test: two records with identical inputs produce identical hashes
Pre-flight reading
CLAUDE.md— worktree rulesdocs/guides/implementation/task-breakdown.md§P0.7.1 — full specdocs/reference/extractions/zeta-decision-trail-extraction.md— record structuresrc/domains/trail/schema.tscontext (will be created in this task)
Ready-to-paste agent prompt
You are a Phase 0 builder agent for Colibri.
TASK: P0.7.1 — Hash-Chained Record Schema
Define Zod schema for thought records and implement deterministic SHA-256 hashing with previous-hash linking.
FILES TO READ FIRST:
1. CLAUDE.md (execution rules)
2. docs/guides/implementation/task-breakdown.md §P0.7.1
3. docs/reference/extractions/zeta-decision-trail-extraction.md
4. src/domains/trail/ (if exists; may reference later in chain)
WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p0-7-1-hash-schema -b feature/p0-7-1-hash-schema origin/main
cd .worktrees/claude/p0-7-1-hash-schema
FILES TO CREATE:
- src/domains/trail/schema.ts
* Zod schema ThoughtRecord:
- id: string (UUID or unique ID)
- type: enum ["plan", "analysis", "decision", "reflection"]
- task_id: string (reference to task)
- agent_id: string (reference to executing agent)
- content: string (thought/decision content)
- timestamp: Date (ISO string or timestamp)
- prev_hash: string (64-hex SHA-256, first record = "0000...0000")
- hash: string (computed field, 64-hex SHA-256)
* Function: computeHash(record: Omit<ThoughtRecord, 'hash'>): string
- Build canonical JSON object: {id, type, task_id, content, timestamp, prev_hash}
- Keys MUST be sorted alphabetically at every nesting level
- NO whitespace, NO line breaks, deterministic UTF-8 encoding
- Return SHA-256 hex (lowercase)
* Function: createRecord(input: {...}): ThoughtRecord
- Auto-assign id (UUID v4)
- Auto-assign timestamp (now)
- Set prev_hash (default "0000...0000" or pass from previous record)
- Compute and set hash
- Return complete record
* Use crypto.createHash('sha256') from Node.js crypto module
* Export types: ThoughtRecord, RecordType
- tests/domains/trail/schema.test.ts
* Test computeHash determinism: hash same input twice → identical output
* Test canonical JSON: {a: 1, b: 2} and {b: 2, a: 1} → same hash
* Test type enum validation: valid ["plan", "analysis", "decision", "reflection"], invalid "unknown"
* Test prev_hash linking: record with prev_hash chains correctly
* Test first record: prev_hash = "0000...0000" (default)
* Test content tampering: modify content slightly → hash changes
* Test full chain: create record1 with default prev_hash, create record2 using record1.hash as prev_hash → hashes differ
ACCEPTANCE CRITERIA (headline):
✓ Zod schema with id, type (4-enum), task_id, agent_id, content, timestamp, prev_hash, hash
✓ Canonical JSON: sorted keys, no whitespace
✓ SHA-256 hash computed from {id, type, task_id, content, timestamp, prev_hash}
✓ First record prev_hash = "0000...0000"
✓ Two identical inputs produce identical hashes (deterministic)
SUCCESS CHECK:
cd .worktrees/claude/p0-7-1-hash-schema && npm test && npm run lint
WRITEBACK (after success):
task_update(task_id="P0.7.1", status="done", progress=100)
thought_record(task_id="P0.7.1", branch="feature/p0-7-1-hash-schema",
commit_sha=<your-sha>, tests_run=["npm test","npm run lint"],
summary="Implemented ThoughtRecord Zod schema with 4-type enum (plan|analysis|decision|reflection). SHA-256 hash computed from canonical JSON (sorted keys, no whitespace) of {id,type,task_id,content,timestamp,prev_hash}. First record prev_hash = '0000...0000'. Hash determinism verified: identical inputs → identical hashes.")
FORBIDDENS:
✗ Do not use JSON.stringify alone for canonical JSON (keys must be sorted at every level)
✗ Do not use crypto.subtle (use Node.js crypto module for SHA-256)
✗ Do not allow prev_hash as mutable field after creation (immutable once set)
✗ Do not edit main checkout
NEXT:
P0.7.2 — Thought Record CRUD (uses schema to persist + retrieve records with hash linking)
Verification checklist (for reviewer agent)
- Zod schema with 4-type enum
- Record fields: id, type, task_id, agent_id, content, timestamp, prev_hash, hash
- computeHash uses canonical JSON (sorted keys, no whitespace)
- SHA-256 from Node.js crypto module
- First record default prev_hash = “0000…0000”
- Two identical inputs produce identical hashes
- Test covers hash determinism, type validation, prev_hash linking
- npm test and npm run lint pass
Writeback template
task_update:
task_id: P0.7.1
status: done
progress: 100
thought_record:
task_id: P0.7.1
branch: feature/p0-7-1-hash-schema
commit_sha: <sha>
tests_run: ["npm test", "npm run lint"]
summary: "Implemented ThoughtRecord Zod schema with id, type (plan|analysis|decision|reflection), task_id, agent_id, content, timestamp, prev_hash, hash. computeHash() builds canonical JSON with sorted keys at every level, no whitespace, deterministic SHA-256. First record prev_hash defaults to '0000...0000' (64 zeros). Test verifies hash determinism: identical inputs → identical outputs. Chain linking ready for P0.7.2."
blockers: []
Common gotchas
- Canonical JSON must use sorted keys at every nesting level — Use a recursive key-sorting function or a dedicated canonicalization library (e.g.,
json-canonicalizenpm package). JSON.stringify does NOT sort keys by default. If you mix key order, hashes will differ unpredictably. - UTF-8 encoding matters — SHA-256 is sensitive to byte encoding. Ensure consistent UTF-8 (Node.js default). Test with non-ASCII characters if your records might contain them.
- First record prev_hash is a sentinel — All 64 zeros (0000…0000) signals “no predecessor.” Do not use null, undefined, or empty string. The chain verifier (P0.7.3) will look for this exact pattern.
- Immutability after creation — Once a record is created with a hash, do not allow mutations to any field (especially
contentorprev_hash). The schema should reflect this (e.g., readonly fields if using TypeScript interfaces).
P0.7.2 — Thought Record CRUD
Spec source: task-breakdown.md §P0.7.2
Extraction reference: docs/reference/extractions/zeta-decision-trail-extraction.md (CRUD section)
Worktree: feature/p0-7-2-trail-crud
Branch command: git worktree add .worktrees/claude/p0-7-2-trail-crud -b feature/p0-7-2-trail-crud origin/main
Estimated effort: M (Medium — 2-3 hours)
Depends on: P0.7.1 (Uses hash schema)
Unblocks: P0.7.3 (Chain verification), P0.8.x (Merkle proofs), writeback enforcement
Files to create
src/domains/trail/repository.ts— Record CRUD, hash linking, persistencesrc/tools/trail.ts— MCP tools:thought_record,thought_record_listtests/domains/trail/repository.test.ts— Persistence, linking, MCP tool tests
Acceptance criteria
createThoughtRecord(input)computes hash, links to previous record’s hashgetThoughtRecord(id)returns record with hashlistThoughtRecords({task_id?, limit?})returns chain in insertion orderthought_recordMCP tool with Zod input returns record with computed hashthought_record_listMCP tool returns chain for given task_id- Stores in SQLite
thought_recordstable (use P0.2.2 schema)
Pre-flight reading
CLAUDE.md— execution rulesdocs/guides/implementation/task-breakdown.md§P0.7.2 — full specdocs/reference/extractions/zeta-decision-trail-extraction.md(CRUD section)src/domains/trail/schema.ts(hash schema from P0.7.1)src/db/schema.sqlor similar (database schema from P0.2.2)
Ready-to-paste agent prompt
You are a Phase 0 builder agent for Colibri.
TASK: P0.7.2 — Thought Record CRUD
Implement database persistence, hash linking, and MCP tools for creating and querying thought records.
FILES TO READ FIRST:
1. CLAUDE.md (execution rules)
2. docs/guides/implementation/task-breakdown.md §P0.7.2
3. docs/reference/extractions/zeta-decision-trail-extraction.md (CRUD section)
4. src/domains/trail/schema.ts (schema from P0.7.1)
5. src/db/schema.sql or similar (database schema)
WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p0-7-2-trail-crud -b feature/p0-7-2-trail-crud origin/main
cd .worktrees/claude/p0-7-2-trail-crud
FILES TO CREATE:
- src/domains/trail/repository.ts
* Class TrailRepository:
- Constructor: initializes DB connection (use existing P0.2.2 setup)
- createThoughtRecord(input: {type, task_id, agent_id, content, prev_hash?}): ThoughtRecord
- Generate id (UUID v4)
- Auto timestamp (now)
- If no prev_hash, use "0000...0000"
- Compute hash using schema.computeHash()
- Persist to DB
- Return complete record
- getThoughtRecord(id: string): ThoughtRecord | null
- Query DB, return record or null
- listThoughtRecords(filters: {task_id?: string, limit?: number}): ThoughtRecord[]
- Query DB, filter by task_id if provided
- Order by timestamp ASC (insertion order)
- Limit if provided (default no limit)
- Return array of records
* Use better-sqlite3 or similar (consistent with P0.2.2)
* Error handling: wrap DB errors, log cleanly
* Static singleton: export instance
- src/tools/trail.ts (MCP tools)
* thought_record: input {type, task_id, agent_id, content, prev_hash?}
- Validate input with Zod (ThoughtRecord schema)
- Call repository.createThoughtRecord(...)
- Return {record: {...}} with full record + computed hash
* thought_record_list: input {task_id: string, limit?: number}
- Call repository.listThoughtRecords({task_id, limit})
- Return {records: [...]} in insertion order
- Include full records (all fields including hash)
- tests/domains/trail/repository.test.ts
* Test createThoughtRecord: persist to DB, compute hash, return record
* Test hash linking: create record1 → create record2 with prev_hash = record1.hash → verify chain
* Test getThoughtRecord: retrieve by id, verify all fields
* Test listThoughtRecords: query by task_id, verify insertion order, respect limit
* Test MCP tools: thought_record creates + returns, thought_record_list retrieves
* Test chain integrity: create 10 records in sequence, verify each links to previous
* Test filtering: create records for multiple tasks, query by task_id → correct subset
ACCEPTANCE CRITERIA (headline):
✓ createThoughtRecord(input) computes hash, links to previous
✓ getThoughtRecord(id) returns record with hash
✓ listThoughtRecords({task_id?, limit?}) returns chain in insertion order
✓ thought_record MCP tool with Zod input, returns computed hash
✓ thought_record_list MCP tool returns chain for task_id
✓ Persists to SQLite using P0.2.2 schema
SUCCESS CHECK:
cd .worktrees/claude/p0-7-2-trail-crud && npm test && npm run lint
WRITEBACK (after success):
task_update(task_id="P0.7.2", status="done", progress=100)
thought_record(task_id="P0.7.2", branch="feature/p0-7-2-trail-crud",
commit_sha=<your-sha>, tests_run=["npm test","npm run lint"],
summary="Implemented TrailRepository with createThoughtRecord (auto-hash, prev_hash linking), getThoughtRecord (retrieve by id), listThoughtRecords (query by task_id + limit). 2 MCP tools: thought_record (create with Zod validation), thought_record_list (query chain). SQLite persistence using P0.2.2 schema. Chain linking verified in tests: 10-record sequence → each links to previous hash.")
FORBIDDENS:
✗ Do not allow hash recomputation after creation (use schema.computeHash once)
✗ Do not skip prev_hash linking (always set it, default to "0000...0000")
✗ Do not use non-insertion-order retrieval (listThoughtRecords must be ASC by timestamp)
✗ Do not edit main checkout
NEXT:
P0.7.3 — Chain Verification Tool (verifies hash links across entire chain)
Verification checklist (for reviewer agent)
- createThoughtRecord persists to DB with computed hash
- getThoughtRecord returns complete record including hash
- listThoughtRecords filters by task_id, respects limit, insertion order (ASC)
- thought_record MCP tool validates input, returns record with hash
- thought_record_list MCP tool returns chain for task_id
- prev_hash linking: create record1 → record2 uses record1.hash as prev_hash
- Test covers chain integrity (10 records verify each links to previous)
- npm test and npm run lint pass
Writeback template
task_update:
task_id: P0.7.2
status: done
progress: 100
thought_record:
task_id: P0.7.2
branch: feature/p0-7-2-trail-crud
commit_sha: <sha>
tests_run: ["npm test", "npm run lint"]
summary: "Implemented TrailRepository with createThoughtRecord (auto-compute hash, link to previous via prev_hash), getThoughtRecord (retrieve by id), listThoughtRecords (query by task_id, optional limit, insertion order). MCP tools: thought_record (create with Zod validation) and thought_record_list (retrieve chain). SQLite persistence using P0.2.2 schema. Chain integrity verified: create 10 records → each links to predecessor hash."
blockers: []
Common gotchas
- Recomputing hashes from prev_hash chains amplifies bugs — Write the tamper-test FIRST (modify one record’s content, verify chain breaks at correct position), then implement verifyChain. This catches logic errors early.
- prev_hash must be set on creation, not later — Do not allow mutable updates to prev_hash. If you store it as nullable and fill it in later, you’ll miss hash mismatches when the chain is incomplete.
- Insertion order matters for audit trails — Store and retrieve by timestamp (ASC). Do not use auto-increment IDs as the ordering signal (timestamps may overlap).
- First record sentinel — The first record in any task must have prev_hash = “0000…0000”. If a task starts with a later record (no prior), that record’s prev_hash should be the sentinel, not null.
P0.7.3 — Chain Verification Tool
Spec source: task-breakdown.md §P0.7.3
Extraction reference: docs/reference/extractions/zeta-decision-trail-extraction.md (verification section)
Worktree: feature/p0-7-3-chain-verifier
Branch command: git worktree add .worktrees/claude/p0-7-3-chain-verifier -b feature/p0-7-3-chain-verifier origin/main
Estimated effort: S (Small — 1-2 hours)
Depends on: P0.7.2 (Uses CRUD + schema)
Unblocks: P0.8.x, audit-grade tasks
Files to create
src/domains/trail/verifier.ts— Chain verification logicsrc/tools/audit.ts— MCP tool:audit_verify_chaintests/domains/trail/verifier.test.ts— Tamper tests, performance tests
Acceptance criteria
verifyChain(records[])iterates chain, recomputes each hash, checks links- Returns
{ valid: bool, first_broken_at?: id, broken_count: number } audit_verify_chainMCP tool calls verifyChain on full DB chain- Test: tamper with one record’s content → verify
valid: falseat correct position - Test: intact 100-record chain →
valid: truein < 500ms
Pre-flight reading
CLAUDE.md— execution rulesdocs/guides/implementation/task-breakdown.md§P0.7.3 — full specdocs/reference/extractions/zeta-decision-trail-extraction.md(verification section)src/domains/trail/schema.ts(hash schema)src/domains/trail/repository.ts(CRUD from P0.7.2)
Ready-to-paste agent prompt
You are a Phase 0 builder agent for Colibri.
TASK: P0.7.3 — Chain Verification Tool
Verify thought record chains by recomputing hashes and checking prev_hash links. Detect tampering.
FILES TO READ FIRST:
1. CLAUDE.md (execution rules)
2. docs/guides/implementation/task-breakdown.md §P0.7.3
3. docs/reference/extractions/zeta-decision-trail-extraction.md (verification section)
4. src/domains/trail/schema.ts (hash schema, computeHash function)
5. src/domains/trail/repository.ts (CRUD + DB access)
WORKTREE SETUP:
git fetch origin
git worktree add .worktrees/claude/p0-7-3-chain-verifier -b feature/p0-7-3-chain-verifier origin/main
cd .worktrees/claude/p0-7-3-chain-verifier
FILES TO CREATE:
- src/domains/trail/verifier.ts
* Function verifyChain(records: ThoughtRecord[]): VerificationResult
- Input: array of records in chronological order
- For each record i:
- Recompute expected hash using schema.computeHash({id, type, task_id, content, timestamp, prev_hash})
- Compare to record[i].hash
- If mismatch: mark as broken
- Check prev_hash link: record[i].prev_hash must equal record[i-1].hash (or "0000...0000" if i==0)
- If link broken: mark as broken
- Return VerificationResult {valid: bool, first_broken_at?: id, broken_count: number}
* Type VerificationResult: {valid: boolean, first_broken_at?: string, broken_count: number}
* Handle edge cases: empty array (valid=true), single record with sentinel prev_hash (valid=true)
* Performance: optimize for 100+ records, should complete in < 500ms
* Logging: use debug logger for each check (no console.log)
- src/tools/audit.ts (MCP tool)
* audit_verify_chain: no input parameters
- Query DB for ALL thought records (ordered by timestamp ASC)
- Call verifyChain(records)
- Return {valid: bool, first_broken_at?: id, broken_count: number, total_records: number, latency_ms: number}
- Include latency measurement (start time at query, end time after verification)
- tests/domains/trail/verifier.test.ts
* Test verifyChain with valid chain: create 5 records, verify → valid=true
* Test first record sentinel: prev_hash = "0000...0000" → valid
* Test prev_hash linking: create 5-record chain, verify each link → valid=true
* Test tamper with content: create 5-record chain, modify record[2].content, recompute hash (NO, leave as is), call verify
- Expected: valid=false, first_broken_at=record[2].id, broken_count=1
- Verify subsequent records are NOT marked broken (they don't know about tampering yet)
* Test tamper with hash: create chain, set record[2].hash to "aaa...aaa" (wrong), call verify
- Expected: valid=false, first_broken_at=record[2].id
- Record[3] prev_hash will reference wrong hash → also broken, broken_count=2
* Test tamper with prev_hash: set record[2].prev_hash to wrong value, call verify
- Expected: valid=false, first_broken_at=record[2].id (link check fails)
* Test empty chain: verifyChain([]) → valid=true, broken_count=0
* Test performance: create 100-record chain, measure time → should complete in < 500ms
* Test MCP tool: call audit_verify_chain on full DB → includes latency_ms
ACCEPTANCE CRITERIA (headline):
✓ verifyChain(records[]) recomputes hashes, checks prev_hash links
✓ Returns {valid, first_broken_at?, broken_count}
✓ Detects content tampering (hash mismatch)
✓ Detects link tampering (prev_hash mismatch)
✓ audit_verify_chain MCP tool on full DB chain
✓ 100-record chain verifies in < 500ms
SUCCESS CHECK:
cd .worktrees/claude/p0-7-3-chain-verifier && npm test && npm run lint
WRITEBACK (after success):
task_update(task_id="P0.7.3", status="done", progress=100)
thought_record(task_id="P0.7.3", branch="feature/p0-7-3-chain-verifier",
commit_sha=<your-sha>, tests_run=["npm test","npm run lint"],
summary="Implemented verifyChain(records[]): iterates chain, recomputes hash for each record, validates prev_hash links. Returns {valid, first_broken_at?, broken_count}. Detects content tampering (hash recompute fails), link tampering (prev_hash reference wrong). MCP tool audit_verify_chain calls verifyChain on full DB, includes latency_ms. 100-record chain: < 500ms. Tamper tests verify correct broken_at detection.")
FORBIDDENS:
✗ Do not skip hash recomputation (always verify hash, never trust record.hash)
✗ Do not trust prev_hash links without validation (always compare to predecessor's hash)
✗ Do not allow tampering to propagate silently (mark first_broken_at when detected)
✗ Do not edit main checkout
NEXT:
P0.8.1 — Merkle Tree Construction (builds proof tree from verified hashes)
Verification checklist (for reviewer agent)
- verifyChain recomputes hash for each record
- verifyChain checks prev_hash link to predecessor
- Returns {valid, first_broken_at?, broken_count}
- audit_verify_chain MCP tool queries DB, calls verifyChain
- Tamper test: modify content, verify detects at correct position
- Link tamper test: modify prev_hash, verify detects link break
- 100-record chain performance < 500ms
- Empty chain and single record (sentinel) handle correctly
- npm test and npm run lint pass
Writeback template
task_update:
task_id: P0.7.3
status: done
progress: 100
thought_record:
task_id: P0.7.3
branch: feature/p0-7-3-chain-verifier
commit_sha: <sha>
tests_run: ["npm test", "npm run lint"]
summary: "Implemented verifyChain(records[]): for each record, recompute hash from canonical JSON and compare to stored hash; validate prev_hash link to predecessor (or sentinel '0000...0000' for first record). Returns {valid: boolean, first_broken_at?: id, broken_count: number}. Detects content tampering (hash mismatch) and link tampering (prev_hash mismatch). MCP tool audit_verify_chain queries full DB chain and includes latency_ms. Performance: 100-record chain < 500ms. Tamper tests verify correct first_broken_at detection."
blockers: []
Common gotchas
- Recomputing hashes FIRST (tamper test, then implementation) — Write the tamper test before verifyChain. This forces you to think through the logic: if you modify content and don’t recompute the hash, that record’s stored hash is now wrong. The verifier should catch it. If you implement naively, you’ll miss this.
- prev_hash chain is sensitive to order — If records are not in chronological order, prev_hash links will be wrong. Always verify input is sorted by timestamp before checking links.
- First record sentinel check — For i==0, prev_hash MUST be “0000…0000” (all 64 zeros). Do NOT allow null, undefined, or empty string. This signals “no predecessor.”
- Broken count accumulation — If you tamper with record[2] hash, record[3]’s prev_hash will NOT match (it points to record[2].hash, but record[2] is broken). Should broken_count be 1 or 2? It should be 1 (only record[2] itself is tampered). Record[3]’s link is correct per the DB; the issue is upstream. Clarify this in tests.