# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What This Is

AgentShore is an RL-based orchestrator that coordinates multiple LLM coding agents (Claude Code, Codex CLI, API-based LLMs) via reinforcement learning. A PPO policy network selects "plays" (22-action head, 19 active plays + 3 reserved, action-space version 13) to progress coding projects via a beads-native epic/story/task graph. AgentShore does not generate code — it decides what to do next and which agent does it.

## Critical: Never Run AgentShore CLI Commands Directly

**Do not invoke any `agentshore` CLI subcommand (`agentshore start`, `agentshore dashboard`, `agentshore report`, etc.) directly from Claude Code.** Running these commands can leave background processes (uvicorn servers, asyncio loops, agent subprocesses) running silently after the tool call completes. Those orphaned processes accumulate API calls and can generate significant unexpected spend. If the user asks to start the dashboard or any other AgentShore service, tell them the exact command to run themselves in their terminal rather than running it here.

**Single exception: the `/monitor_run` skill.** When the user directly invokes `/monitor_run` (and only then), the skill body is authorised to run `agentshore stop --project <DIR>` — and nothing else; `/monitor_run` attaches to an already-running session and never starts one. This carve-out applies only to that named skill, only when the user explicitly invokes it, and only for the command it prescribes — every other AgentShore CLI invocation still falls under the rule above.

This is the same rule as the skill-template direct-usage prohibition below — they cover the two ways an agent can accidentally diverge a live AgentShore from its source tree.

## Critical: Never Edit Installed Skill Templates Directly

**Do not edit `~/.claude/skills/agentshore-*`, `~/.codex/skills/agentshore-*`, or any other deployed copy of a AgentShore skill template directly.** The canonical source is `src/agentshore/skills/templates/` in this repo; the deployed copies are installed there by `uv tool install --reinstall --editable .` followed by `agentshore init --force` (per the documented deploy workflow). Edits to the installed copies are silently overwritten on the next install and never reach the source tree. If you need to change skill behavior, change the file under `src/agentshore/skills/templates/` and let the deploy step propagate it.

## Build & Development Commands

```bash
uv sync --group dev          # Install all dependencies (including dev tools)
uv run agentshore --help        # Run CLI
uv run pytest tests/         # Run full suite (xdist-parallel, ~75s on 8-core)
uv run pytest tests/test_cli.py::test_cli_help -p no:xdist  # Run a focused test
uv run ruff check src/ tests/        # Lint
uv run ruff format src/ tests/       # Format
uv run mypy src/                     # Type check
```

The venv lives at `.venv/` — created automatically by `uv sync`. The CLI entry point is `agentshore = "agentshore.cli:main"` (Click-based).

## Running Tests Efficiently

Per `pyproject.toml`, the default `addopts` runs the suite under `pytest-xdist` with `-n auto --dist=worksteal`, plus branch coverage and a 180s per-test timeout. This drops the full suite from ~20 min serial to ~75s on an 8-core box.

- **Full suite**: `uv run pytest tests/` — do NOT pass `-o addopts=''` (that wipes xdist + coverage + timeout and pushes the run back to 8+ min).
- **Avoid `-o addopts=''`**: This flag silently disables xdist parallelism, coverage enforcement, and the per-test timeout. It should almost never be used. If you think you need it, prefer `-p no:xdist` instead (keeps coverage + timeout). The only defensible use is a single focused test where xdist worker startup costs more than it saves and the coverage gate (80% floor) is unreachable from one test — even then, consider running without it first.
- **Focused single test/file**: `uv run pytest tests/path/to/test.py::test_name` — runs fine with default addopts in most cases. Add `-p no:xdist` if xdist worker startup is slower than the test itself.
- **Debug a flaky parallel-only failure**: `uv run pytest tests/path -p no:xdist` — forces serial execution while keeping coverage + timeout.
- **Never tail-pipe a long-running pytest** (`| tail -N` buffers until EOF, so a healthy run looks hung). Use `-q --tb=line` for compact output, or redirect to a file.

