Skip to content

Latest commit

 

History

History
59 lines (34 loc) · 13.1 KB

handoff-runtime.md

File metadata and controls

59 lines (34 loc) · 13.1 KB

Experimental handoff runtime

Dry-run validates the original prepared target, not a newly resolved replacement. Claude keeps the original PID/generation check; Codex rechecks and compares the original home/writer context. Changed evidence refuses without native submission and never rewrites the prepared binding.

This guide describes the limited experimental handoff implementation shipped in Python v1.1.0 on 2026-10-09 for #181. The frozen Handoff v1 design and its synthetic fixture are unchanged. Publication does not complete every design acceptance gate or certify every platform or live-agent route.

Supported experimental route

Same-machine POSIX Claude inbox submission can use an explicitly initialized, user-owned ledger and a separate private receipt collector. The connected inbox PID, original process birth and registry endpoint must match the selected generation. Same-machine POSIX Codex supports correlation-only queue submission with no receipt capability and null target generation. Required delivered/acknowledged waits refuse before its queue invocation; best-effort ACK/observation requests report unsupported while retaining actual queued facts. No model/session is started. Single-destination POSIX source-streamed SSH supports correlation-only Claude/Codex submission as described below. Codex body-free injection observation, Windows bounded pipes, paired devices and SSH receipt bootstrap remain unsupported. Ordinary opt-out local/SSH behavior and the Codex wake version gate are unchanged.

session-peer handoff init --json
session-peer handoff prepare --to worker --message-file ./private-message.txt --json
session-peer send --to worker --message-file ./private-message.txt --correlation-id PREPARED_UUID --request-ack --wait-for acknowledged --wait-timeout 30 --json
session-peer handoff status --correlation-id PREPARED_UUID --json
session-peer handoff wait --correlation-id PREPARED_UUID --wait-for acknowledged --wait-timeout 30 --json
session-peer ack --receipt - --json
session-peer handoff confirm --receipt - --json

The message file must be a private, owner-only regular UTF-8 file. Use a prepared ID returned by the second command, not the literal placeholder. ACK/confirmation JSON enters only through private stdin. A cooperating recipient chooses whether to submit the delegated receipt under its normal permissions. Receipt authority is not approval to run other tools, send messages, wake a model or read a transcript. No automatic response is inferred.

Experimental handoff sending accepts named/positional message text, a private message file or bounded POSIX raw stdin. Stdin has one original effect cutoff, strict UTF-8 and limits of four million raw bytes/one million codepoints; a stalled pipe or SIGINT refuses before creating an intent, without an invented epoch or submission result. Receipt/confirmation stdin is a separate bounded API. Opt-in multi-host sending remains an implementation gate. Ordinary opt-out stdin and multi-host sending are unchanged. Native effect input uses the shared untrusted-peer body frame, with every external body line quoted; final character limits include that frame and any receipt authority.

Codex preparation records the canonical thread/home plus bounded private original home/writer resolution. Reusing a prepared ID with changed resolution refuses rather than rebinding it. This is not an atomic native incarnation or consumption proof. The ordinary live-writer and explicit inactive-home rules remain mandatory, and the original resolution is rechecked immediately before the single queue invocation. Its version probe and queue share the reviewed storage guard/environment normalizer from #268; effective native configuration layers/daemon guarantees remain that issue's open investigation boundary. The owned POSIX child has bounded raw metadata stdout/stderr and uses the original deadline/cleanup reserve. A complete matching native queue receipt may retain queueId/submitted facts after timeout or abnormal exit; absent, wrong-target, partial, invalid UTF-8 or overflow output remains unknown even at exit 0 and never retries. Diagnostic stderr never supplies native evidence. No app-server/body/transcript API is called.

Before minting authority, the runtime checks its exact protected script/interpreter and probes that script's ACK command. Private effect input names that exact isolated Python receipt-only handler and ledger, not an unverified session-peer from PATH; the raw capability remains on stdin. Receipt/confirmation input has one five-second EOF deadline and a 4096-byte limit. If storage fails after native submission, the output retains independently validated target/character/dry-run facts, omits unprovable handoff/ACK and forbids retry. With no explicit wait, independently known native submission keeps success/exit 0 plus a fixed history diagnostic; an explicit wait that cannot be verified fails with exit 1/130 without inventing a wait result.

For Codex correlation-only mode there is no pending explicit wait: SIGINT after a complete valid queue receipt retains success/exit 0 and wait not_requested. Partial evidence under SIGINT remains unknown/exit 130. Interrupted version preflight refuses/exit 130 before queue. Diagnostic stderr overflow is separate from stdout overflow: bounded stderr never erases a captured complete valid stdout receipt, while protocol stdout overflow is always unknown. Failed native journal snapshots use a closed fixed-reason profile; corrupt positive/secret fields fail as ledger_corrupt without being copied into query results. Malformed public correlation IDs are usage errors (exit 2) before message input; private receipt validation remains an operation failure.

Source-streamed SSH correlation-only route

Verified structured SSH-self addresses keep the local message limit after normalization, including prepared-ID reuse and dry-run. Actual SSH destinations still have the 90,000-character command-argument limit. The protected metadata child additionally bounds each encoded JSON input/output frame to 1 MiB, including UTF-8 encoding and JSON escapes; this can reject otherwise valid local-sized input before submission.

