EugeneTes f42b00cb1c fix(a2a-client): watches no longer hang on a silently dead task stream
Hour-long remote tasks were never delivered: a subscribe watch held one SSE stream for up to a day, the remote
sent nothing while its agent worked without text, and once the quiet connection died on the way (no FIN/RST) the
watch waited on it with no error and no log line.

- A subscription lasts at most 5 minutes (SubscriptionSlice), then GetTask and a new subscription: a pause, not a
  failure. A slice ended mid-read counts as ours even when it surfaces as an I/O error.
- The shared handler connects with TCP keepalive (60 s, every 15 s, 4 probes) and HTTP/2 pings.
- Log lines when a watch starts or is restored, when a slice ends on a running task, when a result is delivered or
  kept; the Telegram host's console log carries a UTC timestamp.
- action=status on a settled task ends its watch, so the result isn't delivered a second time.
- A "context busy" refusal of send says nothing was sent, names the watched task in that context and that its
  result will arrive by itself; refusals never touch a watch.
2026-10-16 13:48:32 +00:00
2026-09-25 11:56:33 +00:00

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
MCP client (stdio servers as tools, Playwright browser preset, remote HTTP/SSE servers per chat) extensions/Volcanic.AgenticCore.Extensions.Mcp See docs/mcp.md
A2A client (a2a tool, watches for long remote tasks) extensions/Volcanic.AgenticCore.Extensions.A2A A2AClient__Enabled; agents in <workspace>/.volcanic/a2a/agents.json; see docs/a2a.md
A2A server (other agents call the assistant) src/Volcanic.AgenticCore.A2A.Server A2A__Enabled; callers in <workspace>/.volcanic/a2a/callers.json; see docs/a2a.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. Agent Skills (Anthropic's SKILL.md format) in <cwd>/.volcanic/skills/<name>/SKILL.md are listed by name and description in the system prompt on every run; the model reads a skill's file when it needs it. An INSTRUCTIONS.md next to it (or alone in the directory) goes into the prompt in full on every run (16 KB per file, 64 KB total).

Not ported (by design): the terminal UI, themes, keybindings, renderers, 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).

Description
No description provided
Readme 3.4 MiB
Languages
C# 99.8%
Dockerfile 0.2%