Lifecycle of a Mutation

This document traces one complete mutation end-to-end: the creation of a task via task_create. Every mutation in Colibri follows this path. The tool changes (what happens in step 6), but the path is invariant.

The Mutation: task_create

A client wants to create a task. They send a JSON-RPC request over stdio to the Colibri MCP server:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "task_create",
    "arguments": {
      "project_id": "colibri",
      "title": "Implement α middleware chain",
      "description": "Build the 5-stage validation and audit pipeline",
      "type": "implementation",
      "proof_grade": true
    }
  }
}

This request now travels through the lifecycle.


Stage 1: Tool Lock (Serialization)

The MCP server’s entry point receives the JSON-RPC tools/call message.

Before any parsing or validation, the tool-lock stage acquires a lock on the task mutation queue. This ensures that exactly one tool call is in flight at any moment. If another client tries to call task_create at the same time, that call will queue and wait.

The tool lock is in-memory (not persisted to the database). It exists to satisfy SQLite’s single-writer constraint. While one tool is being executed, SQLite can safely write its changes without worrying about concurrent modifications.

Output: A lock token (internal, not visible to client). A timestamp marking entry into the chain.


Stage 2: Schema Validation (Zod)

The tool-lock stage releases (the request now holds the lock), and the schema-validate stage begins.

The task_create handler has a Zod schema:

const CreateTaskSchema = z.object({
  project_id: z.string().min(1),
  title: z.string().min(3).max(200),
  description: z.string().max(2000).optional(),
  type: z.enum(['implementation', 'refactor', 'doc', 'investigation', 'spike']),
  proof_grade: z.boolean().default(false),
});

The request arguments are validated against this schema. If validation fails, a ZodError is caught, a ζ error record is written (see Stage 3), and the mutation stops. The client receives a 400-style error response.

If validation succeeds, the payload is marked as safe. All downstream code can trust that project_id, title, description, type, and proof_grade conform to their types.

Output: Validated arguments object. Schema metadata (field names, types).


Stage 3: Audit Entry — ζ Observation Record

The audit-enter stage creates the first ζ thought record. This is where Colibri records the fact of the request before execution.

A new ζ observation record is inserted into the thought_records table:

INSERT INTO thought_records (
  id,
  thought_type,
  session_id,
  parent_chain_hash,
  content_hash,
  content,
  created_at
) VALUES (
  'obs_17...',
  'observation',
  'session_abc...',
  '47e8c0e...',  -- hash of previous record
  'e742f1a...',  -- SHA256(content)
  {
    "event": "tool_call",
    "tool_name": "task_create",
    "caller_id": "executor-03",
    "arguments": {
      "project_id": "colibri",
      "title": "Implement α middleware chain",
      ...
    },
    "schema_valid": true,
    "timestamp": "2026-04-13T14:22:33.456Z"
  },
  '2026-04-13T14:22:33.456Z'
);

The chain hash is computed as:

chain_hash(record) = SHA256(
  record.content_hash + previous_record.chain_hash
)

For the first record in the session, previous_record.chain_hash is the genesis hash: all zeros (0000000000000000000000000000000000000000000000000000000000000000).

The observation record now lives in the database, immutable. If the system crashes during dispatch (stage 4), this record survives. The mutation is logged.

The record is inserted in the same database transaction that will write the final result. If the transaction rolls back, both the observation and the result are rolled back together. This preserves consistency: either the observation + result exist, or neither does.

Output: A ζ record ID (e.g., obs_17...). A chain hash linking this record to the previous one.


Stage 4a: Gamma Mode Check (Mode Gating)

The dispatch stage begins. Before the handler is called, the system checks the current γ (gamma) mode. Colibri supports four modes:

  • FULL: All 19 tools are available. Normal execution.
  • READONLY: Read-only tools only (task_list, skill_list, etc.). Mutations blocked.
  • TEST: Sandbox mode. Writes go to an in-memory SQLite, not the production database.
  • MINIMAL: Only system tools (server_ping, unified_init). Used during bootstrap.

The task_create tool requires FULL or TEST mode. If γ is READONLY, the dispatch stage rejects the mutation and returns an error (that error gets recorded in Stage 5, the audit-exit).

If γ is FULL (the normal case), execution proceeds.

Output: Mode is FULL. Tool is allowed to execute.


Stage 4b: Handler Execution (Mutation Logic)

The task_create handler executes. Here is what it does:

  1. Generate a task UUID (e.g., task_b7a23f...).
  2. Set the initial state to INIT (not yet active).
  3. Write a new row to the tasks table:
INSERT INTO tasks (
  id,
  project_id,
  title,
  description,
  type,
  state,
  proof_grade,
  created_by,
  created_at
) VALUES (
  'task_b7a23f...',
  'colibri',
  'Implement α middleware chain',
  'Build the 5-stage validation and audit pipeline',
  'implementation',
  'INIT',
  TRUE,
  'executor-03',
  '2026-04-13T14:22:34.100Z'
);
  1. Return the created task object to the dispatch stage.

All of this happens in the same database transaction started in Stage 3. The transaction is open but not yet committed. If an error occurs during the handler, the entire transaction (observation + mutation) rolls back.

Output: A task row in SQLite. A task object (for the response). No commit yet.


Stage 5: Audit Exit — ζ Reflection Record

The audit-exit stage creates the second ζ record. This is where Colibri records the outcome of the mutation.

A new ζ reflection record is inserted into the same transaction:

