feat(agent): echo resolved model-context config at startup + --config overrides for warded runs #616

Closed
opened 2026-07-06 16:40:07 +00:00 by coilysiren · 2 comments
Owner

What Kai wants

Two things on every warded / ward agent run:

  1. Echo the full model-context config block at container startup so a run's resolved harness config (agent, model, effort/reasoning, endpoint, context-level) is visible in the container log stream, not silent.
  2. Override those configs from the CLI, repeatable, dotted-path shaped:
ward agent engineer --config agent.claude.model=sonnet --config agent.claude.effort=medium

Kai's steer: fit to the easiest technical impl, not perfect UX.

Current state (the pattern to mirror)

codex already carries this shape end to end and is the template:

  • Fleet manifest node agent codex { model gpt-5.4; reasoning-effort medium; verbosity low } in .ward/ward-kdl/ward-kdl.fleet.kdl (mirrored to cmd/ward/fleetassets/fleet.generated.kdl by make sync-fleet-assets, drift-guarded by fleetassets_test.go).
  • container_bootstrap.go reads each with an env override: CodexModel: envOr("WARD_CODEX_MODEL", codex.Model) (plus WARD_CODEX_REASONING_EFFORT, WARD_CODEX_VERBOSITY).
  • bootstrapEnv -> agentsapi_ctx.go -> RunCtx.CodexModel/CodexEffort/CodexVerbosity.
  • internal/agents/codex/config.go writes the config and logs it: rc.Log("wrote codex config ... model %s / effort %s / verbosity %s ...").

claude has none of this. internal/agents/claude/claude.go record sets no Model, and LaunchArgv launches bare claude with no --model / effort. The fleet Agent grammar in cli-guard already parses model / reasoning-effort / verbosity generically (/substrate/cli-guard/pkg/fleetconfig/fleetconfig.go, applyAgentProp), so putting them on the agent claude node needs no grammar change.

Scope for this issue (ward-only, fully landable)

  1. Wire claude model + effort, mirroring codex end to end:

    • Add model (and reasoning-effort if it maps cleanly to a claude-native knob) to the agent claude node in ward-kdl.fleet.kdl, then make sync-fleet-assets.
    • WARD_CLAUDE_MODEL / WARD_CLAUDE_REASONING_EFFORT env overrides in container_bootstrap.go.
    • Thread ClaudeModel / ClaudeEffort through bootstrapEnv -> agentsapi_ctx.go -> RunCtx.
    • claude.go LaunchArgv appends --model <model> and applies effort by whatever claude-native mechanism is simplest (a flag if one exists, else an env line the harness reads). Empty model means omit the flag, so today's behavior is unchanged.
  2. --config <dotted.path>=<value> repeatable flag on ward agent. Easiest impl: parse agent.<name>.<key>=<value> and translate each into the matching WARD_<NAME>_<KEY> container env line, so it rides the exact override mechanism codex/claude already read (e.g. agent.claude.model -> WARD_CLAUDE_MODEL, agent.claude.effort -> WARD_CLAUDE_REASONING_EFFORT). Unknown keys fail loud with the list of known keys. Add --config to the broker's engineer/advisor flag allowlist in agent_dispatch_broker.go (validateDispatchBrokerArgv) so an in-container dispatch can forward it.

  3. Startup echo of the resolved model-context config block. One clear block in the container log at startup listing, for the launched agent: agent name, model, effort/reasoning, endpoint, context-level (WARD_CONTEXT_LEVEL). Today only codex logs its own config line inside ComposeConfig; generalize so claude (and every agent) echoes its resolved config regardless of whether it writes a config file.

Out of scope (dependent follow-ups)

Per-role config in the fleet kdl (different model/effort per role - engineer vs advisor vs director) needs the cli-guard Role grammar extended: it currently parses only guardfiles and hard-errors on any other child in parseRole. Tracked in cli-guard (grammar) and a ward consumer issue that pins the new cli-guard tag. Do not attempt the cross-repo grammar change in this run - keep this one ward-only and landable.

Precedence

Highest wins: --config CLI flag > WARD_* env > per-agent fleet default. (Per-role slots between env and agent-default once the follow-up lands.)

Acceptance

  • warded engineer #N --config agent.claude.model=sonnet launches claude with that model, and the startup block shows it.
  • No --config and no env means today's behavior, --model flag omitted.
  • ward exec test and ward exec vet green, pre-commit run --all-files clean, agent_adapter_test.go two-way pin still holds after the fleet edit.
  • docs/FEATURES.md and the relevant docs/agent*.md updated in the same commit.