The sender uses one original per-destination budget for metadata, harmless target probing, durable intent, one native effect and cleanup. Its protected metadata child preserves From/Reply and structured SSH-self normalization without renewing detection timeouts. A consumed intent refuses before any new probe. Required remote delivered/acknowledged waits refuse before SSH submission; best-effort requests report unsupported and mint no capability, remote ledger or collector. The effect rechecks the original Claude generation or canonical Codex home/writer context. An explicit Codex generation request is unsupported, not silently removed. Read-only status/wait never invokes SSH submission.

The probe records an old remote boot-bound monotonic anchor before replying. Only after strict response validation does the sender compute both remaining durations from one original clock sample. The remote effect uses that old anchor plus the remaining durations, including exactly five seconds within the total for cleanup; changed boot, future/expired anchor or malformed context refuses before native effect. No UTC or cross-host clock origins are compared. SSH shutdown does not prove cancellation of remote submission or consumption. Home mapping is the trusted remote handler's actual expanduser/resolve result, including owned symlink aliases; it is not a sender-verified inode, atomic incarnation or universal native storage guarantee.

The actual command must enter the closed private handler before config/source/SSH children start. Hidden options are not authentication: the receiver quotes every external body line once. Strict native profiles and request/target/home/context proofs preserve complete submission evidence after disconnect, timeout, diagnostic overflow or interruption, but never salvage an overflowing protocol stdout prefix. Connection-verified SSH identity and safe reply metadata remain separate from native facts. Private proofs, body, clock anchors and native context are not reflected into public outputs. Owned fake-SSH/source-stream/SQLite/queue fixtures verify this shipped slice, not actual remote hosts, native model consumption or installed reverse-SSH receipt bootstrap.

Evidence, privacy and timing

A complete inbox write is submission, not consumption or turn completion. Only an authenticated correlated receipt, or a separate explicit operator attestation with provable ordering, produces acknowledged. An early receipt during native write/drain is saved as bounded unclassified evidence. It is classified only after independently known submission and original-clock ordering; classification cannot invent a receipt or backdate an ACK. A committed ACK survives collector restart; unused authority expires conservatively on restart. Duplicates still require original token-hash proof, including after expiry. Manual confirmation never rewrites an old wait.

Ordinary Python Claude fields remain absent rather than synthesized as false/null. Known native facts survive wait failure. Pending wait interruption records stopped and returns complete structured output with exit 130; querying the stopped intent succeeds with exit 0. No status, wait, ACK or confirmation operation submits a native message. Missing/corrupt/unknown history has a separate handoffQuery error without a fabricated epoch. No automatic resend or fallback is allowed.

The per-destination budget starts before setup: 30 seconds by default, ASCII integers 1..60 only. Exactly five seconds are reserved within the total for cleanup. Observation/effect ends at total minus five seconds; timeout diagnostics describe that cutoff. Budgets at or below five seconds refuse before effect. OS filesystem/identity operations and fsync cannot be forcibly cancelled by this Python implementation; this is not a universal hard-real-time latency guarantee.

Raw receipt capabilities occur only in authorized native message input and private IPC/stdin, never sender/collector ordinary outputs, argv, URI, environment or own journal. The journal stores hashes. Native recipient queue/history may retain the delegated token, and same-user readers may acquire it: token_possession is not independent model identity. No recipient transcript is read.

Storage and remaining gates

Clock investigation (2026-10-09): CPython 3.9.25's Darwin clock subtracts a process-local starting tick. The runtime therefore uses typed mach_absolute_time/mach_timebase_info nanoseconds on Darwin across sender, journal and collector, and native monotonic nanoseconds elsewhere. The inbox adapter receives only a translated remaining duration in its own clock domain, not persisted cross-process timestamps. Missing/invalid native clock proof refuses; earlier candidate records have a distinct clock-provider continuity namespace and are not silently reinterpreted. Tests compare real parent/child samples; macOS Python 3.9 acceptance is also checked by CI, not inferred from this Mac's Python 3.14 run.

The owner-only POSIX directory is ~/.local/share/session-peer/handoff. Atomic fsynced snapshots and a stable exclusive lock fence the first native attempt before effect; a crash after that fence remains unknown even if no write actually began. Reservation includes each intent's bounded future receipt/wait/native detail; the 10,000-intent and 32 MiB quotas include tombstones and reservations. Capacity never evicts effect fences. There are at most 64 distinct waits per intent.

Details compact to a target-bound tombstone after 30 days only under proven native boot/monotonic continuity; unavailable continuity conservatively retains detail and may exhaust quota sooner. Cross-boot retention needs further acceptance review. A known restored ledger must be quarantined with the explicit operator HandoffLedger.quarantine_restored() API before reuse; CLI restore/reconciliation tooling remains a gate. Archive/new initialization is an explicit operator action, never automatic repair. Unmarked rollback or cloned history cannot be detected reliably. A stale collector socket is not automatically removed or rebound. Collector lifetime is at most 24 hours; receipt TTL is 24 hours from the original durable intent commit and collector restart revokes unused authority. Lifecycle/expiry boundaries still require review against the complete design before expanding support.

Verification uses owned local Unix inboxes, actual peer-PID/process-birth checks, private collector/producer subprocesses, owned SQLite/fake Codex queue children, bounded raw stdin, strict JSON, concurrent startup, crash fencing, late/duplicate/expired receipt and SIGINT fixtures. Fake queue/version metadata is not a real Codex session or universal native-version certification. Tests do not prove real Claude model consumption, a native Codex observer, remote bootstrap or Windows cleanup. #181 stays open until those remaining acceptance gates are resolved; the frozen design fixture is not runtime certification.