Plan 018: Replace the open-indexed agent host types with explicit contracts (first slice)
Executor instructions: Follow this plan step by step. Run every
verification command before moving on. If a STOP condition occurs, stop and
report. When done, update this plan's status row in plans/README.md —
unless a reviewer dispatched you and told you they maintain the index.
Drift check (run first):
git diff --stat b8836b8..HEAD -- src/core/agent.ts src/core/agent/AgentDependencyComposer.ts
— re-verify the excerpts below if these files moved.
Status
- Risk: MED — type-tightening can surface real latent mismatches; that is
the point, but each one found must be fixed knowingly, not cast away.
- Depends on: none (avoid running concurrently with plan 017 or any
branch editing src/core/agent.ts)
- Category: tech-debt
- Planned at: commit
b8836b8, 2026-07-15
- Issue: (filled after publication)
Why this matters
The agent runtime was split into focused modules, but the seams between them
are stringly-typed: AgentDependencyComposer.ts declares
export interface AgentDependencyHost { [key: string]: any; … }, and
src/core/agent.ts bridges itself into module hosts with 48
as unknown as X casts (grep -c "as unknown as" src/core/agent.ts). Every
cast is a place the compiler was overruled; a renamed or retyped host member
fails at runtime, not compile time. The repo's roadmap names this as its top
architecture item (improving-jun-2026.md: "Stabilize the runtime
architecture around typed host boundaries…"; "the next step is to remove
broad host escape hatches incrementally") and records that the first slice
(typed AgentInputTurnHost / AgentPromptInstructionHost) already proved
the pattern. This plan continues it incrementally.
Current state
src/core/agent/AgentDependencyComposer.ts:81-83 (approx.):
export interface AgentDependencyHost {
[key: string]: any;
src/core/agent.ts — 48 occurrences of as unknown as
(grep -n "as unknown as" src/core/agent.ts lists them; e.g.
this as unknown as PostTurnActionHost, this as unknown as AgentContextRuntimeHost).
- Precedent (the pattern to copy): the typed host contracts already
introduced per improving-jun-2026.md — find them with
grep -rn "AgentInputTurnHost\|AgentPromptInstructionHost" src/core/agent/
and read how InputTurnCoordinator.ts declares an explicit interface that
AutohandAgent structurally satisfies without a cast.
- A newer good example:
PostTurnActionHost in
src/core/agent/PostTurnActionCoordinator.ts is fully typed — but
agent.ts still reaches it via as unknown as PostTurnActionHost.
- Conventions: strict TS; no
any; no behavior changes in this plan.
Commands you will need
| Purpose |
Command |
Expected on success |
| Typecheck |
bun run typecheck |
exit 0 |
| Lint |
bun run lint |
exit 0 |
| Unit tests |
bun run test |
all pass |
| Full gate |
bun run proof |
exit 0 |
Use bun run test (Vitest), never bare bun test.
Scope
In scope (this slice):
- Remove
as unknown as casts from src/core/agent.ts for host interfaces
where AutohandAgent ALREADY structurally satisfies the contract — start
with PostTurnActionHost and count upward; target eliminating at least 10
of the 48 casts with zero behavior change.
- Where satisfaction is nearly-structural (a member is missing or too loose),
add the explicit member to AutohandAgent or tighten the host interface —
smallest honest change.
src/core/agent/*.ts host interface declarations touched by the above.
- Tests only where a tightened type exposes a real mismatch (then a failing
test first, per repo rules).
Out of scope:
- Deleting the
[key: string]: any index signature from
AgentDependencyHost in one go — that is the end state, not this slice.
- Any runtime behavior change; any UI change; plan 017's entrypoint.
Git workflow
- Branch:
advisor/018-typed-agent-host-boundaries
- One commit per host contract converted; imperative sentences, no prefixes.
- Do not add any Co-authored-by trailer.
- Do NOT push or open a PR unless the operator instructed it.
Steps
Step 1: Inventory the casts
grep -n "as unknown as" src/core/agent.ts → list each target interface, and
for each, check whether AutohandAgent structurally satisfies it today
(open the interface, compare members). Classify: SAFE (satisfies now),
NEAR (one or two members off), HARD (depends on the index signature).
Record the classification in your report.
Verify: inventory produced; count of SAFE ≥ 5 (if not, STOP — the
finding's premise needs revisiting).
Step 2: Convert SAFE casts
For each SAFE host: change the call site from
fn(this as unknown as HostX) to fn(this) — if the compiler accepts it,
commit. If it rejects, the host was NEAR; move it to that bucket with the
compiler error captured.
Verify after each conversion: bun run typecheck → exit 0;
bun run test → all pass.
Step 3: Convert NEAR casts (smallest honest change)
For each NEAR host: either add/tighten the member on AutohandAgent or
loosen nothing — never widen a host type to any/unknown to make it fit.
If a NEAR conversion reveals a genuine runtime mismatch (member missing at
runtime), STOP for that host, write the failing test that proves it, fix it,
and note it in the report as a found bug.
Verify: bun run typecheck, bun run test green after each.
Step 4: Full gates and count
Verify: bun run proof → exit 0;
grep -c "as unknown as" src/core/agent.ts → record the new count (target
≤ 38, i.e. at least 10 removed).
Test plan
- No new tests for pure type-tightening (the compiler is the test).
- A failing-then-passing test for every genuine mismatch Step 3 uncovers, in
the test file owning that module (mirror existing layout under
tests/core/agent/).
Done criteria
STOP conditions
- Fewer than 5 SAFE casts exist (premise wrong — report the inventory).
- A conversion requires touching more than ~3 files — that host is a later
slice; skip it and note why.
- A revealed mismatch implies a user-visible bug — write the failing test,
then STOP and report before fixing if the fix would change behavior beyond
the type seam.
Maintenance notes
- Follow-up slices: repeat until the
AgentDependencyHost index signature
can be deleted; that deletion is the finish line for roadmap item 1.
- Reviewers: the dangerous diff smell here is a widened interface — check
every interface change is a narrowing or an explicit new member.
Plan 018: Replace the open-indexed agent host types with explicit contracts (first slice)
Status
the point, but each one found must be fixed knowingly, not cast away.
branch editing
src/core/agent.ts)b8836b8, 2026-07-15Why this matters
The agent runtime was split into focused modules, but the seams between them
are stringly-typed:
AgentDependencyComposer.tsdeclaresexport interface AgentDependencyHost { [key: string]: any; … }, andsrc/core/agent.tsbridges itself into module hosts with 48as unknown as Xcasts (grep -c "as unknown as" src/core/agent.ts). Everycast is a place the compiler was overruled; a renamed or retyped host member
fails at runtime, not compile time. The repo's roadmap names this as its top
architecture item (
improving-jun-2026.md: "Stabilize the runtimearchitecture around typed host boundaries…"; "the next step is to remove
broad host escape hatches incrementally") and records that the first slice
(typed
AgentInputTurnHost/AgentPromptInstructionHost) already provedthe pattern. This plan continues it incrementally.
Current state
src/core/agent/AgentDependencyComposer.ts:81-83(approx.):src/core/agent.ts— 48 occurrences ofas unknown as(
grep -n "as unknown as" src/core/agent.tslists them; e.g.this as unknown as PostTurnActionHost,this as unknown as AgentContextRuntimeHost).introduced per
improving-jun-2026.md— find them withgrep -rn "AgentInputTurnHost\|AgentPromptInstructionHost" src/core/agent/and read how
InputTurnCoordinator.tsdeclares an explicit interface thatAutohandAgentstructurally satisfies without a cast.PostTurnActionHostinsrc/core/agent/PostTurnActionCoordinator.tsis fully typed — butagent.tsstill reaches it viaas unknown as PostTurnActionHost.any; no behavior changes in this plan.Commands you will need
bun run typecheckbun run lintbun run testbun run proofUse
bun run test(Vitest), never barebun test.Scope
In scope (this slice):
as unknown ascasts fromsrc/core/agent.tsfor host interfaceswhere
AutohandAgentALREADY structurally satisfies the contract — startwith
PostTurnActionHostand count upward; target eliminating at least 10of the 48 casts with zero behavior change.
add the explicit member to
AutohandAgentor tighten the host interface —smallest honest change.
src/core/agent/*.tshost interface declarations touched by the above.test first, per repo rules).
Out of scope:
[key: string]: anyindex signature fromAgentDependencyHostin one go — that is the end state, not this slice.Git workflow
advisor/018-typed-agent-host-boundariesSteps
Step 1: Inventory the casts
grep -n "as unknown as" src/core/agent.ts→ list each target interface, andfor each, check whether
AutohandAgentstructurally satisfies it today(open the interface, compare members). Classify: SAFE (satisfies now),
NEAR (one or two members off), HARD (depends on the index signature).
Record the classification in your report.
Verify: inventory produced; count of SAFE ≥ 5 (if not, STOP — the
finding's premise needs revisiting).
Step 2: Convert SAFE casts
For each SAFE host: change the call site from
fn(this as unknown as HostX)tofn(this)— if the compiler accepts it,commit. If it rejects, the host was NEAR; move it to that bucket with the
compiler error captured.
Verify after each conversion:
bun run typecheck→ exit 0;bun run test→ all pass.Step 3: Convert NEAR casts (smallest honest change)
For each NEAR host: either add/tighten the member on
AutohandAgentorloosen nothing — never widen a host type to
any/unknownto make it fit.If a NEAR conversion reveals a genuine runtime mismatch (member missing at
runtime), STOP for that host, write the failing test that proves it, fix it,
and note it in the report as a found bug.
Verify:
bun run typecheck,bun run testgreen after each.Step 4: Full gates and count
Verify:
bun run proof→ exit 0;grep -c "as unknown as" src/core/agent.ts→ record the new count (target≤ 38, i.e. at least 10 removed).
Test plan
the test file owning that module (mirror existing layout under
tests/core/agent/).Done criteria
grep -c "as unknown as" src/core/agent.tsreduced by ≥ 10 from 48 (record exact numbers)any/unknownmembers (review the diff)bun run typecheck,bun run lint,bun run test,bun run proofall exit 0plans/README.mdstatus row updatedSTOP conditions
slice; skip it and note why.
then STOP and report before fixing if the fix would change behavior beyond
the type seam.
Maintenance notes
AgentDependencyHostindex signaturecan be deleted; that deletion is the finish line for roadmap item 1.
every interface change is a narrowing or an explicit new member.