Skip to content

Replace open-indexed agent host types and 48 unsafe casts with explicit contracts (src/core/agent.ts, src/core/agent/AgentDependencyComposer.ts) #431

Description

@igorcosta

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

  • grep -c "as unknown as" src/core/agent.ts reduced by ≥ 10 from 48 (record exact numbers)
  • No host interface was widened to accept any/unknown members (review the diff)
  • bun run typecheck, bun run lint, bun run test, bun run proof all exit 0
  • Each conversion is its own commit with the host named
  • plans/README.md status row updated

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    improveGenerated by the improve audit workflowtech-debtTechnical debt reduction

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions