Agent integration
Source: packages/cli/src/agent/, packages/cli/src/commands/agent.ts,
packages/core/src/agents/, skills/beetl/SKILL.md · Spec: §9, §10
Claude Code skill + install
Section titled “Claude Code skill + install”beetl agent install writes into the host project:
-
.claude/skills/beetl/SKILL.md— the contract: search before debugging, file on encounter (new/file-complete), always--actor agent:<tool>:<model>, move through gates, report BTL- IDs to the user. Canonical source ispackages/cli/src/agent/skill.ts; the repo copy atskills/beetl/SKILL.mdis kept in sync by a test. -
.claude/hooks/beetl-stop-nudge.sh+ aStophook merged into.claude/settings.json(existing settings preserved, idempotent). The heuristic: source files changed but no.beetl/sessions/file touched → block once with a reminder to file;stop_hook_activeguards re-entry. The script is versioned (# beetl-hook-version: N, M-07) and reconciled on every install: outdated scripts are rewritten, andautoFile: falseremoves an installed hook (script + settings entry, other hooks kept) rather than merely skipping it.Detection is turn-scoped via a per-
session_idstate file inTMPDIR(v4): it accumulates the source paths already nudged for, so each path nudges once and an unrelated later edit does not re-blame a file the user already dismissed. “Was a session filed this turn?” reads the store by mtime against that file’s own mtime (the previous Stop) — not bygit status— so committing the session you were just told to file does not re-arm the nudge; on the first Stop, where no baseline exists yet, a session inHEADstands in. Porcelain is parsed with-z, since field-splitting the quoted form truncates paths containing spaces and reports a rename as its old path. -
--claude-mdappends a two-line pointer to CLAUDE.md (offered, not forced).
Runner abstraction (spec §9)
Section titled “Runner abstraction (spec §9)”AgentRunner interface with classify(), synthesize(), and diagnose()
(M3). Every outcome is either a result or a well-formed prompt — the none
runner emits the prompt so a human or the host agent can execute it.
- Detection order:
claudeon PATH → claude-cli;codex→ codex-cli; else none.apiis explicit-config only (never auto-selected). - Execution context (H-05/L-07): every local runner subprocess runs with
cwdset to the target store’s root (RunnerExecContext.rootDir) — never the caller’s directory, which--cwdcan point elsewhere. Prompts are delivered via stdin (claude -preads stdin;codex exec -), never argv, so they don’t appear in process listings or hit argument-size limits. ExecRunner(core/src/agents/exec-runner.ts) is the shared prompt→execute→parse base; subclasses provide onlyrun(): claude-cli (claude -p), codex-cli (codex exec,--modelhonored), api (Anthropic Messages API, key fromagent.apiKeyEnv, default modelclaude-fable-5). Exec/fetch are constructor-injectable for tests.- Tolerant JSON parsing (fenced or wrapped); unparseable/empty output maps to
exit code 3 (
RUNNER_FAILURE) with a truncated output sample. - API-mode honesty (M-01, ADR-0007): every diagnose outcome carries
grounding: "repository" | "metadata-only". Theapirunner sees only session metadata plus a file-name listing — no repository investigation — so its result is a candidate hypothesis:beetl diagnose --runner apiemits it (kind: "hypothesis",applied: false) without touching the session; persisting requires the explicit--applyflag, and the update event’s note recordsdiagnosed by api (grounding: metadata-only). Repository-grounded runners auto-apply and record the same provenance note.ANTHROPIC_BASE_URLoverrides the API endpoint (proxies, tests). beetl new --classifyfills missing type/category/surface from the runner, or surfaces the prompt when no runner is available.
Search (M1)
Section titled “Search (M1)”Minisearch over title/symptoms/errorOutput/rootCause/mechanism/files/tags/
category/surface, built on demand from the JSON store. beetl search "<query>", --similar-to <ref>, and automatic similar-on-create in
beetl new (returned in --json as similar).