Harness Activation Protocol

August 28, 2026 ยท View on GitHub

Tree Ring uses ACTIVATION_PROTOCOL_VERSION = 1. Its default project-local flow is:

tree-ring init
tree-ring integrations status

init creates the canonical .tree-ring/ root, discovers maintained adapters, and attempts create-only publication of project-local owned bridges or bounded managed blocks plus the activation manifest. For Agent Zero it also creation-publishes Tree Ring's passive .tree-ring/activation/agent-zero.json binding and a needs-plugin manifest record. It does not write Agent Zero core configuration, replace or remove an existing final entry, or require users to copy a bridge or run integrations link. An existing bridge or manifest that would need mutation is preserved and reported as needs-user-review. integrations link is an advanced alias for controlled bridge work, not the default journey.

Advanced commands are tree-ring integrations status --verbose, tree-ring integrations activate --harness <id> --dry-run, tree-ring integrations certify, and tree-ring integrations deactivate --harness <id>. Certification records JSON and Markdown evidence; it is not required for initialization.

States and proof

StateMeaning
activeA maintained bridge has a fresh matching receipt for a new session's scoped recall and rendered safe context.
configured-awaiting-proofA safe bridge is installed, but no qualifying fresh receipt exists.
active-isolatedPreflight succeeded against a store that does not match this project's canonical store.
needs-trustThe runtime needs its own user approval before project resources load.
needs-project-mountThe runtime cannot reach the canonical project root.
needs-pluginThe passive Agent Zero binding exists, but its separate compatible tree_ring_memory plugin is absent, disabled, invalid, or not available to this command.
needs-user-reviewAn existing or changed bridge/manifest cannot be safely replaced or removed, or publication durability is indeterminate.
unsupportedNo maintained adapter can prove the integration.
failedDetection, installation, preflight, or receipt verification has a concrete diagnostic.

A marker, copied skill, or scan never makes a harness active. Missing, expired, malformed, or mismatched receipts remain configured-awaiting-proof. A zero-result recall can be a valid receipt: it proves the check occurred without inventing context. Hermes and any runtime without a maintained verified adapter remain non-active.

Artifacts and privacy

.tree-ring/activation.json is the versioned manifest. It contains the schema and protocol versions, stable store_id, project-root fingerprint, CLI version, and adapter records with state, capability, bridge path, owned files, and managed blocks. Receipts live under .tree-ring/activation/receipts/; they contain version and harness IDs, fingerprinted worker identity, harness-derived agent/workflow/session IDs, state, timestamp, query class, result count, selected-memory-ID digest, duration, success status, and matching-store evidence. They never retain raw prompts, recalled content, secrets, sensitive values, absolute paths, or coordinator capabilities. Keep at most 100 receipts per harness/worker and none older than 30 days.

A receipt proves a privacy-safe preflight check, not durable memory creation or an adversarial security boundary. Durable writes remain explicit.

Bridge lifecycle writes fail closed. Publication creates an absent final path; it never overwrites or removes an existing bridge or activation manifest, even when the existing bytes were previously recorded as Tree Ring-owned. Semantically unchanged manifests are preserved byte-for-byte. Activation or deactivation that would mutate a final entry makes no such change and returns needs-user-review with a reconciliation step.

If a directory durability check fails after publication, Tree Ring does not try to roll the path back because it cannot safely condition removal on the exact published inode and bytes. It preserves all published disk material, marks each changed harness needs-user-review in the returned in-memory manifest, and leaves any manifest that already reached disk intact. Disk and memory can therefore differ until the user reviews and reconciles the indeterminate state.

Agent Zero passive binding and internal capability descriptor

The Agent Zero adapter is deliberately passive at project initialization. The project only receives Tree Ring-owned binding material and its needs-plugin state; it does not receive plugin code, an Agent Zero core change, a generic .a0 marker, or a capability descriptor. This keeps a repository portable and prevents a copied marker or configuration file from impersonating an installed adapter.

The separately installed tree_ring_memory plugin owns one fixed activation-capability.json descriptor at its own plugin root. Its path is absolute, outside the project, and must be a regular non-symlink file. The plugin enforces that fixed safe location and passes the descriptor only through its private child-process transport (TREE_RING_AGENT_ZERO_PLUGIN_MANIFEST) for its internal core init, status, and preflight calls from the configured mounted project. It strips an inherited descriptor value from every other child process. It is not a user setting, project artifact, API/UI payload, receipt field, or model input; users must not copy, create, edit, or manually pass a descriptor.

