docs: CLAUDE.md with project overview, commands and architecture

This commit is contained in:
EugeneTes
2026-09-26 08:33:46 +00:00
parent 6e89acf348
commit 13b0965d63

View File

@@ -1,3 +1,101 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
<!-- release-notes-config
declined: true
-->
## What this is
VolcanicAgenticCore is a headless, extensible agent harness for .NET 10, ported from
[earendil-works/pi](https://github.com/earendil-works/pi) without the terminal UI. It also ships a C# port of
[chem/pi-claude-code-provider](https://github.com/chem/pi-claude-code-provider), which drives the local `claude`
CLI on a Claude subscription as a model provider. A Telegram bot is the first front end.
When porting behaviour, pi and pi-claude-code-provider are the reference: event names, JSON shapes and invariants
deliberately match them.
## Commands
```bash
dotnet build # whole solution (VolcanicAgenticCore.slnx)
dotnet test # all three test projects; no test calls a paid API
dotnet test tests/Volcanic.AgenticCore.Tests # one project
dotnet test --filter "FullyQualifiedName~AgentLoopTests" # one class
dotnet test --filter "FullyQualifiedName~AgentLoopTests.SomeTest" # one test
# reference host with the scripted faux provider (no keys)
dotnet run --project samples/Volcanic.AgenticCore.Sample.Headless -- --faux "Say hello"
dotnet run --project samples/Volcanic.AgenticCore.Sample.Headless -- --model claude-code/sonnet "List the files here"
# Telegram bot
TELEGRAM_BOT_TOKEN=123:abc TELEGRAM_ALLOWED_USER_IDS=111111111 dotnet run --project hosts/Volcanic.AgenticCore.Telegram
```
`Directory.Build.props` sets `TreatWarningsAsErrors=true`, nullable and implicit usings for every non-test project,
so a new warning fails the build. Test projects turn warnings-as-errors off. Tests use xunit 2.
## Architecture
Read `docs/architecture.md` first. Key points:
- **`src/Volcanic.AgenticCore.Abstractions`** holds the contracts: messages, `Model`/`IProvider`, the streaming
event protocol, `ITool`/`Tool<TArgs>`, `IExtension`/`IExtensionApi` and extension events, session entries/stores,
`IUserInterface`, and `AgentSessionEvent`. Extensions reference **only** this assembly, so it stays small and
stable. Don't let runtime types leak into it.
- **`src/Volcanic.AgenticCore`** is organised as **vertical slices** under `Features/` (`Agent`, `Sessions`, `Models`,
`Tools`, `Extensions`, `Commands`, `SystemPrompt`, `Compaction`). Each slice exposes one entry type
(`AgentSession`, `SessionManager`, `ModelRegistry`, `ToolRegistry`, `ExtensionRunner`, `CommandRegistry`,
`SystemPromptBuilder`, `Compactor`). Slices talk only through those entry types and the Abstractions.
`Hosting/` has `AddAgenticCore(...)` and `AgenticCoreOptions`; `Testing/FauxProvider.cs` is the scripted provider
that tests and `--faux` use.
- **One conversation = one `AgentSession`**, built by `IAgentSessionFactory.CreateAsync`. Extensions are
**instantiated per session** and each session has its own `ModelRegistry`, so provider registrations and extension
fields are per conversation. Process-wide state has to be explicit (e.g. `ClaudeLoginCoordinator.Shared`).
- **Two event systems.** `AgentSessionEvent`s go to hosts via `session.Subscribe` and are invoked synchronously on the
loop. `ExtensionEvent`s go to extensions via `api.On<T>`: they are mutable event args (`Block`, `Cancel`,
`MarkHandled`, set `Text`/`Message`/...), run sequentially in load order, and some short-circuit.
- **Loop invariants** (from pi): providers never throw, they emit `Error` events. An error or abort ends the run.
Tool preparation runs in source order, execution runs in parallel by default, results are appended in source order.
Failed assistant messages are persisted but dropped from model context. Context overflow triggers one compaction
and a retry.
- **Persistence**: a session is a tree of JSONL entries (`id`/`parentId` + leaf pointer) under
`~/.volcanic/agent/sessions/<scope>/` (`VOLCANIC_AGENT_DIR` overrides the root). Compaction entries replace older
messages when context is built.
- **System prompt** pulls project context from the working directory: `AGENTS.md`, or `CLAUDE.md` if there is no
`AGENTS.md` (never both), plus `PERSONA.md` and `MEMORY.md`. File names match case-insensitively.
- **Extension discovery**: host code, then `<cwd>/.volcanic/settings.json` / `.volcanic/extensions/*.dll`, then the same
under `~/.volcanic/agent`. Earlier sources win name collisions. Each assembly loads in its own
`AssemblyLoadContext` and shares `Volcanic.AgenticCore.*`, `Microsoft.Extensions.*` and `System.*` with the host.
### Extensions (`extensions/`)
Provider extensions built on the public API only: `Anthropic` (Messages API), `OpenAI` (chat-completions compatible),
and `ClaudeCode` + `ClaudeCode.Bridge`. The Claude Code provider is the most intricate piece (`docs/claude-code-provider.md`):
- Every model call spawns a fresh `claude -p` with built-in tools off and replays the whole transcript as stream-json.
Core tools are exposed as MCP tools `mcp__volcanic__<alias>` through the Bridge, a tiny MCP server that **never
executes** tools. On `tool_use` the provider kills the process and returns `Done(ToolUse)`, and the core runs the tool.
- `system/init` is validated strictly (fail closed). The extension refuses to run if `ANTHROPIC_API_KEY` or a similar
env var is set, because it is subscription-only.
- Session expiry → `ClaudeLoginCoordinator` runs one login flow at a time through `ClaudeCodeProviderOptions.LoginUi`,
then replays the paused request exactly once. `/claude-code-doctor` and `/claude-code-login` are its commands.
- Tests replay recorded CLI output from `tests/Volcanic.AgenticCore.Extensions.Tests/ClaudeCode/Fixtures/*.jsonl`.
### Telegram host (`hosts/Volcanic.AgenticCore.Telegram`)
Long polling (`Polling/`, with `ReconnectPolicy` backoff). Each private chat gets its own `AgentSession`
(`Chats/ChatRegistry`), with workspace `<WorkspaceRoot>/<chatId>` and session scope `telegram-<chatId>`. Access is
limited by `AllowedUserIds`. The first allowed id is the "owner": `OwnerLoginChannel` sends Claude Code re-login
prompts to that chat. It resolves lazily to break the DI cycle ChatRegistry → session factory → extension → ChatRegistry.
Answers go out complete, not streamed (`Rendering/`). Tests drive the host through `Support/FakeBotClient` and `ChatHost`.
## Conventions
- Commits follow Conventional Commits scoped by area: `feat(claude-code): ...`, `fix(telegram): ...`, `docs(spec): ...`,
`test(...)`, `chore: ...`.
- Design specs and implementation plans live in `docs/superpowers/specs/` and `docs/superpowers/plans/`. Update the
relevant spec and the user docs in `docs/` when behaviour changes.
- By design, the port leaves out pi's terminal UI, themes, keybindings, skills/prompt templates, OAuth logins and
session-tree navigation commands.