Compaction runtime and cross-language matrix
The four SDKs expose an optional host-side text compaction adapter:
- Python:
agent_hooks_protocol.compaction.run_compactionandcompaction_capabilities. - TypeScript:
runCompaction,compactionCapabilities,CompactionHookfrom the SDK entrypoint. - Go:
interop.RunCompaction,interop.CompactionCapabilities,interop.CompactionHook. - Rust:
compaction::run_compaction,compaction::run_compaction_observed,compaction::capabilities,compaction::CompactionHook,compaction::CompactionObserver.
These APIs run host-owned callbacks serially. Callbacks receive detached snapshots; Rust callbacks borrow an immutable snapshot. A callback returns an array of draft effects (or an error). Generated effect codecs validate structure; the runtime validates the current boundary, target, operation, value, and enforcement ability. The host controls subscription supplier identities and failure policies. Never populate those from an unauthenticated hook response.
Contract
This adapter supports text replacement only, not object merge or injection. It
advertises instructions.replace before compaction and summary.replace after
compaction. Before supports return and reason-bearing deny; both intercept
boundaries support messages. Unsupported effects fail explicitly.
Each response stages all changes, including messages, candidate invalidation, and new summary bytes. A rejected compound response commits none of these. Fail-open keeps the earlier accepted state; fail-closed prevents generation or downstream application. A failed after response never undoes generation that already ran.
Accepted instructions are visible to later callbacks and the generator. Effective input changes invalidate an earlier supplied summary. Within a compound response, input changes precede binding its returned summary, independent of effect order. A return skips only generation: after callbacks still run, including redaction and failure controls. Later after callbacks see the modified summary. Provenance continues to identify the original generator or supplying subscription.
Only a result with applied: true is eligible for downstream application. The
runtime leaves model invocation and summary consumption to the host.
The default generator returns summary: plus the effective instructions. Hosts
can supply another generator callback; it must generate, not apply the result.
observeOnly / observe_only configures the after boundary as a notification,
not an interception. Settlement sets applied before scheduling observers, and
never waits for their callbacks or returned promises. Each observer gets its own
snapshot of the settled state with no effects or modification capabilities.
Callback return values, errors, and panics have no effect authority and never
mutate the settled result or its failure diagnostics. The before boundary remains
separately enforceable. seen records prepared snapshots, not delivery receipts.
Python uses daemon threads, Go uses detached goroutines with panic recovery, and
Rust uses owned callbacks on detached threads with unwind containment. These tasks
do not keep the process alive during shutdown. Rust’s borrowed interception
callbacks cannot safely escape their stack lifetime: use
run_compaction_observed with Vec<CompactionObserver> containing owned
Arc<dyn Fn + Send + Sync + 'static> callbacks. Passing borrowed after hooks to
run_compaction(..., observe_only=true) is an explicit configuration error,
never a synchronous fallback or unsafe lifetime extension.
TypeScript accepts async CompactionObserver callbacks through options.observers
(and retains observe-only after-hook support). It schedules invocation in an
unref’d task, contains promise rejections, and never awaits observation delivery.
As with other JavaScript callbacks, observers must yield for I/O; blocking native
work belongs in a host worker. The scheduler does not serialize an arbitrary
closure into a worker or promise isolation from a blocked JavaScript event loop.
Barrier-based tests hold observer work incomplete while the runtime returns
applied: true and a downstream sink receives the summary. They also prove that
one blocked observer does not serialize other observers, exceptions do not change
settlement, and snapshot mutation/returned effects cannot change the result.
Watchdog timeouts detect hangs; correctness does not depend on sleep timing.
The local immutable body store uses injective UTF-8-derived reference identifiers.
Changing summary bytes changes its reference while retaining the supplied logical
item ID. Earlier referenced bytes remain available unchanged. The returned
{id, ref} is an internal handle, not a canonical AHP content item. A production
host must upload bodies through the existing authenticated content-upload layer
and create complete item descriptors before exposing AHP wire events.
Host-orchestration transport harness
compaction_matrix.py runs every TypeScript/Python/Go/Rust sender against every
receiver over both loopback HTTP and NDJSON stdin/stdout: 32 pairs, with 29
scenarios per pair. All requests pass through the sender’s native HTTP/subprocess
stack and the receiver’s independent public SDK runtime. No shared evaluator or
expected outcome is sent to receivers. HTTP uses a fresh bearer token.
The fixture’s compaction/run JSON-RPC method provides test-only host
orchestration. It registers fixed callback responses and invokes compaction. Receiver-side SDK code validates and
settles those effects. This harness tests runtime interoperability and transport execution.
The canonical matrix below covers the event-envelope and
preupload path end to end.
Cases cover instructions flowing into generation, serial result edits, return followed by after controls, candidate invalidation, compound rollback at either boundary, earlier accepted messages, fail-open/fail-closed, crossed targets, unsupported merge, wrong value types, missing denial reasons, malformed response arrays, callback errors, observation-only capability restrictions, and Unicode / empty summary bytes. The driver independently checks provenance, serial snapshots, application gates, identity, immutable bytes, and absence of rejected staged bytes.
Reproduce
From the workspace root (four SDK repositories and protocol repository siblings):
(cd typescript-sdk && pnpm --filter @agenthooksprotocol/sdk build)
(cd rust-sdk && cargo build --bin compaction)
python-sdk/.venv/bin/python agent-hooks-protocol/interop/compaction_matrix.py
python-sdk/.venv/bin/python -m unittest discover -s agent-hooks-protocol/interop -p test_compaction_matrix.py
(cd python-sdk && PYTHONPATH=src .venv/bin/python -m unittest discover -s tests -p test_compaction.py)
(cd typescript-sdk && node --test interop/compaction.test.mjs)
(cd go-sdk && go test ./interop -run TestCompaction -count=1)
(cd rust-sdk && cargo test --test compaction)
Matrix flags --sender, --receiver, and --transport select a smaller slice.
--output selects a report path. No LLM, external network, credentials, or real
context mutation is required. The fixture processes are local test utilities,
not hardened production HTTP services.
Canonical AHP wire matrix
compaction_wire_matrix.py exercises all 16 sender/receiver combinations over
both HTTP and stdio. The only event-wire method is hooks/intercept, with
canonical context.compact.before and context.compact.after events. Test
scheduling and subscription callbacks are configured locally before starting
the native SDK processes.
Each sender runs its public SDK compaction pipeline. Each hook callback:
- Constructs complete canonical content items from the current runtime state.
- Uploads UTF-8 instructions, source-context items, or summary bytes over the specified HTTP raw-octet upload binding, scoped to the receiving credential scope. Upload and event credentials are independent. A confirmed 201 with a receiver-assigned JSON content-reference precedes the event, including when the event transport is stdio.
- Sends a canonical
hooks/interceptrequest through the sender’s native HTTP/subprocess stack to the selected receiver SDK. - Validates the correlated canonical response and returns its effects to the public runtime. Accepted effects therefore determine the next hook’s actual content descriptors and referenced bytes, not merely expected test values.
Receivers apply their SDK’s full canonical request/response validators, resolve only preuploaded credential-scoped bodies, and verify their size and SHA-256. Append callbacks derive replies from the received body bytes. Fixed compound callbacks exercise atomic failures. Receivers do not know expected outcomes. They record exact canonical requests/responses and resolved body bytes; the oracle compares those receipts with the sender trace and settled downstream content.
The stdio fixture starts a native receiver subprocess for each subscription call. Its stdin/stdout contain only canonical JSON-RPC. A native receiver HTTP process accepts the independently authenticated uploads; the stdio process reads that credential-scoped immutable store. Filesystem sharing is local receiver state, not a shared semantic evaluator or an upload bypass.
Each pair executes 8 lifecycle cases, 27 accepted canonical hook exchanges, 41 confirmed credential-scoped content uploads, and 4 rejected requests. Across 32 pairs this is 256 lifecycle executions, 864 accepted hook exchanges, 1,312 uploaded bodies, and 128 receiver rejections.
Eight lifecycle cases cover serial input/output edits, supplied-summary after controls, atomic rollback at both boundaries, and fail-open/fail-closed crossed targets. Four additional requests per pair test actual receiver rejection of a missing required event field, an invalid boundary capability, a missing upload, and a mismatched content digest. These negative requests bypass sender validation and traverse the native sender transport, so rejection is proven at the receiver. After controls settle, a synthetic downstream sink receives only applied summaries. Denied/fail-closed cases produce no downstream content.
Build and run from the workspace root:
(cd typescript-sdk && pnpm --filter @agenthooksprotocol/sdk build)
(cd rust-sdk && cargo build --bin compaction_wire --bin compaction)
python-sdk/.venv/bin/python agent-hooks-protocol/interop/compaction_wire_matrix.py
python-sdk/.venv/bin/python -m unittest discover -s agent-hooks-protocol/interop -p test_compaction_wire_matrix.py
The report is compaction-wire-matrix-results.json. The same sender, receiver,
transport, and output filters as the original harness are supported.