INSERT INTO thought_records (
  id,
  thought_type,
  session_id,
  parent_chain_hash,
  content_hash,
  content,
  created_at
) VALUES (
  'ref_82...',
  'reflection',
  'session_abc...',
  'a9f44d2...',  -- chain_hash of the observation record
  '7e2c5b1...',  -- SHA256(content)
  {
    "event": "tool_result",
    "tool_name": "task_create",
    "caller_id": "executor-03",
    "result": {
      "task_id": "task_b7a23f...",
      "title": "Implement α middleware chain",
      "state": "INIT",
      ...
    },
    "status": "success",
    "timestamp": "2026-04-13T14:22:34.101Z"
  },
  '2026-04-13T14:22:34.101Z'
);

The reflection record’s parent chain hash is the chain hash of the observation record. This creates a two-link chain: observation → reflection.

Both records are now in the database, linked by hash. Both are waiting for the transaction to commit.

Output: A ζ reflection record. The parent-child hash chain (observation → reflection).


Stage 6: Transaction Commit and Response

The database transaction commits. All writes become permanent:

  • The tasks table now contains the new task.
  • The thought_records table now contains the observation and reflection.
  • The chain hashes are immutable (SQLite does not allow retroactive updates).

The MCP server sends the JSON-RPC response back to the client:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "task_id": "task_b7a23f...",
    "project_id": "colibri",
    "title": "Implement α middleware chain",
    "description": "Build the 5-stage validation and audit pipeline",
    "type": "implementation",
    "state": "INIT",
    "proof_grade": true,
    "created_by": "executor-03",
    "created_at": "2026-04-13T14:22:34.100Z"
  }
}

The tool lock is released. The next queued tool call (if any) is now allowed to execute.

Output: Committed state in SQLite. Response sent to client. Lock released.


Stage 7 (Deferred): Merkle Attestation (Proof Grade Only)

If proof_grade: true (as in this example), the system will eventually perform Stage 7. This stage is asynchronous and happens outside the synchronous request-response cycle.

The η proof store reads the reflection record and creates a Merkle tree leaf for it. The leaf is an opaque hash of the record’s content. All proof-grade records eventually populate a single Merkle tree. At the end of a work round (Phase 1.5+), the tree is sealed with merkle_finalize.

In Phase 0, merkle attestation is not yet implemented. The record is marked proof_grade: true, but the Merkle tree is not yet built. When Phase 1.5 ships, proof-grade records will be automatically picked up by the η system.

Output: An η leaf in the Merkle tree (Phase 1.5+).


The Invariant Path

This is the path every mutation takes, regardless of tool:

Tool Lock
  ↓
Schema Validate (Zod)
  ↓
Audit Enter (ζ observation record)
  ↓
Gamma Mode Check (γ gating)
  ↓
Handler Execution (tool-specific logic)
  ↓
Audit Exit (ζ reflection record)
  ↓
Transaction Commit
  ↓
JSON-RPC Response
  ↓
(Optional) Merkle Attest (η, proof grade only)

For task_create, the handler does: “create a task row in SQLite and return the task”. For task_list, the handler does: “query the tasks table and return all tasks matching filters”. For thought_record, the handler does: “insert a ζ record with caller-provided content and return the record ID”. For server_ping, the handler does: “return a pong message”.

The 19 tools differ in what happens in the handler. The other six stages are invariant. This is why the system is composable and trustworthy.


Chain Hash Formula

The chain hash that links records together uses this formula:

chain_hash(record) = SHA256(
  record.content_hash + parent_chain_hash
)

record.content_hash = SHA256(
  JSON.stringify(record.content)
)

parent_chain_hash = (previous record's chain_hash)
                  OR
                    (genesis hash "0000...0000" if first record)

The chain hash is stored in thought_records.chain_hash. It is unique and non-repeating, because the parent hash changes with every record. If you hash the same tool call twice, you get two different records with two different chain hashes.

This makes the ζ decision trail tamper-evident: if anyone retroactively modifies an old record, its content hash changes, which breaks all downstream chain hashes. A validator can walk the chain from the latest record back to genesis and detect any tampering instantly.


Summary: One Path, Many Tools

The task_create mutation shows the full path. Here is how the other 18 tools differ:

Tool Handler Logic Proof Grade?
task_create Create task row Optional
task_list Query tasks, apply filters No
task_update Update task row (state, assignee, etc.) Optional
task_plan Transition task to PLAN state Optional
task_apply Transition task to ACTIVE state Optional
task_block Mark task blocked, create block record Optional
task_unblock Unmark task blocked Optional
task_next_actions Query tasks in ACTIVE state, return next unblocked No
thought_record Insert ζ record with caller content Optional
audit_session_start Create a proof session, mark start Yes
audit_verify_chain Validate ζ chain integrity No
merkle_attest Create η leaf, add to proof tree Yes
merkle_finalize Seal the η tree Yes
merkle_root Return the root hash of the η tree Yes
skill_list Query skills registry, return list No
server_ping Return status No
unified_init Bootstrap system, run health checks No
unified_vitals Return system health metrics No
unified_set_project Set the current project name (γ state) No

Every single one goes through the invariant path: lock → validate → audit-enter → dispatch → audit-exit → commit.


Next: Where Truth Lives

See where-truth-lives.md to understand how SQLite, the four-surface topology, and the single-writer constraint together ensure that the mutation log is the source of truth.


Back to top

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

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