director/agents Phase 1: carve internal/agentspi (interface + RunCtx/HostCtx, no behavior) #410

Closed
opened 2026-07-01 07:19:06 +00:00 by coilysiren · 2 comments
Owner

Phase 1 of the agent-agnostic migration designed in ward#401 (boundary decided: sub-packages + HostCtx). This is the first, UNBLOCKED unit of work - pure extraction, no data dependency (aos#306/#308 land in parallel), no behavior change. Core still switches after this lands.

Deliverable

Carve a new types-only, no-behavior package internal/agentspi:

  • The Agent interface: Name() string, Record() Manifest, Signer() attribution.Signer, LaunchArgv(RunCtx) (argv []string, stream bool), PreflightArgv(prompt string) ([]string, bool).
  • The optional capability interfaces core will feature-test: CredentialProvider (ResolveCreds(HostCtx) []EnvLine, WriteCreds(RunCtx) error), ConfigComposer (ComposeConfig(RunCtx) error), Installer (Install(RunCtx) error), OnboardingSeeder (SeedOnboarding(RunCtx) error), LaunchGate (PreLaunchCheck(RunCtx) error).
  • The narrow value types: Manifest, RunCtx, HostCtx, EnvLine.

The real work (why this is Phase 1, not trivial)

ward is today one flat package main in cmd/ward with unexported Runner/bootstrapEnv. A sub-package cannot reach those symbols, so the substance of this issue is carving RunCtx/HostCtx as narrow structs out of bootstrapEnv - carrying ONLY what per-agent code touches (AgentHome, TargetName, model/effort fields, ollama URL, a blog logger, an Exec seam). Do NOT pass the whole Runner/bootstrapEnv across the boundary - that reintroduces the import cycle the design splits.

Reference the field lists in the ward#401 advisor design (host-side seam in container.go, container-side in container_bootstrap.go, compute in container_compute.go, signature in agent_signature.go) to size RunCtx/HostCtx.