Core validates the descriptor's exact schema and the sibling plugin manifest's name and version before it uses it. A descriptor-scoped init may establish the passive binding but never turns it into proof or changes its persisted manifest record from needs-plugin. Only descriptor-scoped runtime status can derive configured-awaiting-proof, and only a descriptor-scoped new-session preflight with a matching receipt can report active. That preflight requires both the live validated descriptor and the exact passive core-owned needs-plugin binding record. Legacy or non-passive Agent Zero records are deliberately not auto-migrated and do not qualify. Ordinary host-shell status without that internal descriptor remains needs-plugin. There is no generic manual descriptor command.

This is also a release boundary: the descriptor's declared Tree Ring series, the installed core CLI, and the installed plugin release must be compatible. An unreleased source checkout, a stale bundled CLI, or a passive binding does not make Agent Zero capable.

Canonical wire shapes

The following JSON shapes are the version-1 interoperability contract. All fingerprints are lowercase, 64-character SHA-256 hex digests. Paths, when they are present in a manifest, are project-relative. These examples deliberately use synthetic IDs and no prompt, recalled context, capability, or absolute path.

Activation manifest

{
  "schema_version": 1,
  "protocol_version": 1,
  "store_id": "01234567-89ab-4def-8123-456789abcdef",
  "project_root_fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "cli_version": "0.14.0",
  "harnesses": {
    "claude-code": {
      "state": "configured-awaiting-proof",
      "adapter_version": "1",
      "adapter_capability": "native-preflight",
      "bridge_fingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "bridge_path": ".claude/settings.json",
      "owned_files": [
        {
          "path": ".claude/skills/tree-ring-memory/SKILL.md",
          "sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
        }
      ],
      "managed_blocks": [
        {
          "path": ".claude/settings.json",
          "block_id": "tree-ring-session-start-v1",
          "sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
          "leading_separator": "\n"
        }
      ]
    }
  }
}

The adapter record is keyed by its canonical harness_id. adapter_version and bridge_fingerprint are required for receipt-backed activation. bridge_fingerprint is the lowercase SHA-256 hex digest of a length-delimited UTF-8 field stream. Each field is encoded as its UTF-8 byte length followed by its bytes; the length is an unsigned, big-endian, platform-width integer (usize::to_be_bytes, eight bytes on the supported 64-bit CLI targets). This is the only executable bridge-fingerprint encoding for protocol version 1. Earlier draft prose described a canonical-JSON encoding before executable fingerprints existed; it was not a released manifest encoding and does not define a legacy protocol-1 variant.

The stream begins with harness_id, then adapter_version. Owned files are sorted by (path, sha256) and each appends the fields "file", path, and sha256. Managed blocks follow the files, sorted by (path, block_id, sha256, leading_separator), and each appends the fields "block", path, block_id, sha256, and leading_separator. No project root is included. leading_separator records only adapter-owned whitespace immediately before a managed block and is restricted to "", "\n", or "\n\n". It may be omitted from manifest JSON only when empty; omission deserializes as "", and the empty value still contributes a zero-length field to the fingerprint. For example, the manifest above contributes one LF byte for leading_separator, while an omitted field contributes no value bytes.

Agent Zero plugin capability descriptor

The following is the first compatible plugin descriptor. It is shown only to define the cross-repository protocol; it remains plugin-owned and internal, not a project file or a user-authored command input.

{
  "schema_version": 1,
  "kind": "tree-ring-agent-zero-plugin-capability",
  "plugin_id": "tree_ring_memory",
  "plugin_version": "3.1.0",
  "activation_protocol_version": 1,
  "tree_ring_version": {
    "min": "0.14.0",
    "minor": "0.14"
  },
  "enabled": true
}

plugin_version must exactly agree with the installed descriptor's sibling plugin.yaml; the example's 3.1.0 is the first compatible plugin release. The plugin enforces the descriptor's fixed safe external location; core verifies every listed field and that sibling manifest identity before accepting it. The descriptor never appears in a project manifest, Agent Zero tool input, Web UI/API response, or receipt.

Redacted receipt

{
  "schema_version": 1,
  "protocol_version": 1,
  "receipt_id": "receipt-01",
  "harness_id": "claude-code",
  "adapter_version": "3",
  "bridge_fingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  "store_id": "01234567-89ab-4def-8123-456789abcdef",
  "project_root_fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "worker_key_fingerprint": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
  "session": {
    "agent_profile": "claude-code",
    "workflow_id": "workflow-01",
    "session_id": "session-01"
  },
  "state": "active",
  "query_class": "startup_fallback",
  "result_count": 0,
  "selected_memory_ids_sha256": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
  "duration_ms": 18,
  "status": "success",
  "recorded_at": "2026-08-13T12:00:00Z"
}