## What Kai wants Two things on every `warded` / `ward agent` run: 1. **Echo the full model-context config block at container startup** so a run's resolved harness config (agent, model, effort/reasoning, endpoint, context-level) is visible in the container log stream, not silent. 2. **Override those configs from the CLI**, repeatable, dotted-path shaped: ``` ward agent engineer --config agent.claude.model=sonnet --config agent.claude.effort=medium ``` Kai's steer: **fit to the easiest technical impl, not perfect UX.** ## Current state (the pattern to mirror) `codex` already carries this shape end to end and is the template: - Fleet manifest node `agent codex { model gpt-5.4; reasoning-effort medium; verbosity low }` in `.ward/ward-kdl/ward-kdl.fleet.kdl` (mirrored to `cmd/ward/fleetassets/fleet.generated.kdl` by `make sync-fleet-assets`, drift-guarded by `fleetassets_test.go`). - `container_bootstrap.go` reads each with an env override: `CodexModel: envOr("WARD_CODEX_MODEL", codex.Model)` (plus `WARD_CODEX_REASONING_EFFORT`, `WARD_CODEX_VERBOSITY`). - `bootstrapEnv` -> `agentsapi_ctx.go` -> `RunCtx.CodexModel/CodexEffort/CodexVerbosity`. - `internal/agents/codex/config.go` writes the config and logs it: `rc.Log("wrote codex config ... model %s / effort %s / verbosity %s ...")`. **claude has none of this.** `internal/agents/claude/claude.go` `record` sets no `Model`, and `LaunchArgv` launches bare `claude` with no `--model` / effort. The fleet `Agent` grammar in cli-guard **already parses** `model` / `reasoning-effort` / `verbosity` generically (`/substrate/cli-guard/pkg/fleetconfig/fleetconfig.go`, `applyAgentProp`), so putting them on the `agent claude` node needs **no** grammar change. ## Scope for this issue (ward-only, fully landable) 1. **Wire claude model + effort**, mirroring codex end to end: - Add `model` (and `reasoning-effort` if it maps cleanly to a claude-native knob) to the `agent claude` node in `ward-kdl.fleet.kdl`, then `make sync-fleet-assets`. - `WARD_CLAUDE_MODEL` / `WARD_CLAUDE_REASONING_EFFORT` env overrides in `container_bootstrap.go`. - Thread `ClaudeModel` / `ClaudeEffort` through `bootstrapEnv` -> `agentsapi_ctx.go` -> `RunCtx`. - `claude.go` `LaunchArgv` appends `--model <model>` and applies effort by whatever claude-native mechanism is simplest (a flag if one exists, else an env line the harness reads). Empty model means omit the flag, so today's behavior is unchanged. 2. **`--config <dotted.path>=<value>` repeatable flag on `ward agent`.** Easiest impl: parse `agent.<name>.<key>=<value>` and translate each into the matching `WARD_<NAME>_<KEY>` container env line, so it rides the exact override mechanism codex/claude already read (e.g. `agent.claude.model` -> `WARD_CLAUDE_MODEL`, `agent.claude.effort` -> `WARD_CLAUDE_REASONING_EFFORT`). Unknown keys fail loud with the list of known keys. Add `--config` to the broker's engineer/advisor flag allowlist in `agent_dispatch_broker.go` (`validateDispatchBrokerArgv`) so an in-container dispatch can forward it. 3. **Startup echo of the resolved model-context config block.** One clear block in the container log at startup listing, for the launched agent: agent name, model, effort/reasoning, endpoint, context-level (`WARD_CONTEXT_LEVEL`). Today only codex logs its own config line inside `ComposeConfig`; generalize so claude (and every agent) echoes its resolved config regardless of whether it writes a config file. ## Out of scope (dependent follow-ups) Per-role config in the fleet kdl (different model/effort per **role** - engineer vs advisor vs director) needs the cli-guard `Role` grammar extended: it currently parses only `guardfiles` and hard-errors on any other child in `parseRole`. Tracked in cli-guard (grammar) and a ward consumer issue that pins the new cli-guard tag. **Do not** attempt the cross-repo grammar change in this run - keep this one ward-only and landable. ## Precedence Highest wins: `--config` CLI flag > `WARD_*` env > per-agent fleet default. (Per-role slots between env and agent-default once the follow-up lands.) ## Acceptance - `warded engineer #N --config agent.claude.model=sonnet` launches claude with that model, and the startup block shows it. - No `--config` and no env means today's behavior, `--model` flag omitted. - `ward exec test` and `ward exec vet` green, `pre-commit run --all-files` clean, `agent_adapter_test.go` two-way pin still holds after the fleet edit. - `docs/FEATURES.md` and the relevant `docs/agent*.md` updated in the same commit.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-616 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-06T16:41:47Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

