feat: Claude Code context injection (UserPromptSubmit hook)

Closes the asymmetry the user surfaced: before this, Claude Code
captured every turn (Stop hook) but retrieval only happened when
Claude chose to call atocore_context (opt-in MCP tool). OpenClaw had
both sides covered after 7I; Claude Code did not.

Now symmetric. Every Claude Code prompt is auto-sent to
/context/build and the returned pack is prepended via
hookSpecificOutput.additionalContext — same as what OpenClaw's
before_agent_start hook now does.

- deploy/hooks/inject_context.py — UserPromptSubmit hook. Fail-open
  (always exit 0). Skips short/XML prompts. 5s timeout. Project
  inference mirrors capture_stop.py cwd→slug table. Kill switch:
  ATOCORE_CONTEXT_DISABLED=1.
- ~/.claude/settings.json registered the hook (local config, not
  committed; copy-paste snippet in docs/capture-surfaces.md).
- Removed /wiki/capture from topnav. Endpoint still exists but the
  page is now labeled "fallback only" with a warning banner. The
  sanctioned surfaces are Claude Code + OpenClaw; manual paste is
  explicitly not the design.
- docs/capture-surfaces.md — scope statement: two surfaces, nothing
  else. Anthropic API polling explicitly prohibited.

Tests: +8 for inject_context.py (exit 0 on all failure modes, kill
switch, short prompt filter, XML filter, bad stdin, mock-server
success shape, project inference from cwd). Updated 2 wiki tests
for the topnav change. 450 → 459.

Verified live with real AtoCore: injected 2979 chars of atocore
project context on a cwd-matched prompt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 12:01:41 -04:00
parent 6e43cc7383
commit 9c91d778d9
5 changed files with 448 additions and 16 deletions

45
docs/capture-surfaces.md Normal file
View File

@@ -0,0 +1,45 @@
# AtoCore — sanctioned capture surfaces
**Scope statement**: AtoCore captures conversations from **two surfaces only**. Everything else is intentionally out of scope.
| Surface | Hooks | Status |
|---|---|---|
| **Claude Code** (local CLI) | `Stop` (capture) + `UserPromptSubmit` (context injection) | both installed |
| **OpenClaw** (agent framework on T420) | `before_agent_start` (context injection) + `llm_output` (capture) | both installed (v0.2.0 plugin, Phase 7I) |
Both surfaces are **symmetric** — push (capture) and pull (context injection on prompt submit) — so AtoCore learns from every turn AND every turn is grounded in what AtoCore already knows.
## Why these two?
- **Stable hook APIs.** Claude Code exposes `Stop` and `UserPromptSubmit` lifecycle hooks with documented JSON contracts. OpenClaw exposes `before_agent_start` and `llm_output`. Both run locally where we control the process.
- **Passive from the user's perspective.** No paste, no manual capture command, no "remember this" prompt. You just use the tool and AtoCore absorbs everything durable.
- **Failure is graceful.** If AtoCore is down, hooks exit 0 with no output — the user's turn proceeds uninterrupted.
## Why not Claude Desktop / Claude.ai web / Claude mobile / ChatGPT / …?
- Claude Desktop has MCP but no `Stop`-equivalent hook for auto-capture; auto-capture would require system-prompt coercion ("call atocore_remember every turn"), which is fragile.
- Claude.ai web has no hook surface — would need a browser extension (real project, not shipped).
- Claude mobile app has neither hooks nor MCP — nothing to wire into.
- ChatGPT etc. — same as above.
**Anthropic API log polling is explicitly prohibited.**
If you find yourself wanting to capture from one of these, the real answer is: use Claude Code or OpenClaw for the work that matters. Don't paste chat transcripts into AtoCore — that contradicts the whole design principle of passive capture.
A `/wiki/capture` fallback form still exists (the endpoint `/interactions` is public) but it is **not promoted in the UI** and is documented as a last-resort escape hatch. If you're reaching for it, something is wrong with your workflow, not with AtoCore.
## Hook files
- `deploy/hooks/capture_stop.py` — Claude Code Stop → POSTs `/interactions`
- `deploy/hooks/inject_context.py` — Claude Code UserPromptSubmit → POSTs `/context/build`, returns pack via `hookSpecificOutput.additionalContext`
- `openclaw-plugins/atocore-capture/index.js` — OpenClaw plugin v0.2.0: capture + context injection
Both Claude Code hooks share a `_infer_project` table mapping cwd to project slug. Keep them in sync when adding a new project path.
## Kill switches
- `ATOCORE_CAPTURE_DISABLED=1` → skip Stop capture
- `ATOCORE_CONTEXT_DISABLED=1` → skip UserPromptSubmit injection
- OpenClaw plugin config `injectContext: false` → skip context injection (capture still fires)
All three are documented in the respective hook/plugin files.