Skip to content

Latest commit

 

History

History
575 lines (492 loc) · 27.1 KB

harness-activation.md

File metadata and controls

575 lines (492 loc) · 27.1 KB

Harness Activation Protocol

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.

The default .tree-ring, explicit ./.tree-ring, and absolute paths to the same project-local store are equivalent for activation and deactivation. Different stores and symlinked bridge targets remain rejected.

Keep an existing root AGENTS.md and .tree-ring/AGENTS.md separate. The project's instructions remain authoritative; Tree Ring's managed root block only references its local guidance. Create-only initialization preserves existing project instructions, and a harness without a managed activation record remains needs-user-review on subsequent status checks. Review and reconcile that bounded reference without replacing either instruction file.

When initialization creates a missing root AGENTS.md, it records ownership of only its marked Tree Ring block. Project instructions may later be added or edited around that unchanged block without invalidating activation retries. Changing the Tree Ring block itself still requires review. Existing manifests that own the complete file keep their recorded ownership; this change does not automatically migrate or replace a previously published manifest.

States and proof

State Meaning
active A maintained bridge has a fresh matching receipt for a new session's scoped recall and rendered safe context.
configured-awaiting-proof A safe bridge is installed, but no qualifying fresh receipt exists.
active-isolated Preflight succeeded against a store that does not match this project's canonical store.
needs-trust The runtime needs its own user approval before project resources load.
needs-project-mount The runtime cannot reach the canonical project root.
needs-plugin The 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-review An existing or changed bridge/manifest cannot be safely replaced or removed, or publication durability is indeterminate.
unsupported No maintained adapter can prove the integration.
failed Detection, 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.

After successful integrations activate or link, the result reports any already-valid matching receipt using the same verification as status. Without that proof, it directs the user to open a new harness session and check integrations status --verbose; it does not ask them to apply the completed plan again. Activation does not create a receipt, and existing proof cannot override a publication review, trust requirement, or other blocked outcome.

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.

integrations status --verbose includes last_recall_result_count and last_recall_query_class from the newest validated receipt. These are omitted when there is no valid receipt. Zero results are reported as zero, rather than being confused with an invocation that never happened.

Recall visibility and startup briefs

As of CLI 0.15.6, automatic preflight applies each memory's declared scope:

Memory scope Required match within the selected project store
Project, global, manual, DOX, Revolve, tool, eval Project name; origin agent/workflow/session fields are provenance
Agent Project and agent profile; previous workflows and sessions remain recallable
Workflow Project and workflow ID
Session Project and session ID

Project-less records and other projects are excluded. For project names that require a safe lifecycle alias (including names with spaces), recall accepts both the original basename and the exact normalized alias used by capture. Worker profiles remain private: a new worker does not inherit a previous worker's agent-scoped notes. Shared publication remains an explicit decision. Explicit tree-ring recall flags continue to use exact conjunctive filtering.

With no safe task hint, preflight builds a startup brief without an FTS keyword requirement. It ranks bounded candidates with salience, confidence, recency, source authority, and scar/heartwood preference. With a safe task hint it uses scoped full-text recall. Sensitivity, supersession, expiry, redaction, project, and scope filters run before the candidate limit. Only safe summaries and source references enter the brief. The complete context fits within 6,000 UTF-8 bytes, preserving relevance order and omitting entries that do not fit. The receipt counts and hashes only the memories actually included.

A startup brief cannot replace task-specific recall during a long session. Its context reminds the agent to query again before substantive work or when the task changes; no prompt or tool hook reads or persists user input.

Codex and Claude adapter version 4 prefer .tree-ring/bin/tree-ring for both hooks and capture instructions, then fall back to PATH. Existing version 3 definitions still need reconciliation: create-only activation preserves those files and reports needs-user-review. Do not treat a binary update as a hook upgrade or a new activation receipt. Review the old managed files and manifest before replacing their definitions; never remove the memory database to reconfigure hooks.

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": "4",
  "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.

Lifecycle launchers use the current checkout's project-local .tree-ring, including in a linked Git worktree. If that directory is genuinely absent, the hook exits successfully without output or creating memory state. An inherited hook does not initialize a worktree or borrow the main checkout's memory store. An existing directory with missing or invalid activation, a file in place of the directory, or a symlink remains an error requiring repair. Explicit capture and preflight commands retain their strict activation requirements.

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 the bounded startup brief. 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.