run seed context — what this run is carrying (ward#609)
  • Resolved: coilyco-flight-deck/ward#616 · branch issue-616 · driver claude · workflow direct-main
  • Run: engineer-claude-ward-616 · ward v0.413.0 · dispatched 2026-07-06T16:41:47Z
  • Comment thread: 0 included in the pre-flight read, 0 stripped (ward's own automated comments).

Issue body as seeded:

## What Kai wants

Two things on every `warded` / `ward agent` run:

1. **Echo the full model-context config block at container startup** so a run's resolved harness config (agent, model, effort/reasoning, endpoint, context-level) is visible in the container log stream, not silent.
2. **Override those configs from the CLI**, repeatable, dotted-path shaped:

` ` `
ward agent engineer --config agent.claude.model=sonnet --config agent.claude.effort=medium
` ` `

Kai's steer: **fit to the easiest technical impl, not perfect UX.**

## Current state (the pattern to mirror)

`codex` already carries this shape end to end and is the template:

- Fleet manifest node `agent codex { model gpt-5.4; reasoning-effort medium; verbosity low }` in `.ward/ward-kdl/ward-kdl.fleet.kdl` (mirrored to `cmd/ward/fleetassets/fleet.generated.kdl` by `make sync-fleet-assets`, drift-guarded by `fleetassets_test.go`).
- `container_bootstrap.go` reads each with an env override: `CodexModel: envOr("WARD_CODEX_MODEL", codex.Model)` (plus `WARD_CODEX_REASONING_EFFORT`, `WARD_CODEX_VERBOSITY`).
- `bootstrapEnv` -> `agentsapi_ctx.go` -> `RunCtx.CodexModel/CodexEffort/CodexVerbosity`.
- `internal/agents/codex/config.go` writes the config and logs it: `rc.Log("wrote codex config ... model %s / effort %s / verbosity %s ...")`.

**claude has none of this.** `internal/agents/claude/claude.go` `record` sets no `Model`, and `LaunchArgv` launches bare `claude` with no `--model` / effort. The fleet `Agent` grammar in cli-guard **already parses** `model` / `reasoning-effort` / `verbosity` generically (`/substrate/cli-guard/pkg/fleetconfig/fleetconfig.go`, `applyAgentProp`), so putting them on the `agent claude` node needs **no** grammar change.

## Scope for this issue (ward-only, fully landable)

1. **Wire claude model + effort**, mirroring codex end to end:
   - Add `model` (and `reasoning-effort` if it maps cleanly to a claude-native knob) to the `agent claude` node in `ward-kdl.fleet.kdl`, then `make sync-fleet

… (truncated to 2000 chars; full body is on this issue)

Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.413.0).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-616` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-06T16:41:47Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <details><summary>run seed context — what this run is carrying (ward#609)</summary> - **Resolved:** `coilyco-flight-deck/ward#616` · branch `issue-616` · driver `claude` · workflow `direct-main` - **Run:** `engineer-claude-ward-616` · ward `v0.413.0` · dispatched `2026-07-06T16:41:47Z` - **Comment thread:** 0 included in the pre-flight read, 0 stripped (ward's own automated comments). **Issue body as seeded:** ``` ## What Kai wants Two things on every `warded` / `ward agent` run: 1. **Echo the full model-context config block at container startup** so a run's resolved harness config (agent, model, effort/reasoning, endpoint, context-level) is visible in the container log stream, not silent. 2. **Override those configs from the CLI**, repeatable, dotted-path shaped: ` ` ` ward agent engineer --config agent.claude.model=sonnet --config agent.claude.effort=medium ` ` ` Kai's steer: **fit to the easiest technical impl, not perfect UX.** ## Current state (the pattern to mirror) `codex` already carries this shape end to end and is the template: - Fleet manifest node `agent codex { model gpt-5.4; reasoning-effort medium; verbosity low }` in `.ward/ward-kdl/ward-kdl.fleet.kdl` (mirrored to `cmd/ward/fleetassets/fleet.generated.kdl` by `make sync-fleet-assets`, drift-guarded by `fleetassets_test.go`). - `container_bootstrap.go` reads each with an env override: `CodexModel: envOr("WARD_CODEX_MODEL", codex.Model)` (plus `WARD_CODEX_REASONING_EFFORT`, `WARD_CODEX_VERBOSITY`). - `bootstrapEnv` -> `agentsapi_ctx.go` -> `RunCtx.CodexModel/CodexEffort/CodexVerbosity`. - `internal/agents/codex/config.go` writes the config and logs it: `rc.Log("wrote codex config ... model %s / effort %s / verbosity %s ...")`. **claude has none of this.** `internal/agents/claude/claude.go` `record` sets no `Model`, and `LaunchArgv` launches bare `claude` with no `--model` / effort. The fleet `Agent` grammar in cli-guard **already parses** `model` / `reasoning-effort` / `verbosity` generically (`/substrate/cli-guard/pkg/fleetconfig/fleetconfig.go`, `applyAgentProp`), so putting them on the `agent claude` node needs **no** grammar change. ## Scope for this issue (ward-only, fully landable) 1. **Wire claude model + effort**, mirroring codex end to end: - Add `model` (and `reasoning-effort` if it maps cleanly to a claude-native knob) to the `agent claude` node in `ward-kdl.fleet.kdl`, then `make sync-fleet ``` … (truncated to 2000 chars; full body is on this issue) Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.413.0). </details> <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - claude model+effort wired end to end, repeatable --config overrides, and a startup config echo for every agent; landed on main.

This one was mostly a tracing exercise, and the codebase made it pleasant: codex already carried the exact shape (fleet node -> WARD_* env -> RunCtx -> config compose), so wiring claude was a matter of following the same rails and the two-way fleet pin + registry contract test told me immediately when I'd kept the mirrors honest. The one real design tension was the fleet claude node: the issue said 'add model to the claude node' but acceptance demanded today's bare launch stay unchanged, so I went with explicit empty-string model "" / reasoning-effort "" defaults (documented as override-only) rather than a concrete default. Effort has no native claude flag, so I threaded + echoed it but deliberately did not invent an argv knob - honest over cute.

The part that actually fought back was docs, not code: agent-flags.md was already 3974/4000 chars, so any mention blew the layout cap. I split the detail into a dedicated agent-config-overrides.md and trimmed a couple of adjacent verbose sentences to fit the pointer. The 2-line comment-block cap also caught several of my longer explanatory comments.

Confidence is high: ward exec test + vet green, pre-commit clean, the --model path is pinned by the registry contract test across all three launch postures, and the --config translation + broker-forward are unit-tested. The one thing I could NOT exercise in-container is the live startup echo against a real claude launch (no docker-in-container here) - it's a straightforward Fprintf over a unit-tested projection, but a real run is the final proof. Per-role model/effort (engineer vs advisor vs director) stays the tracked cli-guard-grammar follow-up, correctly out of scope here.

WARD-OUTCOME: done - claude model+effort wired end to end, repeatable `--config` overrides, and a startup config echo for every agent; landed on main. This one was mostly a tracing exercise, and the codebase made it pleasant: codex already carried the exact shape (fleet node -> WARD_* env -> RunCtx -> config compose), so wiring claude was a matter of following the same rails and the two-way fleet pin + registry contract test told me immediately when I'd kept the mirrors honest. The one real design tension was the fleet claude node: the issue said 'add model to the claude node' but acceptance demanded today's bare launch stay unchanged, so I went with explicit empty-string `model ""` / `reasoning-effort ""` defaults (documented as override-only) rather than a concrete default. Effort has no native claude flag, so I threaded + echoed it but deliberately did not invent an argv knob - honest over cute. The part that actually fought back was docs, not code: agent-flags.md was already 3974/4000 chars, so any mention blew the layout cap. I split the detail into a dedicated agent-config-overrides.md and trimmed a couple of adjacent verbose sentences to fit the pointer. The 2-line comment-block cap also caught several of my longer explanatory comments. Confidence is high: ward exec test + vet green, pre-commit clean, the --model path is pinned by the registry contract test across all three launch postures, and the --config translation + broker-forward are unit-tested. The one thing I could NOT exercise in-container is the live startup echo against a real claude launch (no docker-in-container here) - it's a straightforward Fprintf over a unit-tested projection, but a real run is the final proof. Per-role model/effort (engineer vs advisor vs director) stays the tracked cli-guard-grammar follow-up, correctly out of scope here.
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#616
No description provided.