Skip to content

v0.11.0

Latest

Choose a tag to compare

@wdhwg001 wdhwg001 released this 06 Sep 08:07
· 1 commit to main since this release

The csift channel: a way to get a message to a Claude Code lane the official channel cannot reach, and to find out afterwards whether it arrived.

Why a channel

Claude Code has its own messaging. A session sends to a teammate by name, to one of its own subagents by id, or to another session over a local socket. csift can now name the right official call for a pair of lanes, and for most pairs that is the whole answer. The channel exists for the pairs where the official transport is not weaker but absent. A running workflow lane cannot be reached by the send tool at all. An unnamed subagent has no way to address the subagent that spawned it, because main reaches the top-level conversation and not the parent. Nothing wakes an idle session that never published a socket. And a sender outside Claude Code, a human at a shell or a Claude in another harness, holds no tool to call in the first place. csift reads and writes the same on-disk shapes those senders can see, so it can carry a message where the tool cannot.

The contract changed, so this is a minor release

Through 0.10.5 csift wrote nothing. From 0.11.0 exactly three commands write, and they write exactly one place. send, deliver and ack append to a csift-channel directory that csift creates inside the session's own sidecar folder, beside the subagent transcripts. Never a transcript, never the team mailbox, never the messaging socket, never the session registry, never a settings file. csift does not install its hook either: csift deliver --recipe prints the settings block and a person pastes it. Every other subcommand still only reads. Nothing that worked before stops working. The promise a reader had was narrower than the one they have now, and a narrower promise is a breaking one.

What is new

csift send @<lane> "message" queues one message for one lane and prints one verdict. OK means the receiver is alive, its delivery hooks are configured and armed, and the message fits. FULL means it is queued but needs more slots than the receiver has. MAY-FAIL names a risk. UNPREDICTABLE means the receiver is not alive and nobody can say when it will be. REFUSED means it can never arrive and nothing was queued. A refusal exits 0, because it is a definitive answer about the receiver and not a usage error. Where an official transport does exist, the receipt prints the exact call for you to make, and csift queues its own copy anyway.

csift deliver --slot k is the hook entry. You never run it by hand. Each installed line carries its slot number, the slots of one event order themselves through a small marker chain, and slot k emits chunk k of whatever is waiting. A message that asks to arrive at a turn boundary may hold the turn open through the harness's own stop-hook mechanism, while the harness's block cap allows it, and never chains two holds for one message.

csift msg <id> answers the question a queue cannot: did it actually arrive. The per-lane ledger records what csift emitted. The receiver's transcript proves what landed, as a hook-context attachment carrying the message id. msg joins the two into one verdict, and the proof half on its own is exactly csift search '<id>' @<lane> --additional-context, so the join can be audited without trusting it. csift ack <id> records the one thing only a receiver can say.

whoami gained a lane layer. It accepts an agent id in either of a teammate's two forms, prints self, parent and topology sections, and answers --to @<lane> with a reach prediction that sends nothing. --peers lists every live lane as id, kind and state and nothing else. A description or a role-shaped name is the material one lane would use to claim standing over another, so the census says who is alive and not who should be obeyed. Outside Claude Code, whoami prints the not-a-lane answer with the one channel out and what a receiver needs installed, and exits non-zero, because there is no session to identify.

A delivery lands in the receiving lane as a hook-context attachment, and agent.communication.channel is the one attachment label a default search reaches, because a message addressed at the lane is not machinery. It renders verbatim, envelope header included, so a reader sees who sent it, under what relation, and that it came from neither the user nor the harness. Every other hook attachment stays behind its flag, and two new tests pin that a document which merely quotes the envelope header never leaks through.

A teammate carries two ids minted apart at spawn. The routing form Name@Team is what the official send tool needs, and it can collide when two teammates share a name. The transcript form on disk never collides. csift resolves either, keys all of its own work on the transcript form, teaches which one the official tool wants, and prints both wherever a teammate appears.