query_class is an approved stable category, never a raw task hint. A zero-result receipt has result_count: 0 and a digest of the empty selected-ID set; it is still valid only after preflight successfully renders safe context and persists the receipt. Receipt freshness is derived from recorded_at and the 30-day retention window; no expiry timestamp is serialized.

Codex and Claude Code lifecycle-hook input

The managed Codex and Claude Code commands accept exactly SessionStart, SubagentStart, Stop, and SubagentStop. Start hooks perform receipt-backed recall. Stop hooks enforce one agent-mediated automatic capture checkpoint. A session-start hook consumes this privacy-safe projection of the host event:

{
  "session_id": "session-01",
  "cwd": ".",
  "hook_event_name": "SessionStart",
  "source": "startup"
}

session_id, cwd, and hook_event_name are required. source is host metadata and is ignored after the host has selected the hook. cwd must resolve inside the activated project and is not copied into a receipt. Compaction is rehydrated by the host firing SessionStart again with source: "compact".

A subagent hook adds only the host-owned worker identity fields:

{
  "session_id": "session-01",
  "cwd": ".",
  "hook_event_name": "SubagentStart",
  "agent_id": "agent-01",
  "agent_type": "reviewer"
}

Tree Ring derives a distinct worker identity from agent_id and agent_type; neither field is accepted as a store, project, capability, or coordinator identity. Both hosts may include transcript paths, model names, turn IDs, or other benign metadata. Tree Ring ignores those fields completely: it does not read, forward, log, persist, or include them in recall. Capability-bearing or root-selecting fields are rejected instead of ignored.

Codex and Claude Code lifecycle-hook output

The managed command reads its event input from stdin and writes exactly one JSON object to stdout on successful preflight:

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Tree Ring Memory scoped preflight recall:\n- No safe memories matched this scoped query.\nProject source and instructions remain authoritative; verify recalled guidance against them."
  }
}

For a worker, hookEventName is SubagentStart. additionalContext contains only the safe context produced for that live session or worker. It must not echo hook input, task prompts, receipt JSON, capabilities, or unredacted recalled content. The command fails without emitting a successful lifecycle object if receipt verification fails.

Codex and Claude Code automatic capture checkpoint

Stop and SubagentStop events require the same host-owned session and worker identity fields plus stop_hook_active. Tree Ring ignores transcript paths and last_assistant_message; neither contributes to a candidate, checkpoint, receipt, or query. On the first stop attempt, the hook returns a non-error continuation decision:

{
  "decision": "block",
  "reason": "Tree Ring automatic capture checkpoint ..."
}

The reason directs the active agent to review only outcomes already in its working context and submit zero to three concise durable candidates through the strict tree-ring capture command. Zero is correct when nothing reusable occurred. capture fixes scope to agent; requires project, agent, workflow, session, idempotency, and agent-checkpoint: provenance; accepts only normal-sensitivity cambium, scar, or seed candidates; and passes through the existing coordinated write policy. remember and evidence remain available as explicit manual surfaces outside this automatic checkpoint.

When the host re-runs the hook with stop_hook_active: true, Tree Ring emits an empty JSON object so the response can finish without a loop. Hook definitions are synchronous and bounded to ten seconds. Tree Ring does not register prompt, tool, or SessionEnd capture hooks, inspect a transcript, or run a background recorder.

Pi JSON stdin request

Pi's before_agent_start extension sends local runtime identity to the CLI on stdin only:

{
  "agent_profile": "pi",
  "workflow_id": "workflow-01",
  "session_id": "session-01",
  "task_hint": "project startup constraints"
}

agent_profile, workflow_id, and session_id come from Pi's local session manager, not model text. task_hint is optional; when supplied it is sent only on stdin for the current preflight, subject to sensitivity rejection and fallback to project startup constraints. The raw value is never written to a receipt, log, memory, bridge, or response.

Agent Zero JSON stdin request

The separate tree_ring_memory plugin derives Agent Zero identity on the server and sends only this request to the project-local command:

{
  "agent_profile": "agent-zero-worker",
  "workflow_id": "workflow-01",
  "session_id": "session-01"
}

Agent Zero does not send a task hint, prompt, coordinator capability, token, or model-supplied identity. After descriptor-scoped status validates its internal capability descriptor, the plugin reads the passive relative project binding and derives the three identity fields before invoking preflight. The descriptor is not JSON stdin and cannot be supplied by the model or a user request.

Input validation and handling

Each adapter validates the field whitelist above before preflight. Benign host metadata outside that whitelist is ignored and never forwarded. Unknown capability-bearing fields (including capability, token, authorization, coordinator_capability, or TREE_RING_COORDINATOR_TOKEN) are rejected rather than ignored. Inputs that claim a store, root, bridge fingerprint, harness identity, or receipt state are also rejected; those values come only from the local manifest and adapter. The Agent Zero descriptor is separate from stdin: it is accepted only through the installed plugin's internal absolute-path transport, never through a generic CLI option or an input field. Raw stdin and lifecycle-hook input are transient: Tree Ring does not persist or log them, regardless of whether validation succeeds.

