Skip to content

Commit c703308

Browse files
authored
feat(idef0): Pillar C — live onboarding agent (daemon + Agent SDK + camera chat) (#169)
## Summary **The live onboarding agent (PRD-038 Pillar C).** Talk to your project in the web chat and a **real local Claude Code session** answers — grounded in the actual repo — while the **map moves as it explains**. Two-tier + graceful: Tier 0 answers offline from `map.json`; Tier 1 upgrades to the live agent when the daemon is running. This PR consolidates the Pillar C **shape + build + evidence** (supersedes the shape-only #168). ## Proven end-to-end (EVID-096) Spawned the daemon, connected a WS client, asked *"what is this project and what is it for?"* — the local CC answered verbatim: *"@forgeplan/web is a tiny zero-install npm CLI that scaffolds a pre-built SvelteKit app into `.forgeplan-web/`, serves a read-only force-directed map of Forgeplan artifacts… `npx @forgeplan/web start`…"* — then **called `show_on_map` → `{zone: z.surfaces}`** and narrated the zone flow. Real model turn, grounded, camera-driven. ## What's in it - **RFC-034 + ADR-010** (active) — the daemon/protocol/camera/chat architecture + the packaging decision (separate optional package + spawn-only subcommand). - **Phase 1** — `camera-bus` seam (the one primitive a chat uses to move the RFC-033 tour camera) + Tier-0 chat (`map-chat`: client-grounded, model-free, offline). - **`agent/`** — NEW separate package `@forgeplan/web-agent` (ADR-010: own deps `@anthropic-ai/claude-agent-sdk` + `ws` + `zod`, never in core): a 127.0.0.1 WebSocket daemon booting a persistent Agent SDK `query()` session in a **read-only** profile (Read/Glob/Grep + `show_on_map`; deny Write/Edit/Bash), with an in-process `createSdkMcpServer` `show_on_map` tool that relays camera frames. - **`bin/commands/onboard-agent.mjs`** — spawn-only subcommand (rule 23: `spawn`s the package, never imports it). - **Tier-1 web wiring** — `agent-client.ts` (read-only WS client) + `chat-store` Tier-1 (stream → assistant bubble; `show_on_map` → camera-bus; degrades to Tier 0 when the daemon is down). ## Invariants - **Rule 22**: the live path is browser↔daemon over `ws://127.0.0.1`; `/api/*` is never involved. - **Rule 23 / ADR-010**: core `bin/` stays `node:*`+citty+siblings (spawn-only); the SDK lives only in the `agent/` package; root `package.json` untouched. - **Read-only**: the agent cannot mutate the workspace. ## Test plan - `npx vitest run src/widgets/map-chat src/widgets/composed-map` → **153/153**. - `npx svelte-check` → **0 errors**. - `node agent/scripts/smoke.mjs` → exit 0 (protocol + read-only profile + bind + `/health` + `{ready}`). - Rule-23 allow-list grep over `bin/` → OK. - **Live end-to-end turn** (above) — daemon + local CC + `show_on_map` (EVID-096, CL3). ## To try it `npx @forgeplan/web onboard-agent` in your project → the web chat detects it (`● live`) → ask away. Refs: PRD-038, RFC-034, ADR-010, EVID-096, RFC-033 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents 630fc38 + f0ffeb7 commit c703308

45 files changed

Lines changed: 12719 additions & 165 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
depth: standard
3+
id: ADR-010
4+
kind: adr
5+
links:
6+
- target: PRD-038
7+
relation: based_on
8+
- target: ADR-003
9+
relation: informs
10+
status: active
11+
title: Agent SDK + onboarding daemon in a separate optional npm package launched by a spawn-only bin/ subcommand
12+
---
13+
14+
## Context
15+
16+
Pillar C of the composed-map onboarding program (PRD-038) adds a **live local
17+
onboarding agent**: the user asks questions in the web chat and a real Claude
18+
Code session answers, driving the map camera. The fixed design (FD-1..FD-7): use
19+
the user's LOCAL Claude Code via the **Claude Agent SDK**
20+
(`@anthropic-ai/claude-agent-sdk`), running in a **localhost daemon-bridge** the
21+
user launches (the SvelteKit server structurally cannot spawn `claude` — rule
22+
22 keeps it a read-only mirror).
23+
24+
The trap (PRD-038 **Q1**): **ADR-003 / rule 23** pin `bin/` to a named
25+
allow-list of exactly `node:*` + `citty`. The Agent SDK is a heavy third-party
26+
dependency with a large transitive tree. It cannot enter `bin/` without a
27+
decision, because `bin/` is what `npx @forgeplan/web` runs **before the user has
28+
installed anything** — the whole point of ADR-003 is that no third-party
29+
resolution happens at `npx` time.
30+
31+
## Decision
32+
33+
**Selected**: Ship the Agent SDK + onboarding daemon as a **separate, optional
34+
npm package** (working name `@forgeplan/web-agent`), launched by a **spawn-only**
35+
`bin/` subcommand.
36+
37+
`bin/forgeplan-web.mjs` gains an `onboard-agent` subcommand that does exactly one
38+
new thing: `child_process.spawn` the separate package's binary (resolved from the
39+
user's environment / `npx @forgeplan/web-agent`) and stream its output. **`bin/`
40+
imports nothing from the agent package** — no `import`, no `require`, only a
41+
`spawn` of an external process. ADR-003's allow-list (`node:*` + `citty` +
42+
relative siblings) is therefore **untouched**: the core `@forgeplan/web` stays
43+
lean and `npx`-fast for the 99% of users who only view the map; the agent is
44+
opt-in and its heavy dependency tree is resolved **only** when a user
45+
deliberately runs the agent.
46+
47+
**Why Selected**: it is the only option that keeps ADR-003's `npx`-latency
48+
guarantee intact while still letting Pillar C "use what already exists" (the
49+
Agent SDK). The spawn-only boundary is the same trust seam rule 22 uses for the
50+
`forgeplan` CLI — a process boundary, not an import.
51+
52+
## Alternatives Considered
53+
54+
| Option | Verdict | Why |
55+
|--------|---------|-----|
56+
| **A — separate optional npm package + spawn-only `bin/` subcommand** | **Chosen** | Core stays lean + `npx`-fast (ADR-003 intact); SDK resolved only on deliberate agent use; process boundary mirrors rule 22's `forgeplan`/`git` spawn seam. |
57+
| B — bundled `dist-agent/` image (esbuild-inline, PRD-030/ADR-005 shape) | Rejected | The image discipline (PRD-030) is for **viewer variants** copied by `init`; it would bloat every install with the SDK + its transitive tree even for users who never run the agent, and an esbuild single-file bundle of the Agent SDK (which itself spawns `claude`) is fragile. |
58+
| C — extend the ADR-003 allow-list to admit the SDK into `bin/` | Rejected | Reintroduces the exact `npx`-time third-party resolution ADR-003 removed — every `init`/`start`/`update` would pay to resolve the SDK before doing its job, for a feature most users never touch. Directly violates ADR-003's invariant I3. |
59+
60+
## Consequences
61+
62+
### Positive
63+
- Core `@forgeplan/web` unchanged in weight + `npx` latency; ADR-003 / rule 23
64+
hold verbatim (verified by the existing bin allow-list grep).
65+
- The agent is strictly **opt-in**: no SDK, no `claude`, no API key for the
66+
view-only user.
67+
- Security is a natural consequence of the process boundary: the daemon is a
68+
separate, user-launched, 127.0.0.1-bound process with a read-only agent
69+
profile — the web never gains a code-execution surface.
70+
71+
### Negative (trade-offs)
72+
- A **second package** to publish + version (`@forgeplan/web-agent`), plus a
73+
documented spawn contract between the `onboard-agent` subcommand and that
74+
package's binary.
75+
- The user must install / `npx` the agent package on first use (mitigated by a
76+
guided prompt from the `onboard-agent` subcommand when the package is absent).
77+
78+
### Risks
79+
- **Version skew** between `@forgeplan/web` and `@forgeplan/web-agent` (mitigate:
80+
the daemon advertises a protocol version in its WebSocket probe; the web
81+
tolerates a missing/older daemon by staying in chat **Tier 0**).
82+
- The spawn-only subcommand must **validate the agent package's presence** and
83+
fail with an actionable install hint, never a raw ENOENT.
84+
85+
## Invariants
86+
87+
- `bin/` imports only `node:*`, `citty`, and relative `bin/` siblings — the
88+
`onboard-agent` subcommand adds **only** a `child_process.spawn`, never an
89+
`import`/`require` of the agent package (rule 23 grep must still pass).
90+
- The daemon binds **127.0.0.1 only** and is launched explicitly by the user.
91+
- The agent runs a **read-only** profile: Read/Glob/Grep + read-only forgeplan
92+
MCP; no Write/Edit/Bash.
93+
- The SvelteKit server (`/api/*`) is never involved in the agent path — the
94+
browser talks to the daemon directly (rule 22 intact).
95+
96+
## Evidence Requirements
97+
98+
- A spawn smoke: `bin onboard-agent` spawns the agent package binary (or emits
99+
the install hint when absent) — exit-code asserted.
100+
- Rule-23 verification grep over `bin/` still reports OK (no new bare-specifier
101+
imports).
102+
- The agent package's SDK options object denies Write/Edit/Bash and binds
103+
localhost only (asserted in the agent package's own tests).
104+
105+
## Related Artifacts
106+
107+
| Artifact | Type | Relation |
108+
|----------|------|----------|
109+
| PRD-038 | PRD | based_on (Pillar C, Q1) |
110+
| ADR-003 | ADR | informs (the bin/ allow-list this preserves) |
111+
| RFC (Pillar C daemon, pending) | RFC | based_on (the RFC that presumes this packaging) |
112+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
depth: standard
3+
id: EVID-096
4+
kind: evidence
5+
last_modified_at: 2026-07-06T15:34:34.875409+00:00
6+
last_modified_by: claude-code/2.1.201
7+
links:
8+
- target: RFC-034
9+
relation: informs
10+
status: active
11+
title: 'Pillar C live agent works end-to-end: daemon + local CC answered grounded + drove show_on_map camera; 153 tests, smoke PASS'
12+
---
13+
14+
## Status
15+
16+
draft
17+
18+
## Summary
19+
20+
Prove-phase checkpoint for **RFC-034** (Pillar C, the live onboarding agent) and its packaging
21+
**ADR-010**. The live agent works **end-to-end**: the localhost daemon boots, a **real local Claude
22+
Code session** (via the Agent SDK) answered a project question **grounded in the actual repo**, and
23+
the model **called the `show_on_map` tool** which relayed a camera frame to the client. Plus 153
24+
web-side tests, `svelte-check` 0, and the daemon smoke — all green.
25+
26+
## Observations (measured, 2026-07-06)
27+
28+
### Live end-to-end turn (the headline — a real model turn, not a mock)
29+
30+
Spawned the daemon: `node agent/bin/agent.mjs --cwd <repo> --port 7461` → printed
31+
`onboard-agent live on ws://127.0.0.1:7461`. A WebSocket client connected, received
32+
`{type:"ready", protocolVersion:1, model:"forgeplan-web-agent (claude-agent-sdk)"}`, and sent
33+
*"What is this project and what is it for? … then use show_on_map to point at the most important
34+
zone."* The daemon's persistent `query()` session streamed a **grounded** answer (verbatim):
35+
36+
> "**@forgeplan/web** is a tiny zero-install npm CLI that scaffolds a pre-built SvelteKit app into a
37+
> project's `.forgeplan-web/` folder, then serves a read-only, force-directed map of that project's
38+
> Forgeplan artifacts… run `npx @forgeplan/web start` and *see* a project's decisions and structure
39+
> as an interactive graph — no install, no write access to the workspace."
40+
41+
— then emitted `{type:"show_on_map", target:{kind:"zone", id:"z.surfaces"}}`, then continued
42+
narrating the zone flow (z.surfaces → z.core → z.ui, z.decisions records why) and offered to walk
43+
deeper. Frames observed: `ready``token`* (streamed) → `show_on_map``token`*`done`. The
44+
answer is factually correct and sourced from the real repo — the read-only agent read the project.
45+
46+
### Automated + smoke (measured)
47+
48+
- `npx vitest run src/widgets/map-chat src/widgets/composed-map`**14 files / 153 tests PASS**
49+
(Tier-0 tier0/chat-store/MapChat + Tier-1 agent-client/chat-store + camera-bus + tour + drill).
50+
- `npx svelte-check`**0 errors** (2 pre-existing a11y warnings on the map `<svg>`).
51+
- `node agent/scripts/smoke.mjs`**exit 0, ALL CHECKS PASS**: protocol round-trip; `buildOptions`
52+
denies Write/Edit/Bash + allows `mcp__onboard__show_on_map`; message-queue generator shape; daemon
53+
binds 127.0.0.1, `GET /health` responds, WS sends `{ready}`.
54+
- Rule-23 allow-list grep over `bin/`**OK** for all `.mjs` (the `onboard-agent` subcommand is
55+
spawn-only; no import of `@forgeplan/web-agent`; root `package.json` untouched).
56+
57+
### Invariants confirmed
58+
59+
- **ADR-010**: the Agent SDK + daemon live in the SEPARATE `@forgeplan/web-agent` package (its own
60+
deps `@anthropic-ai/claude-agent-sdk` ^0.3, `ws`, `zod`; own `node_modules`, gitignored). Core
61+
`bin/` unchanged in weight; the subcommand only `child_process.spawn`s the package.
62+
- **Rule 22**: the live path is browser↔daemon over `ws://127.0.0.1`; the SvelteKit `/api/*` server
63+
is never involved.
64+
- **Read-only**: `allowedTools` = Read/Glob/Grep + `show_on_map`; `disallowedTools` =
65+
Write/Edit/Bash; the model cannot mutate the workspace.
66+
67+
## Known scope boundary
68+
69+
- The daemon's `--port` default is 7431; the live turn above used 7461 (explicit). Port discovery
70+
(fixed-port probe vs a discovery file) is RFC-034 **OQ1**.
71+
- `cancel` (abort mid-stream) and socket-closed-mid-tool-call are marked `TODO` (Phase-4 hardening),
72+
not blockers.
73+
- `show_on_map(node)` precision depends on marketplace **CM-02** (stable node ids); `zone`/`flow`
74+
targeting (demonstrated) is exact.
75+
- `@forgeplan/web-agent` is not yet published to npm — the subcommand's `npx` fallback is verified
76+
against the registry 404 only; re-verify post-publish.
77+
78+
## Structured Fields
79+
80+
verdict: supports
81+
congruence_level: 3
82+
evidence_type: test
83+
84+
## Related Artifacts
85+
86+
- **RFC-034** (`informs`) — the daemon/protocol/camera/chat architecture this proves; activation
87+
gated on this checkpoint (rule 11, R_eff > 0).
88+
- **ADR-010** — the packaging decision (separate optional package + spawn-only subcommand) this
89+
build realises and confirms.
90+
- **RFC-033** — the tour camera the agent drives via the `camera-bus` seam (Phase 1).
91+
- **PRD-038** — parent PRD (Pillar C, FD-1..FD-7).
92+
- **`docs/MAP-PACK-FINDINGS-FOR-MARKETPLACE.md`** — CM-02 (stable node ids) for precise
93+
`show_on_map(node)`.
94+
95+

0 commit comments

Comments
 (0)