Surface/design sessions mount substrate but leave WARD_CONTEXT_REPOS empty, so the agent interrogates the operator instead of reading it #593

Closed
opened 2026-07-04 04:24:25 +00:00 by coilysiren · 4 comments
Owner

What happened

A director read-only surface session (WARD_MODE=claude, WARD_CONTEXT_LEVEL=2) was asked a multi-turn infrastructure design question (gating a tailnet-hosted MCP behind auth). The agent asked the operator a run of low-context questions - "do you have a public IP", "Claude Code or claude.ai", "do you have your own domain", "do you need a Funnel" - whose answers were all sitting in the mounted substrate repos the whole time (/substrate/infrastructure: the caddy/Caddyfile, clusters/kai-server/, external-dns, the live *.coilysiren.me subdomains incl. eco-mcp.coilysiren.me).

The operator flagged it directly: "you are asking a bunch of strangely low context questions... I suspect ward's context load machinery is broken."

Root cause

Not the mount - the substrate repos are present and readable. The gap is that nothing labels them as the session's relevant context:

  • WARD_CONTEXT_REPOS is exported empty (value=[]).
  • The composed context under /opt/ward-context (the AGENTS/doctrine set) names conventions but never says which substrate repos bear on the topic at hand.

So the machinery drops a silent pile on disk and trusts the agent to spelunk. When the agent doesn't, it falls back to interrogating the human - exactly the "'Discoverable in the clone' is a trap" failure the AGENTS doctrine already names. Context loaded, but unlabeled.

Proposed fix

Populate WARD_CONTEXT_REPOS (or an equivalent signal in the composed seed) with the substrate repos relevant to the session, so a design/ops conversation starts knowing infrastructure/deploy/etc are its context and reads them before asking. At minimum, surface the non-empty substrate inventory as an explicit "read these first" pointer in the seed rather than an unlabeled mount.

  • #523 (sparse-mount substrate reference repos to their declared reference surface) - adjacent, but about the mount surface, not the labeling.

Repro

Surface session, env | grep WARD_CONTEXT_REPOS -> empty. Ask any infra design question whose answer lives in /substrate/infrastructure. Observe the agent asking the operator for facts that are in the Caddyfile.

Filed from the surface session where it surfaced.

## What happened A director read-only surface session (`WARD_MODE=claude`, `WARD_CONTEXT_LEVEL=2`) was asked a multi-turn infrastructure design question (gating a tailnet-hosted MCP behind auth). The agent asked the operator a run of low-context questions - "do you have a public IP", "Claude Code or claude.ai", "do you have your own domain", "do you need a Funnel" - whose answers were all sitting in the mounted substrate repos the whole time (`/substrate/infrastructure`: the `caddy/Caddyfile`, `clusters/kai-server/`, external-dns, the live `*.coilysiren.me` subdomains incl. `eco-mcp.coilysiren.me`). The operator flagged it directly: "you are asking a bunch of strangely low context questions... I suspect ward's context load machinery is broken." ## Root cause Not the mount - the substrate repos are present and readable. The gap is that nothing labels them as the session's relevant context: - `WARD_CONTEXT_REPOS` is exported empty (`value=[]`). - The composed context under `/opt/ward-context` (the AGENTS/doctrine set) names conventions but never says which substrate repos bear on the topic at hand. So the machinery drops a silent pile on disk and trusts the agent to spelunk. When the agent doesn't, it falls back to interrogating the human - exactly the "'Discoverable in the clone' is a trap" failure the AGENTS doctrine already names. Context loaded, but unlabeled. ## Proposed fix Populate `WARD_CONTEXT_REPOS` (or an equivalent signal in the composed seed) with the substrate repos relevant to the session, so a design/ops conversation starts knowing infrastructure/deploy/etc are its context and reads them before asking. At minimum, surface the non-empty substrate inventory as an explicit "read these first" pointer in the seed rather than an unlabeled mount. ## Related - #523 (sparse-mount substrate reference repos to their declared reference surface) - adjacent, but about the mount surface, not the labeling. ## Repro Surface session, `env | grep WARD_CONTEXT_REPOS` -> empty. Ask any infra design question whose answer lives in `/substrate/infrastructure`. Observe the agent asking the operator for facts that are in the Caddyfile. Filed from the surface session where it surfaced.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-593 on host KAI-SERVER is carrying this issue (reserved 2026-07-04T04:24:44Z). 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).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-593` on host `KAI-SERVER` is carrying this issue (reserved 2026-07-04T04:24:44Z). 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). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

