Skip to content

term2-next: delete the dead bridge path and make the docs describe production - #141

Merged
qduc merged 18 commits into
mainfrom
claude/zealous-volta-dfwldh
Nov 4, 2026
Merged

qduc merged 18 commits into
mainfrom
claude/zealous-volta-dfwldh

Conversation

@qduc

@qduc qduc commented Nov 4, 2026

Copy link
Copy Markdown
Owner

Closes #139 (part of #65). Draft: for the owner's team to integrate; not to be marked ready or merged from here.

Branch note: the issue asked for cloud/dead-bridge. This session can only push to claude/zealous-volta-dfwldh, so the work is here. main (d7bf2ff) is merged in rather than rebased, because a force-push was not permitted.

No user-visible behaviour is meant to change, with one intended exception: --help no longer marks serve, acp and discord as unavailable (group I asked for this).

What changed

Reachability guard (step 1). New rewrite/scripts/check-reachability.mjs runs in CI next to check-layers.mjs. It walks every static, type-only, re-export and dynamic import from rewrite/src/app/cli.ts, and fails on any rewrite/src file it never reaches unless reachability-allowlist.json gives a reason. It takes about 0.6 s.

  • Seeded failure: adding rewrite/src/tools/seeded-unreached.ts exits 1 with unreached from rewrite/src/app/cli.ts: rewrite/src/tools/seeded-unreached.ts.
  • Before: 359 of 395 files reached (36 unreached).
  • After: 354 of 359 reached, with 5 allowlisted (listed below).

Deletions.

  • E1: host/in-process.ts, protocol/{projection,commands}.ts, session/{local-store,reducer}.ts, engine/loop.ts, engine/adopted/, most of session/adopted/, config/{scoped-settings,memory}.ts, model/scripted.ts and tools/notes.ts. Also gone: the bridge contracts in each layer's index.ts (Engine, Host, Connection, Projection, Session, SessionStore and SettingsSource).
  • Bench: every c1/c2/c3a probe, source-app.mts, the native-tracer fixture, direct-manual-unknown-capacity.mts (its target session/direct-command-compaction.ts was replaced by engine/turn-model-compaction.ts), their tsconfigs and their CI step.
  • E2: encodeCanonicalEvent and canonicalEventStream (agent-wire v1).
  • E3:
    • app/run.ts (run, prepareResume, its version walker). withGenerationGuard moved first, to app/generation-guard.ts.
    • renderTurn and HeadlessEvent, the clients/headless.ts Client contract, directAcpWorkspaces, and reresolve/admitRoot.
    • The app-side publishTask/Branch/Notice forwarding. No app code ever supplied it, so DirectConversationRuntime.deps now excludes it.
    • Compactor, prepareDirectHistoryRetry/Rewind, isMessageBody and DIRECT_LEGACY_JSON_FINAL_WINDOW_AVAILABLE.

To find dead exports (not just dead files), I used a throwaway checker-based probe, run to a fixpoint, plus a tsc --noUnusedLocals diff against main. Neither probe is committed.

hops (step 3). bench/hops.mts now runs the real cli.ts in-process as a headless prompt against a local fake OpenAI-compatible provider. It reports the chain of files active at the provider's HTTP request, starting from the client's submit. Output:

message path: 14 hops through 14 files (4 in source/); 114 file transitions before folding
layers: clients -> host -> protocol -> session -> engine -> app -> model -> source
  rewrite/src/clients/headless/direct.ts
  rewrite/src/host/in-process-direct.ts
  rewrite/src/protocol/direct-commands.ts
  rewrite/src/session/direct-session.ts
  rewrite/src/session/conversation-turn.ts
  rewrite/src/engine/turn.ts
  rewrite/src/engine/turn-model.ts
  rewrite/src/app/generation-guard.ts
  rewrite/src/app/direct-application-models.ts
  rewrite/src/model/provider.ts
  source/providers/openai-chat-completions-model.ts
  source/providers/openai-compatible-response-normalizer.ts
  source/providers/fetch/compose.ts
  source/providers/fetch/composer.ts
PASS message path within budget

The budget is now { maxHops: 14, maxFiles: 14 }; it was 29/7 on the bridge.

Docs (step 4).

  • The README's layer table, rules, "How a message travels" and "Where an approval waits" now follow the direct-* files.
  • The native-tracer and C2 sections are gone.
  • "Owner scope" now says that "dropped" means no parity work. MCP, ACP, the profiles and the sandbox are all still wired.
  • Every index.ts header, the layer READMEs, the INTEGRATION docs and the file headers no longer mention the bridge, LocalSessionStore, app/run.ts or scoped settings.

Group I.

  • Help text: next's --help starred serve, acp and discord as unavailable, even though they dispatch and no flag is refused any more. The footnote now prints only while some flag is refused. --profile uses legacy's wording.
  • cli.ts comments: the ones claiming --fork and --ssh are still refused are gone.
  • Effort validators: app/README now names all three. /effort uses VALID_REASONING_EFFORTS instead of a third hand-kept copy, and its messages are byte-identical.
  • Duplicates merged:
    • CREDENTIALS now lives in app/credential-settings.ts.
    • LOCAL_ONLY/REMOTE_UNAVAILABLE became SSH_LOCAL_ONLY_TOOLS.
    • warnIfSandboxUnavailable is shared in app/sandbox-warning.ts, and both commands print the same lines as before.
    • --version reads source's installationVersion.
  • Renames:
    • createAppSettings and createDirectSettingsService became one function, createSettingsService.
    • The loadSurface alias was dropped in favour of createToolSurface.
    • DirectOrchestratorQuestions became DirectTaskQuestions.

Size: 152 files changed, +759 / −18,139.

  • rewrite/src: 395 → 359 .ts/.tsx files, 63,692 → 55,974 lines.
  • rewrite/bench: −9,718 lines.

Kept, with reasons

  • session/adopted/conversation-log.ts + network.ts (allowlisted). The offline v3 migration (scripts/conversation-migration/v3-input.mjs) imports validateSessionFact from the emitted module. Only the validator and its types remain; the CanonicalSessionLog writer and loaders are gone.
  • session/direct-import.ts (allowlisted). migration-command.mjs imports it.
  • protocol/index.ts, session/index.ts (allowlisted). Nothing imports them, but they are the per-layer maps the fresh-reader check reads.
  • Agent-wire v2 (server-wire-v2.ts), createGatewayAssertion, MemoryReplayLedger, directHostConfigFailure. [port C6] host/: server host over the gateway's transport #80 was reopened with "existing work and artifacts stay; gates remain closed". Web is deferred, not dropped.
  • updateView, cancelForeground, the three terminal-size sources. All are in clients/tui/, which the issue says to leave alone. Follow-up.
  • applyModelFlags (ACP) vs applyModelOverrides (CLI). Not a plain duplicate: the messages differ, each matching its legacy command, and only the CLI has the picker. Merging them would change output.
  • readFacts. bench/m1-direct.mjs loads it from dist.
  • clients/tui/INTEGRATION.md. Every backticked path and symbol in it still resolves, and I found nothing stale I could verify, so it is unchanged.

The generated native-log property used to build two items through engine/adopted/conversion.ts#inputToItem. It now builds the same item shapes literally, and pnpm verify:generated --target native --runs 100 passes. After the deletions, the direct build still emits every rewrite/dist module the migration scripts import. It no longer emits 51 modules, including legacy AgentClient.

Fresh-reader check

A context-free subagent got only rewrite/README.md and the seven index.ts files. It traced the path correctly: cli → direct-launch-headless → clients/headless/direct.ts → host/in-process-direct.ts → protocol/direct-commands.ts → DirectSession/Conversation → startTurn, where conversation-turn.ts appends facts then accept(token) → turn-model → app registry + generation-guard → model/provider.ts. On the way back, one DirectProjection per session in in-process-direct-attachment.ts fans out to every connection. It also got the approval wait right: interaction_required, the turn waits in memory, conversation-turn.ts writes the pending interaction, an answer command reaches TurnHandle.answer, and after a crash the turn reopens as interrupted.

It flagged these problems, all fixed in d738d35:

  • The README's "production files are the direct-/turn/conversation*/server-* families" claim was false.
  • "14 hops" came with no list, and "down the layers" was wrong while app/ sits below engine/.
  • config/index.ts still said "settings and profiles".
  • host/index.ts didn't mention the attachment and didn't say the web wire is gated.
  • The README said both "reopened" and "ended" for an interrupted turn.