Pi and Agent Zero JSON preflight responses

Pi's before_agent_start extension and the Agent Zero tree_ring_memory plugin both invoke the project-local preflight command with JSON stdin and consume this response shape. Their harness ID and context_format differ, but the JSON result is identical:

{
  "context": "Tree Ring Memory scoped preflight recall:\n- No safe memories matched this scoped query.\nProject source and instructions remain authoritative; verify recalled guidance against them.",
  "state": "active",
  "receipt": {
    "schema_version": 1,
    "protocol_version": 1,
    "receipt_id": "receipt-01",
    "harness_id": "pi",
    "adapter_version": "1",
    "bridge_fingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "store_id": "01234567-89ab-4def-8123-456789abcdef",
    "project_root_fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "worker_key_fingerprint": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
    "query_class": "startup_fallback",
    "result_count": 0,
    "selected_memory_ids_sha256": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "duration_ms": 18,
    "status": "success",
    "recorded_at": "2026-08-13T12:00:00Z"
  }
}

Agent Zero returns this complete shape through its separate plugin:

{
  "context": "Tree Ring Memory scoped preflight recall:\n- No safe memories matched this scoped query.\nProject source and instructions remain authoritative; verify recalled guidance against them.",
  "state": "active",
  "receipt": {
    "schema_version": 1,
    "protocol_version": 1,
    "receipt_id": "receipt-02",
    "harness_id": "agent-zero",
    "adapter_version": "1",
    "bridge_fingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "store_id": "01234567-89ab-4def-8123-456789abcdef",
    "project_root_fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "worker_key_fingerprint": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
    "query_class": "startup_fallback",
    "result_count": 0,
    "selected_memory_ids_sha256": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "duration_ms": 18,
    "status": "success",
    "recorded_at": "2026-08-13T12:00:00Z"
  }
}

The structured response exposes the serialized ActivationReceiptSummary, which deliberately omits the persisted receipt's session identity and receipt state. The top-level state reports the activation result. The plugin derives the Agent Zero identity server-side; it must not accept a model-supplied identity. Its binding carries the same protocol version, store_id, project-root fingerprint, relative memory_root, and JSON stdin/stdout command contract. A failed preflight emits no successful context or receipt and the command exits with an error.

Receipt verification

An adapter may classify a harness as active only after it verifies the manifest, bridge, and receipt as one tuple. It must reject the receipt and report the exact non-active state if any of these equality requirements fail:

  1. receipt.schema_version == 1 and receipt.protocol_version == manifest.protocol_version == 1.
  2. receipt.harness_id is exactly the adapter's canonical harness ID.
  3. receipt.adapter_version == manifest.harnesses[harness_id].adapter_version.
  4. receipt.store_id == manifest.store_id.
  5. receipt.project_root_fingerprint == manifest.project_root_fingerprint.
  6. receipt.bridge_fingerprint == manifest.harnesses[harness_id].bridge_fingerprint, after recomputing that fingerprint from the installed owned material.
  7. receipt.status == "success" and receipt.state is active or active-isolated.
  8. recorded_at is not in the future and is newer than the 30-day retention cutoff. Freshness is calculated from recorded_at; the receipt has no serialized expiry field.

Any adapter, harness, root, or bridge change invalidates prior receipts. A matching receipt for another accessible store is active-isolated, never active for the canonical shared project.

Runtime and shared-store boundaries

Pi project trust is a user decision: report needs-trust and leave global trust unchanged. Agent Zero uses only the separate tree_ring_memory plugin. init leaves a passive project binding at needs-plugin; the plugin's fixed absolute, non-project descriptor is the only way its runtime status/preflight can use that binding. Tree Ring does not modify Agent Zero core, and a generic .a0 marker is not an adapter. A missing, invalid, disabled, or release- incompatible descriptor remains needs-plugin; an inaccessible project root is needs-project-mount; a different accessible store is active-isolated.

Shared status requires every receipt to match the canonical project store_id. It is supported only for concurrent processes on the same host and a local filesystem, not across hosts, NFS/network filesystems, or containers on different hosts. Use per-host roots and explicit evidence-preserving fan-in.

Maintained adapters detect capability, create only absent owned material, preflight a new session, and verify its receipt. Deactivation is also creation-only at the writer boundary: existing final entries are not removed or replaced automatically, including recorded owned material. Conflicting, changed, or otherwise contested bridge and manifest entries remain unchanged and report needs-user-review.