🔓 Reservation released by ward container reap — container ward (--driver claude) exited without launching the agent (smoke-test death, ward#222/#264), so the hold it took is retracted. A plain ward agent retry no longer needs --force.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation-released --> 🔓 Reservation released by `ward container reap` — container `ward` (`--driver claude`) exited without launching the agent (smoke-test death, ward#222/#264), so the hold it took is retracted. A plain `ward agent` retry no longer needs `--force`. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-593 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-04T04:29:43Z). 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).

The pre-flight judged this issue GO for an unattended run. Its justification:

pre-flight read (GO)

Context to front-load: In the fresh ward clone, before my first edit I will read: (1) the session/container launch code that exports WARD_CONTEXT_REPOS and its siblings (WARD_MODE, WARD_CONTEXT_LEVEL) to learn the variable's intended schema and every consumer of it; (2) the substrate mount machinery — what decides which repos land under /substrate/ and where that inventory lives (including the adjacent sparse-mount surface from ward#523, to avoid colliding with it); (3) the composed-context seed generation for /opt/ward-context, to find the right injection point for a "read these first" section; (4) the WARD_CONTEXT_LEVEL semantics so the fix scopes correctly across surface vs. work sessions; and (5) .ward/ward.yaml plus the repo's test layout so validation routes through ward exec verbs rather than bare tools. I confirm I will read each of these in the clone before editing, not discover them lazily.

The work is squarely ward's: ward composes the container env and the context seed, and the fix is to thread the substrate inventory it already computes into WARD_CONTEXT_REPOS and/or an explicit pointer in the seed. The main unknown is the intended semantics of WARD_CONTEXT_REPOS — whether some existing consumer expects a curated, topic-relevant list rather than the full mount inventory, which would make blind population misleading. The issue itself defuses that risk by explicitly blessing the minimal fix ("at minimum, surface the non-empty substrate inventory as an explicit 'read these first' pointer in the seed"), so a bounded, mergeable change exists even if the env-var semantics turn out to be contested. Per-topic relevance selection is out of scope and I would not attempt it unattended.

GO

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-593` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-04T04:29:43Z). 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). The pre-flight judged this issue **GO** for an unattended run. Its justification: <details><summary>pre-flight read (GO)</summary> **Context to front-load:** In the fresh ward clone, before my first edit I will read: (1) the session/container launch code that exports `WARD_CONTEXT_REPOS` and its siblings (`WARD_MODE`, `WARD_CONTEXT_LEVEL`) to learn the variable's intended schema and every consumer of it; (2) the substrate mount machinery — what decides which repos land under `/substrate/` and where that inventory lives (including the adjacent sparse-mount surface from ward#523, to avoid colliding with it); (3) the composed-context seed generation for `/opt/ward-context`, to find the right injection point for a "read these first" section; (4) the `WARD_CONTEXT_LEVEL` semantics so the fix scopes correctly across surface vs. work sessions; and (5) `.ward/ward.yaml` plus the repo's test layout so validation routes through `ward exec` verbs rather than bare tools. I confirm I will read each of these in the clone before editing, not discover them lazily. The work is squarely ward's: ward composes the container env and the context seed, and the fix is to thread the substrate inventory it already computes into `WARD_CONTEXT_REPOS` and/or an explicit pointer in the seed. The main unknown is the intended semantics of `WARD_CONTEXT_REPOS` — whether some existing consumer expects a curated, topic-relevant list rather than the full mount inventory, which would make blind population misleading. The issue itself defuses that risk by explicitly blessing the minimal fix ("at minimum, surface the non-empty substrate inventory as an explicit 'read these first' pointer in the seed"), so a bounded, mergeable change exists even if the env-var semantics turn out to be contested. Per-topic relevance selection is out of scope and I would not attempt it unattended. GO </details> <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - composed context now ends with a self-labeled read-these-first pointer at the warmed /substrate repos

The fix felt smaller than the diagnosis, which is usually the sign of a good root cause. The tempting reading of the title - "WARD_CONTEXT_REPOS is empty, go fill it" - turned out to be the wrong lever: that key is the read-only catalog.dependsOn set cloned under /workspace and is deliberately deduped against substrate, so stuffing substrate into it would have fought the existing #573/#580 design. The actual gap was labeling, exactly as the issue's "at minimum" line said.

What I liked: pulling each repo's tagline from its own README/AGENTS/FEATURES instead of a hand-maintained map. It rendered cleanly against the live mount on the first try (infrastructure, deploy, lore, agentic-os-kai all gave crisp one-liners), and it can't drift. coilysiren's README opens with an ASCII-art code fence, which is exactly the noise case the fence/badge skipping handles - nice to see it fall through to something sane rather than emit garbage.

What fought back: nothing technical, just the pre-commit suite's style caps - 2-line comment blocks, 90-char comment lines, and a 4000-char doc cap I bumped into three times trimming the new container-substrate.md section. Minor, but it's where the time went.

Confidence is high: unit tests cover the extraction edge cases (fences, badges, heading fallback, AGENTS fallback, non-dir entries), the block is one-sourced across the bash entrypoint and the Go bootstrap via the hidden ward container substrate-inventory command, and I verified both the empty case (0 bytes) and the live render. One honest rough edge I did not chase: I could not exercise the full compose_context path end to end (it needs a real container bootstrap), only the command whose output it appends - so the wiring is verified by inspection and parity with the Go path, not by a live surface session. Worth a follow-up if a surface session ever reports the block missing.

WARD-OUTCOME: done - composed context now ends with a self-labeled read-these-first pointer at the warmed /substrate repos The fix felt smaller than the diagnosis, which is usually the sign of a good root cause. The tempting reading of the title - "WARD_CONTEXT_REPOS is empty, go fill it" - turned out to be the wrong lever: that key is the read-only catalog.dependsOn set cloned under /workspace and is deliberately deduped *against* substrate, so stuffing substrate into it would have fought the existing #573/#580 design. The actual gap was labeling, exactly as the issue's "at minimum" line said. What I liked: pulling each repo's tagline from its own README/AGENTS/FEATURES instead of a hand-maintained map. It rendered cleanly against the live mount on the first try (infrastructure, deploy, lore, agentic-os-kai all gave crisp one-liners), and it can't drift. coilysiren's README opens with an ASCII-art code fence, which is exactly the noise case the fence/badge skipping handles - nice to see it fall through to something sane rather than emit garbage. What fought back: nothing technical, just the pre-commit suite's style caps - 2-line comment blocks, 90-char comment lines, and a 4000-char doc cap I bumped into three times trimming the new container-substrate.md section. Minor, but it's where the time went. Confidence is high: unit tests cover the extraction edge cases (fences, badges, heading fallback, AGENTS fallback, non-dir entries), the block is one-sourced across the bash entrypoint and the Go bootstrap via the hidden `ward container substrate-inventory` command, and I verified both the empty case (0 bytes) and the live render. One honest rough edge I did not chase: I could not exercise the *full* compose_context path end to end (it needs a real container bootstrap), only the command whose output it appends - so the wiring is verified by inspection and parity with the Go path, not by a live surface session. Worth a follow-up if a surface session ever reports the block missing.
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#593
No description provided.