Constraints

  • No call site flips this phase. Core keeps its switches. This issue only introduces the package and the carved structs; nothing dispatches through a registry yet (that's Phase 2).
  • attribution.Signer already lives in the reusable pkg/ engine - agentspi imports it, does not redefine it.
  • Green build + existing tests pass. No new behavior to test yet beyond compilation and the struct carve compiling against real call sites.

Boundary reminder (ward#401)

internal/agents/<name>/ sub-packages (Phase 2) will implement these interfaces; agent_adapter.go becomes the data layer feeding Manifest. Keep agentspi behavior-free - it is the contract package.

Companion: ward#401 (design), aos#306 (data), aos#308 (audit). Mode: headless.

Phase 1 of the agent-agnostic migration designed in ward#401 (boundary decided: **sub-packages + HostCtx**). This is the first, UNBLOCKED unit of work - pure extraction, no data dependency (aos#306/#308 land in parallel), no behavior change. Core still switches after this lands. ## Deliverable Carve a new **types-only, no-behavior** package `internal/agentspi`: - The `Agent` interface: `Name() string`, `Record() Manifest`, `Signer() attribution.Signer`, `LaunchArgv(RunCtx) (argv []string, stream bool)`, `PreflightArgv(prompt string) ([]string, bool)`. - The optional capability interfaces core will feature-test: `CredentialProvider` (`ResolveCreds(HostCtx) []EnvLine`, `WriteCreds(RunCtx) error`), `ConfigComposer` (`ComposeConfig(RunCtx) error`), `Installer` (`Install(RunCtx) error`), `OnboardingSeeder` (`SeedOnboarding(RunCtx) error`), `LaunchGate` (`PreLaunchCheck(RunCtx) error`). - The narrow value types: `Manifest`, `RunCtx`, `HostCtx`, `EnvLine`. ## The real work (why this is Phase 1, not trivial) ward is today one flat `package main` in `cmd/ward` with **unexported** `Runner`/`bootstrapEnv`. A sub-package cannot reach those symbols, so the substance of this issue is **carving `RunCtx`/`HostCtx` as narrow structs out of `bootstrapEnv`** - carrying ONLY what per-agent code touches (`AgentHome`, `TargetName`, model/effort fields, ollama URL, a `blog` logger, an `Exec` seam). Do NOT pass the whole `Runner`/`bootstrapEnv` across the boundary - that reintroduces the import cycle the design splits. Reference the field lists in the ward#401 advisor design (host-side seam in `container.go`, container-side in `container_bootstrap.go`, compute in `container_compute.go`, signature in `agent_signature.go`) to size `RunCtx`/`HostCtx`. ## Constraints - **No call site flips this phase.** Core keeps its switches. This issue only introduces the package and the carved structs; nothing dispatches through a registry yet (that's Phase 2). - `attribution.Signer` already lives in the reusable `pkg/` engine - `agentspi` imports it, does not redefine it. - Green build + existing tests pass. No new behavior to test yet beyond compilation and the struct carve compiling against real call sites. ## Boundary reminder (ward#401) `internal/agents/<name>/` sub-packages (Phase 2) will implement these interfaces; `agent_adapter.go` becomes the data layer feeding `Manifest`. Keep `agentspi` behavior-free - it is the contract package. Companion: ward#401 (design), aos#306 (data), aos#308 (audit). Mode: headless.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-410 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-01T07:19:11Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-410` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-01T07:19:11Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - carved internal/agentspi (Agent + capability interfaces + Manifest/RunCtx/HostCtx/EnvLine), no behavior flipped, landed on main.

Honest retrospective (she/her):

This one was less "write the interface" and more "read enough to size the structs right," which is where the real work sat. The design in #401 called the shape almost exactly, so most of my time went into confirming the field lists against the live seam - bootstrapEnv, the host-side cred resolvers in container.go, the container-side compose/install/gate functions - so RunCtx/HostCtx carry only what per-agent code actually touches and *shell.Runner satisfies the Exec seam with zero adapter.

What fought back: the code-comments hook. My instinct is dense multi-line doc comments, and this repo enforces 2-line contiguous blocks with the overflow pushed to docs/. Several trim passes later it's in house style. FEATURES.md turned out to sit at exactly 3999/4000 chars, so adding an entry would have forced an unrelated doc split - I judged a no-behavior Phase 1 doesn't warrant that and left FEATURES alone, documenting the contract in a standalone docs/agentspi.md instead. Flagging that call in case a maintainer wants the FEATURES line once the split lands.

Confidence is high. It's types-only and compiles clean (build/vet/test/lint-on-new-files/pre-commit all green), and I added a carve test (agentspi_ctx_test.go) so a later rename of a carried bootstrapEnv field breaks the build here rather than silently in Phase 2. One deliberate deviation worth naming: I added agentHostCtx/agentRunCtx converters in core to prove the carve against real fields - they flip no dispatch (core still switches on e.Mode), but they're the honest evidence the struct carve works.

Rough edges / follow-ups for Phase 2: RunCtx.OpencodeModel reads bootstrapEnv.QwenModel today - the qwen->opencode roster untangle is explicitly Phase 2, so the neutral name is intentional but will want reconciling when the rename lands. And Manifest has Model/Endpoint/Auth fields that are shaped but unfed until agent_adapter.go becomes the loader (aos#306). No blockers.

WARD-OUTCOME: done - carved `internal/agentspi` (Agent + capability interfaces + Manifest/RunCtx/HostCtx/EnvLine), no behavior flipped, landed on main. Honest retrospective (she/her): This one was less "write the interface" and more "read enough to size the structs right," which is where the real work sat. The design in #401 called the shape almost exactly, so most of my time went into confirming the field lists against the live seam - `bootstrapEnv`, the host-side cred resolvers in `container.go`, the container-side compose/install/gate functions - so `RunCtx`/`HostCtx` carry only what per-agent code actually touches and `*shell.Runner` satisfies the `Exec` seam with zero adapter. What fought back: the `code-comments` hook. My instinct is dense multi-line doc comments, and this repo enforces 2-line contiguous blocks with the overflow pushed to `docs/`. Several trim passes later it's in house style. FEATURES.md turned out to sit at exactly 3999/4000 chars, so adding an entry would have forced an unrelated doc split - I judged a no-behavior Phase 1 doesn't warrant that and left FEATURES alone, documenting the contract in a standalone `docs/agentspi.md` instead. Flagging that call in case a maintainer wants the FEATURES line once the split lands. Confidence is high. It's types-only and compiles clean (build/vet/test/lint-on-new-files/pre-commit all green), and I added a carve test (`agentspi_ctx_test.go`) so a later rename of a carried `bootstrapEnv` field breaks the build here rather than silently in Phase 2. One deliberate deviation worth naming: I added `agentHostCtx`/`agentRunCtx` converters in core to *prove* the carve against real fields - they flip no dispatch (core still switches on `e.Mode`), but they're the honest evidence the struct carve works. Rough edges / follow-ups for Phase 2: `RunCtx.OpencodeModel` reads `bootstrapEnv.QwenModel` today - the qwen->opencode roster untangle is explicitly Phase 2, so the neutral name is intentional but will want reconciling when the rename lands. And `Manifest` has `Model`/`Endpoint`/`Auth` fields that are shaped but unfed until agent_adapter.go becomes the loader (aos#306). No blockers.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/ward#410
No description provided.