Points it raised that I left alone: retry ownership between model/retry.ts and direct-recovery*, and some unexplained symbol names (DirectDraft, ToolContextSeed).

Checks

Tested revision: the tree of fd861da (identical to the rebased commit that was tested). The suites ran before the last two doc-only commits, which change no code.

  • pnpm install && pnpm build && node rewrite/scripts/build.mjs: pass. Needs pnpm ≥ 11; pnpm 10 reports the lockfile as broken.
  • pnpm exec tsc -p rewrite/tsconfig.json: pass.
  • node rewrite/scripts/check-layers.mjs: pass (521 edges).
  • node rewrite/scripts/check-reachability.mjs: pass.
  • node rewrite/scripts/check-complexity.mjs: pass. No file is over 500 lines any more, and every ceiling is removed.
  • pnpm typecheck: pass.
  • NODE_ENV=test pnpm exec vitest run --config vitest.e2e.config.ts source/direct-: 97 of 98 passed. The one failure is in direct-settings.e2e.test.ts, at teardown: ENOTEMPTY: rmdir .../home, with no failed assertion. It fails the same way on unmodified main d7bf2ff (3 of 6 isolated runs), and on this branch it failed 1 of 3 isolated reruns. The cause is that afterEach kills the PTY without waiting for the child to exit, then removes its directory. The lesson is recorded in the testing skill.
  • pnpm test:provider-black-box: 252 passed, 8 skipped.
  • Also passing: rewrite/bench/m1-direct.mjs (33/33 rows), parity headless-plain-answer settings-batch against the direct build, parity/inventory.mjs, and hops.mts.

Not run: the full scripts/run-ci-lane.mjs lanes and the old-CLI parity check.


Generated by Claude Code

claude added 18 commits November 4, 2026 01:02
check-reachability.mjs walks every import (static, type-only, re-export,
dynamic) from rewrite/src/app/cli.ts and fails when a rewrite/src file is
never reached, unless reachability-allowlist.json gives a reason it stays.
The allowlist starts with today's 36 unreached files; the bridge deletions
that follow shrink it. Runs next to check-layers.mjs in CI (~0.6s).

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
hops.mts ran a message through the bench-only LocalSessionStore and adopted
engine, so its budget guarded a path no shipped mode takes. It now runs the
real entry (rewrite/src/app/cli.ts) in-process as a headless prompt against
a local fake OpenAI-compatible provider, and reports the chain of files
active at the provider's HTTP request, from the headless client's submit:
14 hops, one per layer, clients -> host -> protocol -> session -> engine ->
app -> model -> source.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
The c1/c2/c3a probes, source-app.mts and the native-tracer fixture drive
LocalSessionStore, the adopted engine or the pre-C2 native engine, none of
which a shipped mode runs. Their CI step goes with them; parity drops the
C2-proof-only assertion. direct-manual-unknown-capacity.mts covered
session/direct-command-compaction.ts, which production replaced with
engine/turn-model-compaction.ts. The generated native-log property now
builds its two items literally instead of through engine/adopted/conversion.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
host/in-process.ts, protocol/projection.ts, protocol/commands.ts and the
headless Client contract (clients/headless.ts, clients/index.ts) served only
the bridge; the direct host, protocol and headless client replace them.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
…ed handle

session/local-store.ts, session/reducer.ts, the adopted foreground,
submission, command and handle files, and direct-command-compaction.ts are
unreached from app/cli.ts. session/adopted keeps conversation-log.ts (the
fact union production and migration decode) and network.ts.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
engine/adopted/ ran the old CLI's AgentClient behind the bridge;
engine/loop.ts was the native-tracer fixture's engine. The direct engine
(engine/turn*.ts) is the only one production runs.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
…es tool

config/scoped-settings.ts, config/memory.ts, model/scripted.ts and
tools/notes.ts were reached only by deleted benches and the tracer fixture.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
…p/generation-guard.ts

