The first SDK-backed seeded runs. Run 1 refused both seed sessions: the
sidecar's schema conversion dropped every field description, `due` was an
undescribed integer against a clock the model never saw, and the subject
tool did not say a grant is required. Run 2 seeded six people in one round
and ticked three times.
A refusal used to reopen the session with an empty draft and a prompt that
named sites from a draft the model could no longer see. It now continues
the same conversation: `Refusal { after_round, mismatches }` on both
authoring checkpoints (controller_work.v12), the refusal as the next user
turn, and the draft rebuilt by one DraftFold shared by evaluator and oracle
from the calls after it. The harness takes the connector only when its
address is set, so an all-claude run needs none.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The sidecar registered a closed Zod schema, so the SDK judged arguments
before Rust ever saw them: an extra key was stripped, a missing required
property was refused with an MCP error, and the call never reached the
handler. The connector path decodes the same argument in Rust, where
`deny_unknown_fields` and required-field decoding turn a bad argument
into the gap the evaluator records.
`toolInputSchema` now builds a loose object whose every property
catches, so nothing is stripped and nothing is refused. The conversion
survives only to advertise: the in-process MCP server derives the schema
the model sees from this object, and that schema still carries every
property, type, bound, and `required` name the emitter produced. What is
deleted is the strictness that made it a second validator -- `.strict()`,
the closed-object requirement, and the every-property-is-required
requirement.
One case cannot be carried through as written: a value outside a
property's own type empties that property, so it reaches Rust as an
absent field rather than a wrong one. Rust still names the gap; the SDK
still does not answer the call.
The undispatched-tool_use protocol violation stays, because no argument
reaches it any more but a call never offered to the handler still does.
`resultFault` is the result-subtype mapping lifted out of `runQuery`, so
`error_max_turns` becoming an ordinary round and every other subtype
becoming its named fault are both asserted without a credential.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`sidecar/claude-sdk` is the repo's second TypeScript package, wired to the
root only through `npm --prefix`. It reads length-prefixed MessagePack
frames on stdin, runs one SDK query, forwards every tool call to Rust, and
returns Rust's answer to the model verbatim. It computes no tool result,
reads no credential, logs no prompt, argument, or result, and writes nothing
outside its own stdout.
The query is opened with a custom system prompt, `settingSources: []`,
`tools: []`, `strictMcpConfig`, `persistSession: false`, and `alwaysLoad` on
every tool, so no CLAUDE.md, settings file, output style, built-in tool, or
persisted transcript reaches a caller-derived prompt.
`schema.ts` converts exactly the grammar `tool_schema.rs` emits and throws on
anything else, so an emitter that outgrows it fails a test rather than
registering a looser tool. Two Rust tests publish the fixtures the sidecar
tests read — the frame bytes and every real `parameters_json` — so the halves
cannot drift apart silently.
Dependencies are exact-pinned: @anthropic-ai/claude-agent-sdk 0.3.261,
@msgpack/msgpack 3.1.3, zod 4.5.4, and dev typescript 5.9.3 with
@types/node 24.13.3. `engines.node` is the installed major.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>