Files
VolcanicAgenticCore/docs/superpowers/plans/2026-09-25-volcanic-agentic-core.md
2026-09-25 11:55:12 +00:00

22 KiB
Raw Blame History

VolcanicAgenticCore Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Build a headless, extensible C# agent harness (a pi clone) with pluggable LLM providers, an extension API, persistent sessions, and a Claude Code subscription provider extension.

Architecture: Two core projects (Abstractions contracts, Core runtime organised as vertical feature slices) plus provider extensions that use only the public extension API. One AgentSession per conversation is the facade a UI consumes; everything the UI needs is an event stream plus a handful of commands.

Tech Stack: .NET 10, C# 14, System.Text.Json, Microsoft.Extensions.DependencyInjection/Logging abstractions, xunit, claude CLI (for the ClaudeCode extension).

Spec: docs/superpowers/specs/2026-09-25-volcanic-agentic-core-design.md


File map

Directory.Build.props
VolcanicAgenticCore.sln
src/Volcanic.AgenticCore.Abstractions/
  Messages/{Message.cs, ContentBlock.cs, Usage.cs, StopReason.cs, MessageJson.cs}
  Providers/{Model.cs, ThinkingLevel.cs, LlmContext.cs, StreamOptions.cs, AssistantMessageEvent.cs,
             AssistantMessageBuilder.cs, IProvider.cs, ProviderRegistration.cs, IModelRegistry.cs}
  Tools/{ITool.cs, Tool.cs, ToolResult.cs, ToolCall.cs, JsonSchema.cs}
  Extensions/{IExtension.cs, IExtensionApi.cs, IExtensionContext.cs, ExtensionEvent.cs, Events/*.cs,
              CommandDefinition.cs, IEventBus.cs, MessageDelivery.cs, ExecResult.cs}
  Sessions/{SessionEntry.cs, IReadOnlySessionManager.cs, ISessionStore.cs}
  Ui/{IUserInterface.cs, NullUserInterface.cs}
  Events/AgentSessionEvent.cs
src/Volcanic.AgenticCore/
  Hosting/{AgenticCoreOptions.cs, ServiceCollectionExtensions.cs, VolcanicPaths.cs, AgentSessionOptions.cs}
  Features/Models/{ModelRegistry.cs, ApiKeyResolver.cs, TranscriptNormalizer.cs, ThinkingLevels.cs}
  Features/Sessions/{SessionManager.cs, FileSessionStore.cs, InMemorySessionStore.cs, SessionContextBuilder.cs}
  Features/Tools/{ToolRegistry.cs, ToolExecutor.cs, ArgumentValidator.cs, OutputTruncation.cs,
                  BuiltIn/{ReadTool.cs, WriteTool.cs, EditTool.cs, BashTool.cs, LsTool.cs, GrepTool.cs, FindTool.cs}}
  Features/Extensions/{ExtensionRunner.cs, ExtensionApi.cs, ExtensionContext.cs, ExtensionLoader.cs,
                       ExtensionLoadContext.cs, EventBus.cs, CombinePolicies.cs, ExtensionRegistration.cs}
  Features/Commands/{CommandRegistry.cs}
  Features/SystemPrompt/{SystemPromptBuilder.cs, DefaultPrompts.cs}
  Features/Compaction/{Compactor.cs, CompactionPrompts.cs, TokenEstimator.cs}
  Features/Agent/{AgentLoop.cs, AgentSession.cs, AgentSessionFactory.cs, PendingMessageQueue.cs, RetryPolicy.cs}
extensions/Volcanic.AgenticCore.Extensions.Anthropic/{AnthropicProviderExtension.cs, AnthropicApi.cs, AnthropicModels.cs, Sse.cs}
extensions/Volcanic.AgenticCore.Extensions.OpenAI/{OpenAIProviderExtension.cs, OpenAICompletionsApi.cs, OpenAIModels.cs}
extensions/Volcanic.AgenticCore.Extensions.ClaudeCode/{ClaudeCodeProviderExtension.cs, ClaudeCodeApi.cs, ClaudeArgs.cs,
    ClaudeProcess.cs, StreamEventMapper.cs, ContextSerializer.cs, ToolCatalog.cs, SessionRegistry.cs,
    ClaudeCodeModels.cs, ClaudeInstallation.cs, ClaudeCodeException.cs, JsonlReader.cs, DoctorCommand.cs}
extensions/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge/Program.cs
samples/Volcanic.AgenticCore.Sample.Headless/Program.cs
tests/Volcanic.AgenticCore.Tests/{Support/FauxProvider.cs, Support/TestHost.cs, ...}
tests/Volcanic.AgenticCore.Extensions.Tests/{Anthropic/*, OpenAI/*, ClaudeCode/*, Fixtures/*}
docs/{architecture.md, extensions.md, providers.md, ui-integration.md, claude-code-provider.md}
README.md

Conventions

  • Directory.Build.props: net10.0, Nullable=enable, ImplicitUsings=enable, LangVersion=latest, TreatWarningsAsErrors=true for src/ and extensions/.
  • JSON: System.Text.Json with JsonSerializerDefaults.Web, camelCase, polymorphism via [JsonPolymorphic(TypeDiscriminatorPropertyName="role")] for messages and "type" for content blocks and session entries.
  • Tests: xunit + Xunit.Assert only (no FluentAssertions). One test class per feature.
  • Commits after each task, explicit paths, message format feat|fix|docs|test(scope): message.

Task 1: Solution scaffold

Files: Directory.Build.props, VolcanicAgenticCore.sln, all .csproj files, tests/Volcanic.AgenticCore.Tests/SmokeTests.cs.

  • Create projects with dotnet new classlib|console|xunit, add references: Core → Abstractions; each extension → Abstractions (ClaudeCode also → Bridge as a ProjectReference with ReferenceOutputAssembly=false and copy of output); Sample → Core + all extensions; tests → Core + extensions.
  • Add packages: Core: Microsoft.Extensions.DependencyInjection.Abstractions, Microsoft.Extensions.Logging.Abstractions, Microsoft.Extensions.Options. Tests: Microsoft.NET.Test.Sdk, xunit, xunit.runner.visualstudio, Microsoft.Extensions.DependencyInjection, Microsoft.Extensions.Logging.
  • SmokeTests.ProjectCompiles asserts typeof(Message) is non-null; run dotnet test; commit chore: scaffold solution.

Task 2: Abstractions — messages and JSON

Files: Messages/*.cs, test MessageJsonTests.cs.

  • Tests: round-trip UserMessage (text + image), AssistantMessage (text, thinking, toolCall, usage, stopReason), ToolResultMessage, CustomMessage through MessageJson.Options; discriminator is role; content discriminator is type with values text|image|thinking|toolCall.
  • Implement records per spec §6.1. ToolCallContent.Arguments is JsonElement. Usage has static Zero and operator +. StopReason serialises as camelCase strings (JsonStringEnumConverter with camelCase policy).
  • Commit feat(abstractions): message model.

Task 3: Abstractions — providers, streaming, tools

Files: Providers/*.cs, Tools/*.cs, tests AssistantMessageBuilderTests.cs, ToolSchemaTests.cs.

  • AssistantMessageBuilder tests: StartText/AppendText/EndText produce TextStart/TextDelta/TextEnd events with correct ContentIndex and a cumulative Partial; StartToolCall/AppendToolCallDelta/EndToolCall parse the accumulated JSON; Finish(StopReason.Stop) yields Done with the final message; Fail("boom", aborted:false) yields Error with StopReason.Error and ErrorMessage.
  • Tool<TArgs> test: a record HelloArgs(string Name, int? Times) produces schema {type:object, properties:{name:{type:string}, times:{type:integer}}, required:[name]} via JsonSchema.For<T>() (reflection: strings, bool, int/long → integer, double/decimal → number, arrays, nested records, nullable → not required, [Description] → description).
  • Implement Model, ModelCost, ThinkingLevel, LlmContext, ToolDescriptor, StreamOptions, CacheRetention, ProviderResponseInfo, AssistantMessageEvent hierarchy, IProvider, ProviderRegistration (with StreamDelegate), IModelRegistry, ITool, Tool<TArgs>, ToolResult, ToolExecutionMode.
  • Commit feat(abstractions): provider and tool contracts.

Task 4: Abstractions — extension API, sessions, UI, session events

Files: Extensions/**, Sessions/*.cs, Ui/*.cs, Events/AgentSessionEvent.cs, test SessionEntryJsonTests.cs.

  • Tests: session entries round-trip with type discriminator (session|message|model_change|thinking_level_change|compaction|custom|custom_message|label|session_info).
  • Implement IExtension, IExtensionApi, IExtensionContext, IExtensionCommandContext, ExtensionEvent base with abstract record ExtensionEvent<TResult>, all event records and result records from spec §6.5 table, CommandDefinition, IEventBus, MessageDelivery, ExecOptions/ExecResult, ToolInfo, CommandInfo, ContextUsage, CompactOptions, SwitchResult, NewSessionOptions, SessionEntry family, ISessionStore, SessionData, SessionSummary, IReadOnlySessionManager, IUserInterface, NullUserInterface, NotifyKind, UiRequestOptions, AgentSessionEvent family.
  • Commit feat(abstractions): extension, session, ui and event contracts.

Task 5: Core — Models slice

Files: Features/Models/*.cs, tests ModelRegistryTests.cs, ApiKeyResolverTests.cs, TranscriptNormalizerTests.cs.

  • Tests: register provider → Find("p/m") returns model; unregister removes; ListModels() ordered by provider then id. ApiKeyResolver: literal, $VAR, ${VAR}, !echo secret, auth.json lookup, <PROVIDER>_API_KEY env fallback, null when nothing. TranscriptNormalizer: drops error/aborted assistant messages; inserts synthetic tool result; converts foreign thinking to text; replaces images for text-only models; converts custom → user.
  • Implement ModelRegistry : IModelRegistry (thread-safe dictionary of ProviderRegistration, StreamAsync(model, ctx, options, ct) dispatch that injects the resolved API key), ApiKeyResolver, TranscriptNormalizer, ThinkingLevels.Supported(model), Clamp(model, level).
  • Commit feat(core): models slice.

Task 6: Core — Sessions slice

Files: Features/Sessions/*.cs, tests SessionManagerTests.cs, FileSessionStoreTests.cs.

  • Tests: SessionManager.Create(cwd, store) appends header; AppendMessage chains ParentId to previous leaf; GetBranch() returns root→leaf; Open(id) reloads and continues appending; BuildContext() after a CompactionEntry yields summary user message + kept messages; FileSessionStore writes <dir>/<scope>/<id>.jsonl with one JSON per line and ListAsync(scope) returns summaries sorted by timestamp desc.
  • Implement per spec §6.6. Entry ids: 8 hex chars from Guid.NewGuid().
  • Commit feat(core): sessions slice.

Task 7: Core — Tools slice

Files: Features/Tools/**, tests ToolRegistryTests.cs, ArgumentValidatorTests.cs, BuiltInToolTests.cs.

  • Tests: registry first-registration-wins and SetActive; validator rejects missing required and wrong primitive types with a readable message; read returns numbered content and truncation details; write creates parent dirs; edit replaces exactly-once text and errors on 0 or >1 matches; bash streams output via IProgress and returns exit code in details, honours timeout; ls marks dirs with /; grep finds regex matches with file:line:text; find matches globs and skips .git.
  • Implement ToolRegistry, ArgumentValidator, OutputTruncation (2000 lines / 50 KB), built-ins per spec §7.4 with PromptSnippet and PromptGuidelines.
  • Commit feat(core): tools slice with built-in tools.

Task 8: Core — Extensions slice

Files: Features/Extensions/*.cs, tests ExtensionRunnerTests.cs, ExtensionLoaderTests.cs.

  • Tests: activation queues tool/command/provider registrations and commits on success, discards on throw (and records error); action methods throw ExtensionNotBoundException before Bind(); EmitAsync(ToolCallEvent) returns first block; handler exception in ToolCallEvent blocks; InputEvent chain transform then handled short-circuits; ContextEvent chain; BeforeAgentStartEvent collects messages and last system prompt; SessionBeforeSwitchEvent first cancel; notify events swallow exceptions and raise ExtensionErrorEvent; EventBus delivers across two extensions; Invalidate() makes old api throw. Loader: builds an extension assembly into a temp dir (compile a tiny extension with Roslyn is too heavy — instead test with the test assembly itself: ExtensionLoader.LoadFromAssembly(typeof(TestExtension).Assembly) discovers public IExtension types) and DiscoverDirectories ignores missing dirs.
  • Implement ExtensionRunner (handler table keyed by event type, ordered), CombinePolicies per event type, ExtensionApi, ExtensionContext, EventBus, ExtensionLoader (+ ExtensionLoadContext : AssemblyLoadContext using AssemblyDependencyResolver, unifying Volcanic.AgenticCore.Abstractions and Microsoft.Extensions.* to the default context), ExtensionRegistration(Name, Func<IServiceProvider, IExtension>).
  • Commit feat(core): extensions slice.

Task 9: Core — Commands, SystemPrompt, Compaction slices

Files: Features/Commands/CommandRegistry.cs, Features/SystemPrompt/*.cs, Features/Compaction/*.cs, tests CommandRegistryTests.cs, SystemPromptBuilderTests.cs, CompactorTests.cs.

  • Tests: command lookup splits at first space and passes raw args; duplicate names get name:2; prompt builder includes tools section, guidelines, AGENTS.md content from cwd and cwd line; compactor ShouldCompact threshold; FindCutPoint never cuts before a tool result; Serialize formats roles; CompactAsync with FauxProvider produces CompactionEntry with FirstKeptEntryId and summary text.
  • Implement per spec §7.2, §7.3.
  • Commit feat(core): commands, system prompt and compaction slices.

Task 10: Core — Agent slice (loop, session facade, factory, hosting)

Files: Features/Agent/*.cs, Hosting/*.cs, tests AgentLoopTests.cs, AgentSessionTests.cs, Support/FauxProvider.cs, Support/TestHost.cs.

  • FauxProvider: registered as provider faux with model faux/echo; Script(params FauxTurn[]) where FauxTurn.Text(".."), FauxTurn.ToolCall("name", args), FauxTurn.Error("msg", retryable), FauxTurn.Length(..); emits events through AssistantMessageBuilder in token-sized chunks; honours ct.
  • Loop tests: single text turn emits AgentStart, TurnStart, MessageStart, MessageUpdate*, MessageEnd, TurnEnd, AgentEnd, AgentSettled; tool round trip executes the tool and makes a second provider call whose context contains the ToolResultMessage; parallel tools end in completion order and results in source order; steering during a tool turn is delivered before the next call; follow-up runs as an extra turn; abort yields StopReason.Aborted; retryable error retries with AutoRetryStart/End; Length fails tool calls; overflow error triggers compaction then retry.
  • Session tests: /cmd args dispatches even while streaming; PromptAsync while streaming without behavior throws; SetModelAsync appends ModelChangeEntry and emits ModelChanged; messages persist and reload through SwitchSessionAsync; SessionStart/SessionShutdown extension events fire; IUserInterface reaches ctx.Ui in a tool.
  • Implement PendingMessageQueue, RetryPolicy (retryable classification: contains 429, 5xx, overloaded, rate limit, stream ended before, ECONNRESET, timeouts; overflow: context_length_exceeded, prompt is too long, maximum context length), AgentLoop, AgentSession, AgentSessionFactory : IAgentSessionFactory, AgenticCoreOptions, AgentSessionOptions, VolcanicPaths, ServiceCollectionExtensions.AddAgenticCore.
  • Commit feat(core): agent loop and session facade.

Task 11: Anthropic provider extension

Files: extensions/Volcanic.AgenticCore.Extensions.Anthropic/*.cs, tests Anthropic/AnthropicApiTests.cs with a fake HttpMessageHandler replaying an SSE fixture.

  • Tests: request body has system top-level, messages with tool_use/tool_result mapping, tools, max_tokens, thinking when reasoning level set, cache_control on system and last user block; SSE fixture with text + thinking + tool_use maps to the correct event sequence and Usage (input, output, cache read/write) and StopReason.ToolUse; HTTP 529 body yields Error event with message containing 529.
  • Implement Sse line parser, AnthropicApi.StreamAsync, AnthropicModels (claude-opus-4-1, claude-sonnet-4-5, claude-haiku-4-5 with costs/context 200k, ThinkingLevelMap), AnthropicProviderExtension : IExtension calling api.RegisterProvider(...) with ApiKey = "$ANTHROPIC_API_KEY".
  • Commit feat(anthropic): messages api provider extension.

Task 12: OpenAI-compatible provider extension

Files: extensions/Volcanic.AgenticCore.Extensions.OpenAI/*.cs, tests OpenAI/OpenAICompletionsApiTests.cs.

  • Tests: request maps system → system role message, tool calls → assistant.tool_calls, tool results → tool role with tool_call_id, stream: true, stream_options.include_usage, reasoning_effort when reasoning; SSE fixture with delta.content, delta.reasoning_content, chunked delta.tool_calls maps to correct events and finish_reason: tool_calls → ToolUse; [DONE] terminates; usage chunk populates Usage.
  • Implement OpenAICompletionsApi, OpenAIModels (gpt-5, gpt-5-mini, gpt-4.1 defaults), OpenAIProviderExtension with Options { BaseUrl, ProviderId, Models } so hosts can instantiate it for OpenRouter/Ollama.
  • Commit feat(openai): chat completions provider extension.

Task 13: ClaudeCode bridge

Files: extensions/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge/Program.cs, tests ClaudeCode/BridgeTests.cs (runs the bridge in-process via Bridge.Run(TextReader, TextWriter, env)).

  • Tests: initialize → result with protocolVersion, capabilities.tools; tools/list → catalogue and creates ready marker; tools/call → error -32001 and creates violation marker; unknown method → -32601; invalid JSON → -32700; missing catalogue env → exit 2.
  • Implement Bridge static class with Run(...) and Main that wires stdin/stdout; 1 MB request cap.
  • Commit feat(claude-code): mcp proposal bridge.

Task 14: ClaudeCode provider extension

Files: extensions/Volcanic.AgenticCore.Extensions.ClaudeCode/*.cs, fixtures copied from pi-claude-code-provider (tests/.../Fixtures/claude-2.1.281-*.jsonl, MIT, attributed in Fixtures/README.md), tests ClaudeCode/*Tests.cs.

  • Tests: ClaudeArgs.Build yields the exact argv from spec §8.3 (order-sensitive); ToolCatalog aliases invalid names to tool_<sha16> and maps mcp__volcanic__<alias> back; ContextSerializer emits header record + one record per message, escapes @, puts cache_control on the last block, and appends image attachment block; JsonlReader splits on LF, strips CR, rejects > 8 MB; StreamEventMapper on claude-2.1.281-stream-tool-ok.jsonl yields Start, TextStart, TextDelta("calling"), TextEnd, ToolCallStart, ToolCallDelta, ToolCallEnd(name "probe", args {path:"probe"}), Done(ToolUse) and ignores everything after the latch; stream-max-tokens → Done(Length); stream-http529 → Error retryable (stream ended before message_stop); stream-context-window-exceeded → error message starting with context_length_exceeded:; stream-prompt-too-long likewise; init validation fails when apiKeySource != "none" or tools mismatch; SessionRegistry.Resolve prefers registered session, else <cwd> in prompt, else newest session for tool-free requests.
  • Implement per spec §8.3: ClaudeInstallation.InspectAsync (path, --version, auth status JSON, --help flag check), ClaudeProcess (spawn with allow-listed env, process group kill via kill -TERM -pid then -KILL, Windows taskkill /T /F, stderr tail 64 KB, idle 5 min / total 30 min timeouts), ClaudeCodeApi.StreamAsync, ClaudeCodeModels, DoctorCommand (/claude-code-doctor), ClaudeCodeProviderExtension (registers provider claude-code, SessionStartEvent → registry, SessionShutdownEvent → remove, MessageEndEvent → prefix overflow errors). Bridge location: Path.Combine(AppContext.BaseDirectory, "Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.dll") run with the dotnet muxer resolved from Environment.ProcessPath or PATH.
  • Commit feat(claude-code): provider extension.

Task 15: Sample host and end-to-end smoke

Files: samples/Volcanic.AgenticCore.Sample.Headless/Program.cs, test EndToEndTests.cs.

  • Sample: builds a host with all extensions, reads --model, --cwd, prompt from args, prints every AgentSessionEvent as one JSON line, exits on AgentSettled. With --faux it registers the FauxProvider (moved to Volcanic.AgenticCore/Testing/FauxProvider.cs so the sample can use it).
  • E2E test: DI host + FauxProvider + built-in bash tool round trip through AgentSession.
  • Commit feat(sample): headless reference host.

Task 16: Documentation

Files: README.md, docs/architecture.md, docs/extensions.md, docs/providers.md, docs/ui-integration.md, docs/claude-code-provider.md.

  • Write each per spec §9 (ui-integration includes a complete Telegram.Bot sketch: session-per-chat map, streaming edit throttle, inline-keyboard IUserInterface, ISessionStore over a DB), extension authoring walkthrough with a full IExtension example (tool + command + tool_call hook), provider authoring walkthrough, ClaudeCode requirements/limits/doctor.
  • Commit docs: user documentation.

Task 17: Verification and push

  • dotnet build -warnaserror clean; dotnet test all green; dotnet run --project samples/... -- --faux "hello" prints events; optional --model claude-code/sonnet smoke if claude auth status reports a subscription.
  • Push main to origin.

Self-review

  • Spec coverage: §6.1–6.8 → Tasks 2–4; §7.1 → Task 10; §7.1.1 → Task 5; §7.2/7.3 → Task 9; §7.4 → Task 7; §7.5 → Task 5; §7.6 → Task 10; §8.1 → 11; §8.2 → 12; §8.3 → 13–14; §9 → 16; §11 → each task's tests.
  • Names used consistently: AgentSession, IAgentSessionFactory, AgentSessionOptions, AgenticCoreOptions, ModelRegistry, ExtensionRunner, SessionManager, ToolRegistry, Compactor, SystemPromptBuilder, AssistantMessageBuilder, FauxProvider, ClaudeArgs, StreamEventMapper, ContextSerializer, ToolCatalog, SessionRegistry, Bridge.