A continued task (input-required -> new message) produced only status updates, so the SDK answered 'Agent handler did not produce any response events'. The handler now enqueues the resumed task (caller message and question in history, working) as the first event. The guarded SocketsHttpHandler sets UseProxy = false: a proxy would make the connection and skip the ConnectCallback address check.
VolcanicAgenticCore
A headless, extensible agent harness for .NET, modelled on earendil-works/pi. It keeps the parts of pi that make it extensible and provider-agnostic and leaves out the terminal UI, so you can put any front end on top. A Telegram bot ships as the first one.
It also ships a C# port of chem/pi-claude-code-provider:
an extension that drives the locally installed claude CLI on a Claude subscription, so the core can use
Claude Code as a model provider without an API key.
What you get
| Piece | Where | Notes |
|---|---|---|
| Contracts for extensions, providers, tools, sessions, UI | src/Volcanic.AgenticCore.Abstractions |
The only assembly an extension needs to reference |
| Runtime (vertical slices) | src/Volcanic.AgenticCore |
Agent loop, sessions, models, tools, extensions, commands, compaction, system prompt, hosting |
| Anthropic Messages API provider | extensions/Volcanic.AgenticCore.Extensions.Anthropic |
ANTHROPIC_API_KEY |
| OpenAI-compatible provider | extensions/Volcanic.AgenticCore.Extensions.OpenAI |
OpenAI, OpenRouter, Groq, Ollama, llama.cpp, vLLM |
| Claude Code (subscription) provider | extensions/Volcanic.AgenticCore.Extensions.ClaudeCode (+ .Bridge) |
Spawns claude -p, MCP "proposal" bridge |
| Google Calendar (tool + connect commands) | extensions/Volcanic.AgenticCore.Extensions.GoogleCalendar |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET; one account per chat |
| Scheduler (scheduled prompts, watcher scripts) | extensions/Volcanic.AgenticCore.Extensions.Scheduler |
Cronos; per-chat jobs in <agent dir>/scheduler/jobs.json; see docs/telegram-bot.md |
| Telegram bot | hosts/Volcanic.AgenticCore.Telegram |
Chat with the agent from Telegram; long polling, Claude Code, allow-list |
| Reference headless host | samples/Volcanic.AgenticCore.Sample.Headless |
Prompt in, JSON events out |
| Tests | tests/ |
xunit; no test calls a paid API |
Quick start
dotnet build
dotnet test
# run the reference host with the scripted faux provider (no keys needed)
dotnet run --project samples/Volcanic.AgenticCore.Sample.Headless -- --faux "Say hello"
# with a real provider
ANTHROPIC_API_KEY=sk-ant-... dotnet run --project samples/Volcanic.AgenticCore.Sample.Headless -- --model anthropic/claude-opus-5 "List the files here"
# with a Claude subscription through the claude CLI
dotnet run --project samples/Volcanic.AgenticCore.Sample.Headless -- --model claude-code/sonnet "List the files here"
# the Telegram bot (see docs/telegram-bot.md)
TELEGRAM_BOT_TOKEN=123:abc TELEGRAM_ALLOWED_USER_IDS=111111111 dotnet run --project hosts/Volcanic.AgenticCore.Telegram
To run the bot in a container (Coolify or any Docker host): cp .env.example .env, fill in the two required values,
docker compose up -d --build. See docs/docker.md.
Hosting it yourself:
var services = new ServiceCollection();
services.AddAgenticCore(o =>
{
o.DefaultModel = "anthropic/claude-opus-5";
o.AddExtension<AnthropicProviderExtension>();
o.AddExtension<OpenAIProviderExtension>();
o.AddExtension<ClaudeCodeProviderExtension>();
});
var factory = services.BuildServiceProvider().GetRequiredService<IAgentSessionFactory>();
await using var session = await factory.CreateAsync(new AgentSessionOptions { Cwd = "/path/to/project" });
session.Subscribe(e =>
{
if (e is AgentSessionEvent.MessageUpdate { AssistantMessageEvent: AssistantMessageEvent.TextDelta d }) Console.Write(d.Delta);
});
await session.PromptAsync("Explain this repository");
Documentation
- Architecture: projects, vertical slices, the agent loop and the event model.
- Writing extensions: tools, commands, lifecycle hooks, providers, message injection, discovery.
- Providers and models: model keys, authentication, the built-in provider extensions.
- Telegram bot: setup, commands, security, connection recovery.
- Docker and Coolify: the container image, configuration, volumes, first login.
- Google Calendar: Google Cloud setup, connecting a chat, token storage, time zones.
- Building a UI (Telegram guide): how to put a chat front end on the core.
- Claude Code provider: requirements, how the bridge works, limits, the doctor command, automatic re-login when the subscription session expires.
- Design spec and plan:
docs/superpowers/specs/,docs/superpowers/plans/.
Layout of the agent directory
Everything the runtime writes lives under ~/.volcanic/agent (override with VOLCANIC_AGENT_DIR or AgenticCoreOptions.AgentDir):
~/.volcanic/agent/
sessions/<scope>/<session-id>.jsonl one JSONL file per session
extensions/*.dll extension assemblies discovered at start
auth.json { "anthropic": { "apiKey": "sk-..." } }
settings.json { "extensions": ["path/to/Ext.dll"] }
A project can add .volcanic/extensions/*.dll and .volcanic/settings.json in its working directory.
Status
Ported from pi: agent loop with parallel tool execution, steering and follow-up queues, abort, auto-retry,
overflow recovery and threshold compaction; JSONL session trees with resume and branching; the extension
event model; slash commands; provider registration; API key resolution; built-in read, write, edit,
bash, ls, grep, find tools; system prompt assembly with AGENTS.md (or, if absent, CLAUDE.md) project context.
Beyond pi: the working directory may also hold PERSONA.md (the agent's personality) and MEMORY.md (memories
the agent maintains itself with edit/write); both are appended to the system prompt when present. File names
match case-insensitively.
Not ported (by design): the terminal UI, themes, keybindings, renderers, skills and prompt templates, OAuth logins, image generation models, session tree navigation commands.
License: MIT. Fixtures under tests/.../ClaudeCode/Fixtures are from pi-claude-code-provider (MIT).