## Builds: the Python build spine

**Any "build" request always means running `uv run python -m scripts.buildkit macos` (with no flags) from the repo root — never just `uv build`.** The shipping artifact is the signed `.app`/`.dmg`/`.pkg` that command produces (dashboard + bd sidecar + Tauri shell + Python wheel, signed with Developer ID and optionally notarized). A bare `uv build` only emits the wheel/sdist and skips every other phase, so it does not constitute a real build. Always run the full build with no flags unless the user explicitly requests a subset; never substitute a different command. (The old `scripts/build-macos.sh` / `build-windows.ps1` shell scripts have been removed — the build entrypoint is the Python spine.)

**The build spine** is `scripts/buildkit/` (`python -m scripts.buildkit <macos|windows|version|verify>`). It owns every phase (clean → dashboard → sidecar → wheel → tauri build → **verify gate** → pkg/Inno → sign → notarize → reveal); shared phases live once in `phases.py`, OS-native packaging in `macos.py`/`windows.py`, and the only remaining PowerShell is the native cert/signtool carve-out `_win_signing.ps1` (invoked by `windows.py`, not an entrypoint). Every build ends with a verification gate (`verify.py`) asserting the `.app` payload contains exactly the expected binaries (no stray/stale ones), the embedded version matches source, and the signature verifies. Windows: `uv run python -m scripts.buildkit windows`. See `docs/design/build-pipeline-unification.md`.

**Version is single-sourced.** `pyproject.toml [project].version` is canonical; the Tauri config, both Cargo manifests, and both `package.json` files are mirrors. To bump the version, edit `pyproject.toml` then run `uv run python -m scripts.buildkit version --write` (CI + a pytest guard fail on drift). Never hand-edit the mirrors.

## Architecture

The system runs as a single asyncio process. The core loop is: observe state → RL policy selects a play → execute play via agent → compute reward → update policy → repeat.

**UI and transport modes, same core**: In solo mode, a Textual TUI renders state. In embedded/headless agent mode, state streams over a Unix domain socket or TCP IPC. Dashboard mode is a browser bridge on top of the same IPC stream. The `StateProvider` protocol (`src/agentshore/state.py`) decouples core from the consumers.

**RL engine** (`src/agentshore/rl/`): Custom PPO policy network in PyTorch. 22-action discrete head (19 active plays + 3 permanently reserved/masked slots, action-space version 13). State vector (246 features, observation version 13) encodes alignment scores, budget, agent states, failure counts, trajectory projections. Policy outputs are masked to prevent invalid plays (e.g., can't review a PR that doesn't exist).

**Plays** (`src/agentshore/plays/`): Each play implements a `Play` protocol with `preconditions()`, `execute()`, and `estimated_cost()`. The RL engine selects the play type; a separate parameter resolver picks which agent/issue/PR. Anti-confirmation bias is a hard invariant for Code Review: the reviewer GitHub identity must differ from the PR author. Run QA validates trunk/default-branch state and is not identity-blocked in the current implementation.

**Agents** (`src/agentshore/agents/`): CLI agents (Claude Code, Codex, Gemini) are asyncio subprocesses. API agents (GPT and other OpenAI-compatible backends) use httpx. The agent manager handles lifecycle, health monitoring, handoff tracking, and context enrichment from session learnings.

**Beads integration**: AgentShore operates on a three-layer architecture. **BEADS** is the canonical project graph (epics → stories → tasks); **GitHub** is the human conversation surface, with each issue/PR mirrored via `external_ref="gh-N"`; **AgentShore SQLite** is the session-scoped RL state (schema version 4). `agentshore init` runs `ensure_bd_installed → bd_init_project → bd_setup_for_agent_types` to wire the layers together. Alignment is tracked as `alignment_delta: float | None` — `None` means beads is not initialised; `0.0` means first tick or no change; a non-zero float is the `global_closure_ratio` delta since the last tick.