To answer "will a delivery hook actually run there", csift now reads Claude Code's settings cascade the way Claude Code does: five scopes in the harness's own order, env merged per key, hooks concatenated per event, plugin hook manifests unioned, the policy tier composed first-wins, and the three policy switches that can empty the whole hook set. Every receipt and every reach prediction names the scopes it read and the inputs it cannot see (a settings file passed on the command line, a restricted source list, the trust dialog's state, a managed profile), so a gate verdict says "unknown" with its evidence instead of implying the file scopes are the whole story. plan reads its plansDirectory through the same model with its precedence unchanged.

What the build found and fixed on the way

The channel was built by a fleet of implementers, each paired with a verifier that re-ran the whole gate and had to catch a planted fabrication in the implementer's report before its acceptance counted. Those verifiers found real defects before any of this reached a user. The default-on candidate needle for the channel label let an ordinary hook context that merely mentioned the envelope literal into a flagless search. A post-parse gate now keeps both gated attachment leaves behind their flags. The policy table treated a subagent addressing its own parent as a sibling and would have handed the message to a transport with no parent arm. The relation is now read from csift's own topology in both directions, and a real parent never receives the peer caution sentence. A regex whose construction carried an expect in a library path was replaced by a token walk. The reach prediction borrowed a sentence from the send receipt and claimed a write it never made. The hand-written command list in the root help had drifted from the command enum in two places, and a drift guard now pins the two together.

Verification

The suite is 1243 unit tests and 690 end-to-end cases, all passing on every one of the twenty-one commits behind this release, because each one went through the pre-commit gate: the structure limits, formatting, clippy with warnings as errors, the full suite, and the introspection ledger gate. Line coverage is 96.14 percent (983 lines missed of 25,472), above the 95.79 percent no-drop baseline of 0.10.5 after a top-up pass that added twenty-six tests and changed no production code.

A scoped mutation census ran over every source file this release touched, 55 files and 1,476 mutants, at two jobs with a disk watchdog. It reached 1,242 caught before the coverage top-up's tests were counted, 1,261 after a re-test of the survivors, and 1,314 after a kill tranche wrote fifty-three differentiating tests, each verified against its own mutant by a targeted census. Of the 51 that survive, 14 sit in this release's code and are equivalent by construction (an early-out whose absence changes no output, a hash mixer whose only contract is sixteen fresh hex characters, an arm compiled only off this platform) and 37 are the documented pre-existing zones of earlier releases, each classified with its reason in the commit that closed the gate. The score is 96.3 percent counting timeouts as kills, and 96.1 percent counting caught mutants alone, against 91.2 percent for the previous release. Every gate ran on this machine, so the timeouts are the conventional loop-counter mutants, not load.

The introspection ledger holds 616 claims, 94 of them in the new messaging area, one per Claude Code behaviour the channel depends on: every hook event's payload, the additionalContext persistence threshold, the stop-hook block cap, the settings cascade and its two feature gates, the resume state machine, the official mailbox and socket shapes, and the history floors of each literal across sixty cached builds. The README table states how much of that is traced end to end and how much rests on a producer trace or a specimen alone. The messaging claims land mostly below the end-to-end line, with byte offsets but not yet the verbatim three-hop excerpts the next audit will add.

The build itself ran as three layers of Opus implementers in isolated worktrees, each paired with a read-only verifier that re-ran the whole gate and had to catch a planted fabrication in the implementer's report before its acceptance counted. Every verifier caught its canary. The whole pipeline, send to deliver to msg to search, was then driven by hand against a throwaway configuration home before the docs were written.

The eight binaries were built from the tag on their own platforms, and the full suite ran on every platform that can run it. The four Linux lanes (gnu and musl, arm64 and amd64, the amd64 pair under emulation) each passed 1243 unit tests and 690 end-to-end cases. The two Windows lanes, arm64 native and x64 under emulation on the same guest, each passed 1242 and 689, the two absent tests being the unix-only process probe and its locale pin. The two macOS binaries were built with path remapping on the development machine, where the suite had already run through the gate on every commit, and were smoke-run for their version.

The Windows arm64 lane failed once before it passed, and the failure is worth recording because it is the third release in a row where that lane found something the others could not. The end-to-end case for the slot chain's timeout path pre-made the chain directory under the test process's own pid, which is the parent pid csift keys the chain by on unix. On Windows no parent pid is exposed to a std program, so csift keys the chain by a hash of the hook's session id there, which every slot of one firing shares. The delivery itself passed on Windows, with the disorder warning first and the message intact, and only the fixture's final assertion about which directory had been used failed. The fixture now derives the key the way csift does on each platform, a test-only commit on main after the tag with the source tree unchanged, and the relaunched lane passed both architectures.