refactor slash command onto shared client + llm-client-integration doc

Codex's review caught that the Claude Code slash command shipped in
Session 2 was a parallel reimplementation of routing logic the
existing scripts/atocore_client.py already had. That client was
introduced via the codex/port-atocore-ops-client merge and is
already a comprehensive operator client (auto-context,
detect-project, refresh-project, project-state, audit-query, etc.).
The slash command should have been a thin wrapper from the start.

This commit fixes the shape without expanding scope.

.claude/commands/atocore-context.md
-----------------------------------
Rewritten as a thin Claude Code-specific frontend that shells out
to the shared client:

- explicit project hint -> calls `python scripts/atocore_client.py
  context-build "<prompt>" "<project>"`
- no explicit hint -> calls `python scripts/atocore_client.py
  auto-context "<prompt>"` which runs the client's detect-project
  routing first and falls through to context-build with the match

Inherits the client's stable behaviour for free:
- ATOCORE_BASE_URL env var (default http://dalidou:8100)
- fail-open on network errors via ATOCORE_FAIL_OPEN
- consistent JSON output shape
- the same project alias matching the OpenClaw helper uses

Removes the speculative `--capture` capture path that was in the
original draft. Capture/extract/queue/promote/reject are
intentionally NOT in the shared client yet (memory-review
workflow not exercised in real use), so the slash command can't
expose them either.

docs/architecture/llm-client-integration.md
-------------------------------------------
New planning doc that defines the layering rule for AtoCore's
relationship with LLM client contexts:

Three layers:
1. AtoCore HTTP API (universal, src/atocore/api/routes.py)
2. Shared operator client (scripts/atocore_client.py) — the
   canonical Python backbone for stable AtoCore operations
3. Per-agent thin frontends (Claude Code slash command,
   OpenClaw helper, future Codex skill, future MCP server)
   that shell out to the shared client

Three non-negotiable rules:
- every per-agent frontend is a thin wrapper (translate the
  agent's command format and render the JSON; nothing else)
- the shared client never duplicates the API (it composes
  endpoints; new logic goes in the API first)
- the shared client only exposes stable operations (subcommands
  land only after the API has been exercised in a real workflow)

Doc covers:
- the full table of subcommands currently in scope (project
  lifecycle, ingestion, project-state, retrieval, context build,
  audit-query, debug-context, health/stats)
- the three deferred families with rationale: memory review
  queue (workflow not exercised), backup admin (fail-open
  default would hide errors), engineering layer entities (V1
  not yet implemented)
- the integration recipe for new agent platforms
- explicit acknowledgement that the OpenClaw helper currently
  duplicates routing logic and that the refactor to the shared
  client is a queued cross-repo follow-up
- how the layering connects to phase 8 (OpenClaw) and phase 11
  (multi-model)
- versioning and stability rules for the shared client surface
- open follow-ups: OpenClaw refactor, memory-review subcommands
  when ready, optional backup admin subcommands, engineering
  entity subcommands during V1 implementation

master-plan-status.md updated
-----------------------------
- New "LLM Client Integration" subsection that points to the
  layering doc and explicitly notes the deferral of memory-review
  and engineering-entity subcommands
- Frames the layering as sitting between phase 8 and phase 11

Scope is intentionally narrow per codex's framing: promote the
existing client to canonical status, refactor the slash command
to use it, document the layering. No new client subcommands
added in this commit. The OpenClaw helper refactor is a
separate cross-repo follow-up. Memory-review and engineering-
entity work stay deferred.

Full suite: 160 passing, no behavior changes.
This commit is contained in:
2026-04-07 07:22:54 -04:00
parent d6ce6128cf
commit 78d4e979e5
3 changed files with 426 additions and 86 deletions

View File

@@ -3,121 +3,122 @@ description: Pull a context pack from the live AtoCore service for the current p
argument-hint: <prompt text> [project-id]
---
You are about to enrich a user prompt with context from the live AtoCore
service. This is the daily-use entry point for AtoCore from inside Claude
Code.
You are about to enrich a user prompt with context from the live
AtoCore service. This is the daily-use entry point for AtoCore from
inside Claude Code.
The work happens via the **shared AtoCore operator client** at
`scripts/atocore_client.py`. That client is the canonical Python
backbone for stable AtoCore operations and is meant to be reused by
every LLM client (OpenClaw helper, future Codex skill, etc.) — see
`docs/architecture/llm-client-integration.md` for the layering. This
slash command is a thin Claude Code-specific frontend on top of it.
## Step 1 — parse the arguments
The user invoked `/atocore-context` with the following arguments:
The user invoked `/atocore-context` with:
```
$ARGUMENTS
```
Treat the **entire argument string** as the prompt text by default. If the
last whitespace-separated token looks like a registered project id (matches
one of `atocore`, `p04-gigabit`, `p04`, `p05-interferometer`, `p05`,
`p06-polisher`, `p06`, or any case-insensitive variant), treat it as the
project hint and use the rest as the prompt text. Otherwise, leave the
project hint empty.
Treat the **entire argument string** as the prompt by default. If the
last whitespace-separated token looks like a registered project id or
alias (`atocore`, `p04`, `p04-gigabit`, `gigabit`, `p05`,
`p05-interferometer`, `interferometer`, `p06`, `p06-polisher`,
`polisher`, or any case-insensitive variant), pull it off and treat
it as an explicit project hint. The remaining tokens become the
prompt. Otherwise leave the project hint empty and the client will
try to auto-detect one from the prompt itself.
## Step 2 — call the AtoCore /context/build endpoint
## Step 2 — call the shared client
Use the Bash tool to call AtoCore. The default endpoint is the live
Dalidou instance. Read `ATOCORE_API_BASE` from the environment if set,
otherwise default to `http://dalidou:3000` (the gitea host) — wait,
no, AtoCore lives on a different port. Default to `http://dalidou:8100`
which is the AtoCore service port from `pyproject.toml` and `config.py`.
Use the Bash tool. The client respects `ATOCORE_BASE_URL` (default
`http://dalidou:8100`) and is fail-open by default — if AtoCore is
unreachable it returns a `{"status": "unavailable"}` payload and
exits 0, which is what the daily-use loop wants.
Build the JSON body with `jq -n` so quoting is safe. Run something like:
**If the user passed an explicit project hint**, call `context-build`
directly so AtoCore uses exactly that project:
```bash
ATOCORE_API_BASE="${ATOCORE_API_BASE:-http://dalidou:8100}"
PROMPT_TEXT='<the prompt text from step 1>'
PROJECT_HINT='<the project hint or empty string>'
if [ -n "$PROJECT_HINT" ]; then
BODY=$(jq -n --arg p "$PROMPT_TEXT" --arg proj "$PROJECT_HINT" \
'{prompt:$p, project:$proj}')
else
BODY=$(jq -n --arg p "$PROMPT_TEXT" '{prompt:$p}')
fi
curl -fsS -X POST "$ATOCORE_API_BASE/context/build" \
-H "Content-Type: application/json" \
-d "$BODY"
python scripts/atocore_client.py context-build \
"<the prompt text>" \
"<the project hint>"
```
If `jq` is not available on the host, fall back to a Python one-liner:
**If no explicit project hint**, call `auto-context` which will run
the client's `detect-project` routing first and only call
`context-build` once it has a match:
```bash
python -c "import json,sys; print(json.dumps({'prompt': sys.argv[1], 'project': sys.argv[2]} if sys.argv[2] else {'prompt': sys.argv[1]}))" "$PROMPT_TEXT" "$PROJECT_HINT"
python scripts/atocore_client.py auto-context "<the prompt text>"
```
In both cases the response is the JSON payload from `/context/build`
(or, for the `auto-context` no-match case, a small
`{"status": "no_project_match"}` envelope).
## Step 3 — present the context pack to the user
The response is JSON with at least these fields:
`formatted_context`, `chunks_used`, `total_chars`, `budget`,
`budget_remaining`, `duration_ms`, and a `chunks` array.
The successful response contains at least:
Print the response in a readable summary:
- `formatted_context` — the assembled context block AtoCore would
feed an LLM
- `chunks_used`, `total_chars`, `budget`, `budget_remaining`,
`duration_ms`
- `chunks` — array of source documents that contributed, each with
`source_file`, `heading_path`, `score`
1. Print a one-line stats banner: `chunks=N, chars=X/budget, duration=Yms`
2. Print the `formatted_context` block verbatim inside a fenced text
code block so the user can read what AtoCore would feed an LLM
3. Print the `chunks` array as a small bulleted list with `source_file`,
Render in this order:
1. A one-line stats banner: `chunks=N, chars=X/budget, duration=Yms`
2. The `formatted_context` block verbatim inside a fenced text code
block so the user can read what AtoCore would feed an LLM
3. The `chunks` array as a small bullet list with `source_file`,
`heading_path`, and `score` per chunk
If the response is empty (`chunks_used=0`, no project state, no
memories), tell the user explicitly: "AtoCore returned no context for
this prompt — either the corpus does not have relevant information or
the project hint is wrong. Try `/atocore-context <prompt> <project-id>`."
Three special cases:
If the curl call fails:
- Network errortell the user the AtoCore service may be down at
`$ATOCORE_API_BASE` and suggest checking `curl $ATOCORE_API_BASE/health`
- 4xx → print the error body verbatim, the API error message is usually
enough
- 5xx → print the error body and suggest checking the service logs
- **`{"status": "no_project_match"}`** (from `auto-context`)
Tell the user: "AtoCore could not auto-detect a project from the
prompt. Re-run with an explicit project id:
`/atocore-context <prompt> <project-id>` (or call without a hint
to use the corpus-wide context build)."
- **`{"status": "unavailable"}`** (fail-open from the client)
→ Tell the user: "AtoCore is unreachable at `$ATOCORE_BASE_URL`.
Check `python scripts/atocore_client.py health` for diagnostics."
- **Empty `chunks_used: 0` with no project state and no memories**
→ Tell the user: "AtoCore returned no context for this prompt —
either the corpus does not have relevant information for the
detected project or the project hint is wrong. Try a different
hint or a longer prompt."
## Step 4 — capture the interaction (optional, opt-in)
## Step 4 — what about capturing the interaction
If the user has previously asked the assistant to capture interactions
into AtoCore (or if the slash command was invoked with the trailing
literal `--capture` token), also POST the captured exchange to
`/interactions` so the Phase 9 reflection loop sees it. Skip this step
silently otherwise. The capture body is:
Capture (Phase 9 Commit A) and the rest of the reflection loop
(reinforcement, extraction, review queue) are intentionally NOT
exposed by the shared client yet. The contracts are stable but the
workflow ergonomics are not, so the daily-use slash command stays
focused on context retrieval until those review flows have been
exercised in real use. See `docs/architecture/llm-client-integration.md`
for the deferral rationale.
```json
{
"prompt": "<user prompt>",
"response": "",
"response_summary": "",
"project": "<project hint or empty>",
"client": "claude-code-slash",
"session_id": "<a stable id for this Claude Code session>",
"memories_used": ["<from chunks array if available>"],
"chunks_used": ["<chunk_id from chunks array>"],
"context_pack": {"chunks_used": <N>, "total_chars": <X>}
}
```
Note that the response field stays empty here — the LLM hasn't actually
answered yet at the moment the slash command runs. A separate post-turn
hook (not part of this command) would update the same interaction with
the response, OR a follow-up `/atocore-record-response <interaction-id>`
command would do it. For now, leave that as future work.
When capture is added to the shared client, this slash command will
gain a follow-up `/atocore-record-response` companion command that
posts the LLM's response back to the same interaction. That work is
queued.
## Notes for the assistant
- DO NOT invent project ids that aren't in the registry. If the user
passed something that doesn't match, treat it as part of the prompt.
- DO NOT silently fall back to a different endpoint. If `ATOCORE_API_BASE`
is wrong, surface the network error and let the user fix the env var.
- DO NOT hide the formatted context pack from the user. The whole point
of this command is to show what AtoCore would feed an LLM, so the user
can decide if it's relevant.
- The output goes into the user's working context as background — they
may follow up with their actual question, and the AtoCore context pack
acts as informal injected knowledge.
- DO NOT bypass the shared client by calling curl yourself. The
client is the contract between AtoCore and every LLM frontend; if
you find a missing capability, the right fix is to extend the
client, not to work around it.
- DO NOT silently change `ATOCORE_BASE_URL`. If the env var points at
the wrong instance, surface the error so the user can fix it.
- DO NOT hide the formatted context pack from the user. Showing
what AtoCore would feed an LLM is the whole point.
- The output goes into the user's working context as background;
they may follow up with their actual question, and the AtoCore
context pack acts as informal injected knowledge.