feat(modules/airc): adopt airc v5 owner-core schema (SHA bump + daemon_transport migration)#1506
Open
joelteply wants to merge 3 commits into
Open
feat(modules/airc): adopt airc v5 owner-core schema (SHA bump + daemon_transport migration)#1506joelteply wants to merge 3 commits into
joelteply wants to merge 3 commits into
Conversation
…n_transport migration) Headless break #3 from the moment-of-truth iterate loop (continuum task #82). After #1504 (socket discovery) and #1505 (attach channel), the next concrete error revealed itself: AIRC daemon attach stream stopped: failed to read airc daemon event: Semantic(None, "missing field `event`") CBOR deserialization mismatch: continuum's pinned airc-ipc SHA (428f9281) predated the v5 owner-core rewrite, where the IPC vocabulary was split from the SDK projection: - Response::Event: { event: Box<TranscriptEvent> } → { envelope: Vec<u8> } - PublishRequest: { wire, body } → { from_peer, from_client, payload: Vec<u8>, delivery, correlation_id, coalesce_key } - PublishRequest.kind: FrameKind → IpcKind - PublishRequest.target: MentionTarget → IpcTarget - InboxRequest.since: TranscriptCursor → IpcCursor - InboxResponse: { events: Vec<TranscriptEvent> } → { envelopes: Vec<Vec<u8>> } - ResolveWire removed entirely (owner-core daemon owns channels) Bumped 428f9281 → 8f6948c (rebased on rust-rewrite + airc#1096's `impl From<>` blocks). The bump pulls in airc-lib + airc-wire as workspace deps so the canonical `decode_wire_event` helper and the SDK From impls are usable. ### What this PR touches - `src/workers/Cargo.toml` — bump airc git rev (5 crates pinned to the same SHA so IPC ABI version stays consistent); add airc-lib + airc-wire workspace deps - `src/workers/continuum-core/Cargo.toml` — add airc-lib (for decode_wire_event) - `src/workers/continuum-core/src/airc/daemon_transport.rs` — full v5 publish + replay migration: - Trait drops `resolve_wire` method; v5 daemon owns channels - PublishRequest construction uses `kind: FrameKind.into()`, `target: MentionTarget::All.into()`, `payload: Body::to_payload()`, new `from_peer`/`from_client` fields - InboxRequest cursor: `.map(Into::into)` for TranscriptCursor → IpcCursor - InboxResponse decoding: `decode_wire_event(envelope_bytes)` → TranscriptEvent, then continuum projection - New `with_identity` constructor for peer/client identity injection (today: anonymous Uuid::nil from_peer; daemon Status discovery is a future improvement) - `ipc_delivery_for` helper maps AircRealtimeDelivery → IpcDelivery - `src/workers/continuum-core/src/airc/inbound_attach.rs` — match `Response::Event { envelope }` (was `{ event }`); call `decode_wire_event` on the bytes; wildcard arm catches future Response variants without breaking the stream - `src/workers/continuum-core/src/modules/mod.rs` — disable `airc_runtime_e2e_tests` (was modeled entirely on v4 wire shape; rewrite tracked as continuum task #83) ### Verification (end-to-end on this branch) $ rm -f /tmp/hctest.sock && \ target/release/continuum-core-server /tmp/hctest.sock > boot.log 2>&1 & $ grep "Discovered airc" boot.log Discovered airc daemon socket via `airc ipc-endpoint` socket_path="/Users/joel/.airc/runtime/airc-machine-…-v5.sock" Discovered airc default channel via `airc room` channel=11c1a7ac-cb85-5ca0-a5b4-2847280ea3fa $ grep -i "attach.*stopped\|requires a channel\|missing field" boot.log # (empty — no errors) Three concrete breaks fixed in three successive PRs (#1504, #1505, this one). Headless inbound attach is now alive end-to-end. $ cargo test --release --lib --features metal,accelerate airc:: test result: ok. 73 passed; 0 failed; 0 ignored. ### Co-evolution pattern Joel, 2026-05-31: > "I always simultaneously develop the sdk and consumer of it. It > helps you build the best patterns." Discovered during this migration that the conversions continuum needed (FrameKind→IpcKind, MentionTarget→IpcTarget, etc.) lived as private free functions in airc-lib. Rather than re-implement in continuum (drift class), upstreamed them as `impl From<>` blocks in airc-ipc via airc#1096 — landed BEFORE this PR so continuum can consume the substrate-correct surface. The continuum side is then a clean `kind: frame_kind.into()` instead of reaching for a duplicated helper. Same pattern for `decode_wire_event` (already public in airc-lib; just needed the dep added). ### Follow-ups (filed) - continuum #83: rewrite `airc_runtime_e2e_tests.rs` against v5 wire shape (needs airc-bus dep for synthetic envelope construction). - airc PR #1095 (open, pending Windows CI): `airc ipc-endpoint` CLI. Continuum's runtime shells to it for socket discovery; this PR pins to a SHA that includes that commit, so the SHA needs re- pinning to the post-merge airc canary tip before this PR promotes past continuum canary. - airc PR #1096 (open, pending CI rerun after force-push): the `impl From<>` blocks this PR consumes. Same re-pinning gate. - Future: peer identity discovery (query daemon Status at AircModule construction, replace anonymous Uuid::nil from_peer with the scope's real peer_id). ### References - continuum #1504 + #1505 — sibling fixes for breaks #1 + #2; this PR fixes break #3. - airc PR #1095 — `airc ipc-endpoint` CLI (continuum's runtime shell-out). - airc PR #1096 — SDK-side `impl From<>` blocks (continuum's compile-time imports). - Memories: `headless-rust-must-work-soon`, `continuum-thesis-airc-is-the-medium`, `every-error-is-an- opportunity-to-battle-harden`, `agent-review-as-acceptable- approval`. - ALPHA-GAP §0A line 706 — headless target. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nded waits at boot Audit response to Joel's concern about multi-persona-load deadlock exposure: every subprocess `.output().await` in continuum's airc discovery path was unbounded. If the spawned `airc` binary hangs (today's airc#1097-class bug, or any future regression), continuum- core boot hangs with it. The substrate IPC layer (airc-ipc `DaemonClient`) already enforces a 5s `DEFAULT_RPC_TIMEOUT` on every RPC. Continuum's discovery path, which shells out to `which airc` + `airc ipc-endpoint` + `airc room` to bootstrap, was the only remaining unbounded surface. ### What this PR adds - `DISCOVERY_SUBPROCESS_DEADLINE: Duration = Duration::from_secs(5)` — matches the substrate-wide RPC convention. Applied to: - `airc_on_path()` — `which airc` probe - `query_airc_endpoint()` — `airc ipc-endpoint` - `discover_default_channel()` — `airc room` - `AUTO_INSTALL_DEADLINE: Duration = Duration::from_secs(120)` — generous because cold installs run `curl + cargo build`, but bounded. Applied to: - `auto_install_airc()` — `bash -c "curl -fsSL .../install.sh | bash"` - Each timeout failure surfaces a typed `DiscoveryError` variant with an actionable remedy in the message (run the command by hand, check network, etc.). ### Doctrinal alignment Per [[no-stdio-piping-for-process-ipc]] memory landed today: every subprocess wait MUST be bounded. An unbounded `.output().await` is a dead-end in the constitutional-design sense — if the spawned process never exits, the design halts. Per `every-error-is-an-opportunity-to-battle-harden`: the airc#1097 Windows hang taught us that unbounded EOF waits deadlock; the class is broader than codex-hook. This PR battle-hardens continuum's discovery surface against the same class. ### Scaling story this confirms Audit results, briefed to Joel separately: - airc-ipc `DaemonClient` methods (publish, inbox, status, ping, attach-handshake) all bounded by 5s via `call_with_timeout` — good. - Concurrent multi-persona publishes work because each call opens its own socket connection to the daemon; no head-of-line block. - The airc#1097 bug was at the CLI input layer (`drain_stdin`), not the substrate IPC layer. - Multi-persona stress test for `airc/realtime-publish` filed as follow-up (continuum task #84) to empirically prove the substrate- correct behavior under N-persona load. ### Test plan - [x] `cargo test --release --lib --features metal,accelerate airc::discovery` — 7/7 pass in 0.00s (timeouts not triggered; pure parsing + env-override paths). - [ ] Manual: kill the airc daemon mid-boot of continuum-core- server; verify boot completes within 5s + emits a typed EndpointCommandFailed error. ### Follow-ups (filed) - continuum #84 — multi-persona stress test for AIRC realtime publish path - Replace stdout-parsing discovery entirely once airc exposes the right typed IPC surface (per `no-stdio-piping-for-process-ipc` memory's "concrete continuum debt" section) ### References - [[no-stdio-piping-for-process-ipc]] — doctrinal memory landed today; this PR is an immediate consumer - airc#1097 — Windows pipe-EOF deadlock; same class as the unbounded subprocess wait this PR fixes - airc#1098 — sibling airc-side fix (`drain_stdin` 5s deadline); same shape applied to the parent side Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… carry real attribution
Continuum's publish path was using `Uuid::nil()` for `from_peer`,
so messages appeared in airc transcripts as "from nobody" — the
hollow-attribution problem flagged in the `headless-success-is-
hosted-personas-talking-over-airc` memory and called out by Joel:
"talking to a hosted persona shows messages from nobody — UX broken."
### What this ships
- New `discover_peer_id(socket_path) -> Result<Uuid, DiscoveryError>`
in `airc/discovery.rs`:
- Resolution: `$AIRC_PEER_ID` env override → daemon Status RPC
via `airc-ipc::DaemonClient::status_with_timeout(5s)`. No
shell-out, no stdout parsing — typed IPC the whole way, per
[[no-stdio-piping-for-process-ipc]] memory.
- Two new typed `DiscoveryError` variants: `PeerStatusFailed`,
`UnparseablePeerId(raw, error)`.
- `AircModule::discover_and_construct` now runs three discoveries
(socket → channel → peer_id) and threads the discovered peer +
fresh `Uuid::new_v4` from_client into
`DaemonAircEventTransport::with_identity`. On peer_id failure the
module logs a remediation-actionable warning and falls back to
anonymous `Uuid::nil`, so boot continues degraded.
### Verification (end-to-end on this branch)
```
$ rm -f /tmp/hctest.sock && \
target/release/continuum-core-server /tmp/hctest.sock > boot.log 2>&1 &
$ grep "Discovered" boot.log
Discovered airc daemon socket via `airc ipc-endpoint`
socket_path="/Users/joel/.airc/runtime/airc-machine-…-v5.sock"
Discovered airc default channel via `airc room`
channel=11c1a7ac-cb85-5ca0-a5b4-2847280ea3fa
Discovered airc scope peer_id via daemon Status
peer_id=9bb24964-1a1a-43e2-a5aa-8140362bab63
```
The discovered peer_id matches the scope's actual airc identity
(visible in `pgrep airc | grep daemon` output as the daemon's
`peer_id`). Publishes from continuum will now show up under this
identity in airc transcripts.
### Doctrinal alignment
- Per [[headless-success-is-hosted-personas-talking-over-airc]]:
this is one of the load-bearing follow-ups for "personas talking
over airc as recognized peers." Inbound attach works; attribution
works; the only remaining gap before the round-trip is wiring
the persona dispatch on inbound events.
- Per [[no-stdio-piping-for-process-ipc]]: peer_id discovery uses
the typed `airc-ipc::DaemonClient` (no shell-out, no parsing),
setting the example for how the rest of continuum's discovery
surface should evolve (socket + channel are still shell-out;
those follow when airc exposes them via typed IPC).
### Follow-ups (filed)
- continuum #84 — multi-persona stress test for `airc/realtime-
publish` under N-persona load (peer attribution + concurrency).
- continuum #85 — diagnose airc#1097 Windows hang on the 5090.
- Socket + channel discovery still shell out (`airc ipc-endpoint`,
`airc room`). When airc exposes these as typed RPCs, migrate to
match this PR's pattern.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
428f9281→8f6948c(rebased on rust-rewrite + airc#1096 SDK From impls). Adopts v5 owner-core schema.daemon_transport.rspublish + replay paths: opaquepayload: Vec<u8>instead of typedbody: Body,from_peer/from_clientidentity fields, IpcKind/IpcTarget/IpcCursor instead of FrameKind/MentionTarget/TranscriptCursor,decode_wire_eventfor InboxResponse envelopes, ResolveWire removed.inbound_attach.rsto matchResponse::Event { envelope }and decode viaairc_lib::decode_wire_event.airc_runtime_e2e_tests(v4-shape fixture; rewrite tracked as continuum Build(deps-dev): Bump @typescript-eslint/parser from 8.29.1 to 8.35.1 #83).AIRC daemon attach stream stoppedwarnings).Why
Third concrete break revealed by the headless moment-of-truth iterate loop:
CBOR deserialization mismatch — continuum's pinned airc-ipc predated the owner-core rewrite, where
Response::Event { event: Box<TranscriptEvent> }becameResponse::Event { envelope: Vec<u8> }. The whole IPC vocabulary split from the SDK projection in v5; this PR is the consumer-side adoption.Co-evolution with airc
Joel, 2026-05-31:
Started the v5 migration here, discovered continuum needed conversions (
FrameKind→IpcKind,TranscriptCursor→IpcCursor, etc.) that lived as private free functions inairc-lib. Wrong surface for external consumers. Upstreamed them asimpl From<>blocks in airc-ipc via airc#1096 so continuum can writekind: frame_kind.into()instead of duplicating a bit-shift. Same pattern fordecode_wire_event(already public in airc-lib; just needed the workspace dep added).Verification (manual end-to-end on this branch)
Three concrete breaks fixed in three successive PRs (#1504 socket discovery, #1505 attach channel, this one). Headless inbound attach is alive end-to-end.
Files
src/workers/Cargo.tomlsrc/workers/continuum-core/Cargo.tomldecode_wire_eventsrc/workers/continuum-core/src/airc/daemon_transport.rswith_identityconstructor;ipc_delivery_forhelper; updated testssrc/workers/continuum-core/src/airc/inbound_attach.rsResponse::Event { envelope }; decode viadecode_wire_eventsrc/workers/continuum-core/src/modules/mod.rsairc_runtime_e2e_tests(rewrite tracked as continuum #83)Merge gates
This PR depends on airc#1095 + airc#1096 landing on airc canary/main first. The pinned SHA (
8f6948c) is currently the tip of airc#1096's force-pushed branch (squash-merge will change the SHA). Re-pin to the post-merge airc rust-rewrite tip SHA before promoting this past continuum canary.Follow-ups
airc_runtime_e2e_tests.rsagainst v5 wire shape. Needs airc-bus dep for synthetic envelope construction viaairc_wire::encode(&Envelope).Uuid::nilfrom_peer with the scope's real peer_id. Today's anonymous publishes likely work but lose attribution.Test plan
cargo build --release --bin continuum-core-server --features metal,acceleratecleancargo test --release --lib --features metal,accelerate airc::— 73/73 passdaemon attach stream stoppedwarningsReferences
airc ipc-endpointCLI (continuum's runtime shell-out for socket discovery)impl From<>blocks (continuum's compile-time imports)airc ipc-endpoint+ auto-install #1504 — sibling: socket discovery (break Feature: CLI Implementation with Testing #1)headless-rust-must-work-soon,continuum-thesis-airc-is-the-medium,every-error-is-an-opportunity-to-battle-harden,agent-review-as-acceptable-approvalGenerated with Claude Code