**Data** (`src/agentshore/data/`): Single SQLite database per project (aiosqlite, WAL mode). Schema is in `src/agentshore/data/schema.sql` — 22 tables (schema version 4) covering sessions, plays, agents, GitHub issues, pull requests, branch activity, review queue, work claims, dispatch replay, external mutations, scope evidence, policy checkpoints, RL experience, handoffs, trajectory snapshots, human feedback, learnings, archives, review patterns, and worktrees (plus the `schema_info`/`schema_version` meta tables). The version/table-count are pinned by `tests/test_schema_fresh_db.py`.

**Scope validation**: After each skill-backed play, `validate_scope()` enforces issue-inflation limits. Artifact drift is not blocked until AgentShore has reliable beads-native path boundaries; existing drift tables are evidence logs for other consumers.

## Dashboard Development & Demo Mode

To test the dashboard UI without a running AgentShore session, use the built-in client-side demo transport:

```bash
cd dashboard && npm run dev   # run yourself, not via Claude
# Then open: http://localhost:5173/?demo=1&scenario=active
```

Available scenarios: `active`, `empty`, `feedback`, `disconnected`, `stress`. Add `&freeze=1` to stop the game loop for stable inspection.

For a real WebSocket server with mock data (e.g. E2E tests):
```bash
AGENTSHORE_MOCK_PORT=9473 node dashboard/tests/e2e/mockAgentShoreServer.mjs
AGENTSHORE_DASHBOARD_WS_TARGET=ws://localhost:9473 npm run dev
```

The demo transport lives in `dashboard/src/demoTransport.ts`; the mock server in `dashboard/tests/e2e/mockAgentShoreServer.mjs`.

**Never start the dashboard bridge or any AgentShore server process via a tool call** — give the user the command to run themselves (see rule above). `npm run dev` in `dashboard/` is fine to run directly; it's a plain Vite dev server with no API cost.

## Per-Agent GitHub Identities

CLI agents (Claude Code, Codex, Gemini) can be bound to different GitHub identities via the `identities:` block in `agentshore.yaml` and an `identity:` field per agent. The Agent Manager applies the resolved identity (git authorship + `GH_TOKEN`) as a per-subprocess env overlay in `src/agentshore/agents/identity.py:resolve_identity_env`. API-only agents (`api_*`) reject `identity:` at parse time. Tokens load via `gh_token_login`, `gh_token_env`, or AgentShore-managed `gh_token_keychain` services; they never appear in log events. See `docs/identity.md` for the full reference and provisioning recipe. Use `agentshore identity` to verify token resolution and repository access; `agentshore identity --reconfigure` re-runs the wizard against an existing project without resetting the database.

## Design Docs

Comprehensive design documentation lives in `docs/design/`. The PRD cross-links to relevant design docs. Start with `docs/design/HLD.md` for architecture diagrams and the component map — it links to all 13 component design docs.

## Git Commit Signing

Commits in this repo are SSH-signed. If a `git commit` fails with `incorrect passphrase supplied to decrypt private key`, the SSH agent doesn't have the key loaded. Load it (substitute your own key filename for `<your-key>`):

```bash
# macOS — loads from Keychain without prompting for a passphrase
ssh-add --apple-use-keychain ~/.ssh/<your-key>
```

```powershell
# Windows — start the OpenSSH agent service first if it isn't running
Start-Service ssh-agent; ssh-add $env:USERPROFILE\.ssh\<your-key>
```

Run it once per terminal session (or after a reboot) and subsequent signed commits will work normally.

## Key Conventions

- Python 3.12+, `src/` layout, hatch build backend
- `from __future__ import annotations` in every module
- Ruff for linting and formatting (line length 100)
- mypy strict mode
- asyncio for all I/O — no blocking calls in the core loop
- SQLite via aiosqlite (never raw sqlite3)
- structlog → NDJSON for all logging
- YAML for config (`agentshore.yaml`)
- Config is deeply immutable frozen dataclasses; SIGHUP reload swaps the entire instance atomically
