Skip to content

fix(vocabulary): close the nine drift items that needed a decision - #959

Open
drewstone wants to merge 5 commits into
mainfrom
fix/vocabulary-drift-954
Open

fix(vocabulary): close the nine drift items that needed a decision#959
drewstone wants to merge 5 commits into
mainfrom
fix/vocabulary-drift-954

Conversation

@drewstone

@drewstone drewstone commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Closes the nine items in #954. The decision for each, with its evidence, is recorded in this comment; this pull request is that record executed. One pull request rather than nine because each would need its own version bump and fixture regeneration, and each rebase would invalidate the next.

Three of the nine turned out different from filed. Those corrections are called out below.

Behaviour changes

The bridge reasoning check now reads one owner, and it was refusing live runs. expectedBridgeAppliedReasoning expected codex to apply minimal for none and high for both xhigh and ultracode. cli-bridge 0.3.0 applies none, xhigh and ultra — its own comment records why: "Codex CLI 0.147.0 accepts the canonical values directly." The values are compared at runtime.ts:3582 and a mismatch throws ValidationError, so three of the seven rungs refused a legitimate codex run. The default arm asserted the canonical rung for eleven more harnesses, none of which plumbs a thinking flag — their receipts carry applied: null, so those runs were refused too. Both halves are gone: the check now calls nativeReasoningControl from @tangle-network/agent-interface 1.6.0, the same table the cli-bridge argv builders read (tangle-network/agent-sdk#243).

An in-process worker is no longer recorded as a sandbox sibling. LoopSandboxPlacement.kind and LoopIterationDispatchPayload.placement carry 'in-process', and the in-process executor returns it. Both are @stable, so this is a deliberate widening: a cost or latency breakdown split by placement was counting every local worktree-CLI iteration in the sandbox bucket, which is a wrong number rather than a missing one. placementInfoFromLoopPlacement folded everything non-fleet into sandbox; PlacementInfo.kind already admits 'local', which is where in-process belongs.

improve() can no longer produce a surface a proposal cannot name. ImproveSurface is now Exclude<AgentImprovementSurface, 'knowledge'>, so the subset relation holds by construction. agent-interface 1.6.0 adds rollout-policy to the proposal vocabulary. Two sites fell out of deriving the type: improvementSurfaceValues must now produce a rollout-policy value (it reads structuralRolloutPolicyFromProfile, the same accessor improve() uses), and opaqueProfileSlice folded profile.extensions — where the rollout policy lives — into the agent-profile surface, so one rollout-policy edit would have reported two changed surfaces. The slice now excludes that key.

One word for a failed projection row. PursuitRunStatus said failed and PursuitNodeStatus said down, in the same file, so a consumer joining run rows to node rows on status split one failure population into two. Both are replaced by one exported PursuitStatus = 'running' | 'done' | 'down'. down wins because it is the journal's own word — a settlement is journaled as kind: 'down' — and three of the four downstream copies already used it. The agent.run hook payload still spells a failure failed and is still read that way; what changed is what the projection writes.

Deletions

Restart, SpawnOpts.restart, and restartCount. A caller could write scope.spawn({ restart: 'permanent' }) and the supervisor dropped it silently. Implementing restart was the alternative and it is the wrong one: the retry story already has an owner. A keyed spawn is idempotent per key, and a key whose prior attempt settled down spawns fresh and says so (prior.state: 'retried' | 'lost'). restartCount goes with it because nothing could ever increment it — and it was deader than filed: replaySpawnTree hard-coded it to 0 at all three of its own settlement sites, overwriting whatever a journal carried.

CoderReview.recommendation. Its docstring promised traces. The delegate has no run id to emit a loop.decision with, and the reviewer runs after the kernel's loop has finalized, so there is no iteration trace to attach to — the promise was unkeepable at that call site, not merely unkept. The field also adds nothing actionable: selection reads approved and readiness, and ship/approve-with-nits are both approved: true while changes-requested/reject are both approved: false. notes is now documented as the one field a caller sees.

UsageClass. Its declaration was its only occurrence anywhere. UsageSplit already carries the same idea as two named number fields.

Two of the three memory-store provisions. provision is 'sqlite' alone — the only value the codebase or its test ever names.

Dedupe

ResearchSource has one owner. src/profiles/researcher.ts declares it; src/mcp/types.ts imports and re-exports it, the direction it already imports for CoderTask and the UI-auditor types. Both public entry points still name the type.

Corrections to #954

  • Item 4 is bigger than filed. The drift is not confined to the default arm — the codex mapping inside the switch is stale against cli-bridge 0.3.0 and refuses three of seven rungs today.
  • Item 8's ResearchSource claim is wrong. It is not dead: DelegateResearchArgs.sources reads it, and DelegateResearchArgs is a live member of DelegationArgs, the task queue's dispatch union (task-queue.ts:59), with its own test. Only UsageClass was dead. Dedupe, not deletion.
  • Item 6 is a documentation fix, not a defect, so nothing is deleted. interactive-session-not-started is the Executor PORT's vocabulary: Scope.interactive keeps whatever reason an executor's own interactive() returns (scope.ts:1327), so an executor implemented outside this package can emit it today, and deleting it would break those — the same reasoning that saved SpawnEvent.cancelled from the refuted list. No first-party executor emits it because exactly one executor implements interactive() at all; the sandbox arm doing so is feat(execution): expose exact interactive session attachment #773, blocked upstream on a Sandbox SDK method absent in 0.31. The member's docstring now says both.
  • Item 9 is smaller than filed. The resolver already refuses the whole memory-store arm with a named CapabilityNotAdmittedError, so a neo4j manifest was never quietly provisioned. Only the type over-promised.

Verification

pnpm run typecheck, pnpm run lint, pnpm run check:version-bump, pnpm run check:testing-fixture and pnpm run docs:check pass. Targeted suites for every touched area pass.

Four failures in src/improvement/improve.test.ts reproduce identically on an unmodified origin/main checkout of this machine (git-worktree fixtures on macOS); they are not from this change. Linux CI is the authority.

Follow-up

primeThinkingLevel in @tangle-network/agent-profile-materialize maps the same ladder onto the prime fork's --thinking rungs. The two agree today and were verified value by value, but they are two owners of one fact; collapsing them is tangle-network/agent-dev-container#6136. Nothing reads a prime materialization receipt today — the prime backend never calls profileExecutionIdentity — so no check depends on it.

The bridge reasoning check read a private copy of the cli-bridge effort map and the copy was
stale. It expected codex to apply `minimal` for `none` and `high` for `xhigh` and `ultracode`;
cli-bridge 0.3.0 applies `none`, `xhigh` and `ultra`. A mismatch throws, so three of the seven
rungs refused a legitimate codex run. Its default arm asserted the canonical rung for eleven
more harnesses whose receipts carry `applied: null`, refusing those too. Both halves now read
`nativeReasoningControl` from @tangle-network/agent-interface 1.6.0, the same table the bridge
argv builders read. The two test fakes echoed the same stale map, which is why the drift
survived; each now transcribes cli-bridge instead.

An in-process worker is no longer journaled as a sandbox sibling: the two loop trace types
carry `in-process`, and `PlacementInfo` maps it to `local`. A cost or latency breakdown split
by placement counted every local run in the sandbox bucket.

`ImproveSurface` is derived from `AgentImprovementSurface`, so what `improve()` produces is a
subset of what a proposal can name by construction. `improvementSurfaceValues` gains the
rollout-policy value, and `opaqueProfileSlice` stops folding the same extension into
`agent-profile`, which would have reported two changed surfaces for one edit.

One `PursuitStatus` replaces `PursuitRunStatus` and `PursuitNodeStatus`: they disagreed on the
word for a failure in the same file, so joining run rows to node rows split one failure
population into two. `down` wins because the journal writes it.

Deleted: `Restart` and `SpawnOpts.restart`, which the supervisor dropped silently while the
keyed-spawn contract already owns retry; `restartCount`, which nothing could increment and
which replay overwrote with 0 anyway; `CoderReview.recommendation`, which promised traces the
delegate has no run id to emit and which selection never read; `UsageClass`, whose declaration
was its only occurrence. `memory-store.provision` narrows to the one backend anything names.

`ResearchSource` is deduped rather than deleted: it has a live reader through `DelegationArgs`.
`interactive-session-not-started` is kept and documented: it is the Executor port's vocabulary,
and its first-party emitter is #773, blocked upstream.

Closes #954
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.

1 participant