run() and prepareResume were the pre-direct headless runner; cli.ts launches
headless runs through direct-launch-headless.ts. withGenerationGuard is live
(both direct runtimes wrap their model registry with it), so it moves first.
RunRequest/RunHandle fold into DirectHeadlessRequest/Handle, and the M1
renderTurn renderer and HeadlessEvent go with their only caller. The
log-reader exception for app/run.ts in check-layers.mjs is retired.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
encodeCanonicalEvent and canonicalEventStream encoded accepted events as
agent-wire v1; no route serves that format. canonicalCandidate stays: the
gated agent-wire v2 encoder (server-wire-v2.ts) builds on it. The v2 wire
itself stays behind AGENT_WIRE_V2_RUNTIME_AVAILABLE=false because #80
(web/server) is deferred, not dropped, and keeps its existing work.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
…acts

engine/, host/, protocol/, session/ and config/ index.ts still declared the
bridge's Engine, Host, Connection, Projection, Session and SessionStore
contracts, which nothing in production implements or calls. They go, and
each header now describes the direct-* files a message actually passes
through. With the bridge Host gone, the adopted session-fact union is used
only by the offline v3 migration, so session/adopted/conversation-log.ts
keeps just validateSessionFact and its types; its CanonicalSessionLog writer
and loaders go. protocol/ and session/ index.ts are imported by nothing and
are allowlisted as the layer maps the README's fresh-reader check reads.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
prepareDirectHistoryRetry/Rewind, isMessageBody,
DIRECT_LEGACY_JSON_FINAL_WINDOW_AVAILABLE, directAcpWorkspaces and the
workspace authority's reresolve/admitRoot have no callers. The direct store
forwarded publishTask/Branch/Notice to optional app-supplied publishers that
no app code provides; DirectConversationRuntime now excludes them and the
store publishes to the session alone.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
--help starred serve, acp and discord as 'Not available in the direct
executable yet', though all three dispatch, and no option carries a refusal
any more. The footnote now prints only while some option is refused. The
--profile line takes legacy's wording (bare [default] ...; standard/full/lite
= bare), which matches canonicalProfileId. cli.ts loses comments claiming
--fork and --ssh are still refused. /effort reads VALID_REASONING_EFFORTS
instead of a third hand-kept copy; its messages are unchanged.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
README: the layer table, rules, 'How a message travels' and 'Where an
approval waits' now follow the direct-* files a headless prompt passes
through (the path hops.mts measures); the native-tracer and C2-proof
sections and bridge-only checks go; 'Owner scope' says dropped means no
parity work, and that MCP, ACP, profiles and the sandbox are still wired.
Layer READMEs, INTEGRATION docs and file headers drop references to the
bridge Session/Host, LocalSessionStore, app/run.ts and scoped settings;
app/README names the three reasoning-effort validators.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
- CREDENTIALS: the account commands and the server's settings projection
  kept the same provider-to-setting table; it lives in
  app/credential-settings.ts (providerId was always the key).
- LOCAL_ONLY (SSH adapter) and REMOTE_UNAVAILABLE (tool surface) were the
  same five tools; direct-remote-tools.ts exports SSH_LOCAL_ONLY_TOOLS.
- warnIfSandboxUnavailable: acp and serve share app/sandbox-warning.ts; the
  lines they print are unchanged.
- --version reads source's installationVersion, the resolver session
  metadata already used, instead of a second package.json walk in cli.ts.
Also drops four unused type imports.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
- createAppSettings wrapped createDirectSettingsService, which only called
  new SettingsService(options). One function, createSettingsService in
  app/settings.ts, now returns the service itself.
- loadSurface was an alias of createToolSurface; app/agent-surface.ts goes
  and callers use createToolSurface.
- DirectOrchestratorQuestions becomes DirectTaskQuestions
  (session/direct-task-questions.ts): it holds the one pending question each
  background task asked its parent, recorded as task_question_* facts.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
A context-free reader given only the README and the layer index files traced
the message path and the approval wait correctly, and flagged: a wrong claim
that production is the direct-*/turn*/conversation*/server-* families; a hop
count with no list (and 'down the layers' while app/ sits below engine/);
config/index.ts still saying 'settings and profiles'; host/index.ts omitting
the attachment that owns the projection and not saying the web wire is gated;
'reopened' vs 'ended' for an interrupted turn. Each is corrected.

Refs #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
Reproduced on unmodified main (3 of 6 isolated runs) while verifying #139.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJB9USbeW5UuNMFVsDas
@qduc
qduc merged commit c69012a into main Nov 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

term2-next: delete the dead bridge path and make the docs describe production

2 participants