Architecture overview
netclaw is a single-process .NET 10 application that runs as an always-on agent. It takes input from Slack, Discord, Mattermost, webhooks, timers and a local TUI, and treats all of them the same way: as messages routed into persistent conversation actors. One daemon owns everything that matters, and the rest of this page maps its parts.
Two binaries ship together: netclawd (the daemon) and netclaw (the CLI). The daemon holds the agent logic. The CLI is mostly a thin client that renders the daemon’s output in a terminal, with a few commands (memory, most of doctor) that work straight on files, so they still run when the daemon is down.
Two binaries
Section titled “Two binaries”netclawd: the daemon
Section titled “netclawd: the daemon”The daemon is an ASP.NET Core application that owns the Akka.NET actor system, LLM sessions, tool execution, the channel adapters (Slack, Discord, Mattermost), the SignalR hub, the REST API, and SQLite persistence. It binds to http://127.0.0.1:5199 by default, and a PID file at ~/.netclaw/netclaw.pid plus a lock file enforce one daemon per machine. To listen beyond loopback, see exposure modes.
netclaw: the CLI
Section titled “netclaw: the CLI”A Termina-based TUI with no actor system and no tool execution. It connects to the daemon over SignalR and renders what comes back. It also manages the daemon: netclaw daemon start and netclaw daemon stop run netclawd as a detached process.
The split matters. Restart the CLI without interrupting conversations. Keep the daemon in Docker and the CLI on your host, or run the CLI inside the container with docker exec <container> netclaw <command>, since the image ships both binaries. Talk to the same sessions from Slack instead of a terminal.

netclaw status reports daemon state, connectors, uptime and the current model.
Communication
Section titled “Communication”The CLI reaches the daemon over two protocols:
| Protocol | Path | Purpose |
|---|---|---|
| SignalR | /hub/session | Interactive sessions: create or attach to a session, send messages, answer approval prompts, stream output back |
| REST | /api/* | Everything without real-time delivery: health, stats, device pairing, MCP queries, shutdown |
The hub’s methods are CreateSession, EnsureSession, AttachSession, SendMessage and RespondToInteraction. Output comes back as typed events on a stream. Remote clients authenticate with a device bearer token issued through pairing. A loopback request counts as the operator’s own only when its Host is localhost or a loopback address and any Origin matches. Otherwise it gets a 401.
Akka.NET internals
Section titled “Akka.NET internals”Akka.NET is the concurrency and persistence layer. Every conversation is an actor, every channel adapter is an actor, and so are background jobs, reminders and tool approvals.
Key actors
Section titled “Key actors”| Actor | Role |
|---|---|
LlmSessionActor | One per conversation thread. Persistent, with five phases: Recovering, Ready, Processing, Compacting, Passivating. See sessions. |
SessionManager | Routes messages to the right session actor with child-per-entity routing (no lookup table). |
SessionMemoryObserverActor | A persistent child of each session. Watches the transcript and proposes memories when the session goes quiet. |
MemoryCurationActor | Validates and deduplicates memory proposals. A background worker applies the same logic to the durable checkpoint queue. |
SubAgentActor | Child of a session actor. Runs delegated work (research, code analysis, summarization) in the background while the session carries on. |
ReminderManagerActor | Scheduled tasks, backed by Akka.Reminders and SQLite. |
BackgroundJobManagerActor | Detached shell jobs, up to 5 at a time. |
ToolApprovalActor | Holds approval requests until a human answers. |
PairingActor | Issues pairing codes and device tokens for remote clients. |
ModelCapabilityActor | Singleton that caches model capabilities at startup. |
| Channel actors | A gateway, conversation and thread-binding actor for each of Slack, Discord and Mattermost. |
Why actors
Section titled “Why actors”Each conversation needs its own state, its own message queue and crash isolation from the others. Actors give you that without thread management. A session actor processes one turn at a time, journals its events to SQLite, and recovers its full history on restart. If one conversation crashes, the rest keep running.
Persistence uses Akka.Persistence.Sql with SQLite and Google Protobuf serialization through NetclawProtobufSerializer. The journal records turns, accepted input, tool batches and their calls, approval requests and decisions, compaction, title changes and background job reaping. It’s an event log for the session, not just a transcript.
Three boundaries
Section titled “Three boundaries”Three logical boundaries divide the daemon, from the runtime spec.
At the edge, the gateway receives transport events (a Slack message, a webhook POST, CLI input), runs policy checks, and converts them to actor commands. Nothing reaches the session layer before policy runs.
The session layer owns conversation state, turn lifecycle, compaction and persistence. Sessions only broadcast outward. They don’t know whether they’re talking to Slack, Discord or the CLI.
Subscribers (channel adapters and UI clients) consume those broadcasts. They render output and nothing else, and they can’t read actor internals or influence model behavior.
Data flows one way: the gateway sends commands to sessions, and sessions broadcast to subscribers. A policy error closes the gate.
Everything is just input
Section titled “Everything is just input”A Slack message, a webhook POST, a timer, a CLI chat: each produces a message routed to a session actor with context-specific instructions. The actor doesn’t know where it came from, and turns run identically regardless of source. Adding a channel adapter requires no changes to the session layer.
Channel adapters
Section titled “Channel adapters”Slack, Discord and Mattermost adapters run inside the daemon as hosted services. They’re in-process actors holding persistent connections to the platform APIs, not external bridges.
| Channel | Connection | Actors |
|---|---|---|
| Slack | Socket Mode via SlackNet | SlackGatewayActor, SlackConversationActor, SlackThreadBindingActor |
| Discord | WebSocket via Discord.Net | DiscordGatewayActor, DiscordConversationActor, DiscordSessionBindingActor |
| Mattermost | WebSocket and REST, with interactive actions posted back to the daemon | MattermostGatewayActor, MattermostConversationActor, MattermostSessionBindingActor |
An Akka.Streams SessionPipeline bridges channel adapters to the actor system. Setup for each is in the channels section.
LLM providers
Section titled “LLM providers”netclaw uses Microsoft.Extensions.AI (IChatClient) as the provider abstraction. Supported providers:
| Provider | Type key | Notes |
|---|---|---|
| Ollama | ollama | Native integration, queries /api/show for capabilities. |
| OpenRouter | openrouter | Widest model catalog. |
| Anthropic | anthropic | Direct API. |
| OpenAI | openai | Direct API, OAuth or API key. |
| GitHub Copilot | github-copilot | OAuth device flow. Uses your Copilot subscription. |
| Venice.ai | veniceai | Privacy-focused, OpenAI-compatible API. |
| DeepSeek | deepseek | deepseek-v4-* models get a 1M context window and text-only limits from netclaw. |
| OpenAI-compatible | openai-compatible | Any self-hosted endpoint (llama.cpp, vLLM). The API key is optional. |
At startup the daemon works out each model’s context window and input and output modalities. It asks the provider first, then falls back through the OpenRouter catalog and HuggingFace metadata, filling each field from the first source that knows it, and assumes text-only if nothing answers. See Models for configuration and managed or self-hosted providers for setup.
Session lifecycle
Section titled “Session lifecycle”Each conversation thread maps to one persistent actor. The actor cycles between Ready and Processing, compacts when its context passes a threshold, and passivates when idle. Sessions covers the phases, the timeouts, compaction and what a session leaves on disk.
Tool system
Section titled “Tool system”Tools are C# methods registered at compile time through a Roslyn incremental source generator, which produces the JSON schema and typed deserializers with no runtime reflection. ToolRegistry holds the catalog and IToolExecutor runs them.
Since 0.27.1 every tool call and every shell call goes through one ToolAuthorizer: the same authority and consent loop, so there’s no second gate to drift out of sync. The older policy classes (ToolAccessPolicy for which tools exist, ToolPathPolicy for filesystem boundaries, ShellCommandPolicy for shell commands) still do the individual checks inside it. Feature gates hide the tools of a disabled subsystem (memory, search, skills, scheduling, subagents) from every audience. The deployment posture caps the audience of every call, including a sub-agent’s, so a Team posture gives netclaw chat Team tools. See the security model.
A session starts with a small core of tools and loads the rest on demand, so the prompt stays small. That’s progressive disclosure. search_tools ranks its results by how many query words match, so a built-in tool like list_reminders turns up ahead of a shell command.
MCP integration
Section titled “MCP integration”External tool servers connect through the Model Context Protocol. McpClientManager handles the server lifecycle, including OAuth 2.1 flows and, since 0.27.0, pre-registered confidential clients with a stored client secret. Servers connect in parallel at startup. Sessions see server summaries until a tool is requested through search_tools. A server’s prompts can be invoked as skills, and image, audio and resource results from MCP tools are delivered as files. See MCP servers.
Supporting systems
Section titled “Supporting systems”The daemon keeps memory in ~/.netclaw/netclaw.db: documents, records, a full-text index and, since 0.27.0, vectors. Two small ONNX models downloaded to ~/.netclaw/models/ embed memories and gate recall, entirely in-process. A background MemoryCurationEngine extracts, deduplicates and merges proposals. See the memory model.
Skills are markdown files that extend what the agent knows how to do. The built-in system skills ship inside the daemon binary and are restored on every start. Everything else comes from your own skills directory, operator-configured skill feeds and external sources, or from the agent itself through the skill_manage tool.
For delegated work, a session spawns a subagent as a child actor. Definitions live in ~/.netclaw/agents/*.md with YAML frontmatter (built-ins: research-assistant, code-analyst, summarizer), and each run goes on until it gives a final reply, is cancelled or goes quiet for too long, with no fixed cap on tool rounds. spawn_agent returns a run ID at once. The run is recorded in the session journal, and its result arrives in a later turn as a notice that names a result.md file. See custom subagents.
Search runs through Brave, DuckDuckGo or SearXNG backends. Images and attachments are normalized before they reach a model. Scheduled tasks go through ReminderManagerActor, covered in Reminders.
Configuration
Section titled “Configuration”Layered, with later sources winning:
~/.netclaw/config/netclaw.json: base config~/.netclaw/config/secrets.json: encrypted credentials (ASP.NET Core Data Protection)NETCLAW_*environment variables: highest priority
ConfigWatcherService watches netclaw.json with a 500 ms debounce. A valid change triggers a coordinated restart: close ingress, drain active sessions, persist state, restart in-process, then warm the active sessions back up. An invalid change is refused and the running daemon keeps its current configuration. Secrets and environment changes aren’t watched, so those need a manual restart, and an OAuth token refresh writes secrets.json without causing one.
The drain stops each session at a safe point, such as the end of a tool batch, and tells the person in the chat. A turn stopped after its first tool batch resumes after the restart when nothing else is waiting. A running sub-agent parks and resumes too, which can add up to about 18 seconds to a graceful restart. A graceful stop of the whole daemon is still bounded to 30 seconds.
Deployment
Section titled “Deployment”netclaw runs on Linux (x64 and arm64), Windows x64 with native PowerShell, and macOS on Apple Silicon. The installer downloads two archives per platform, netclaw and netclawd, as .tar.gz (.zip on Windows).
| Method | Guide |
|---|---|
| systemd (recommended for Linux) | systemd service |
| Docker | Docker deployment |
| Direct | netclaw daemon start (detached process) |
The Docker image ships both binaries, with netclaw on the PATH, so docker exec <container> netclaw status works. For remote access beyond loopback, see exposure modes.
Design principles
Section titled “Design principles”None of these are aspirational. They’re constraints the codebase enforces.
- Gall’s Law first. Ship a simple working system before building a generalized framework.
- Actor transport boundary. Channel adapters consume broadcasts through pub/sub and never drive model internals directly.
- Serialization boundary. netclaw controls its own persistence schema and never persists framework-external chat types.
- Security boundary. Default deny, explicit allow, fail closed. See the security model.
- Everything is just input. Every source (Slack, webhook, CLI, timer) produces a message routed to a session actor. No special-casing by channel.
Limitations
Section titled “Limitations”- No clustering. One daemon, one machine, one actor system.
- SQLite is the only persistence backend. That’s right for a single node and doesn’t scale to multiple instances.
- Remote access works through exposure modes and paired devices, not through an identity provider. There’s no per-user login.
- Binary updates need a daemon restart. There’s no hot code reload.
- Local semantic memory loads about 129 MB of models and makes the daemon roughly twice the size (about 480 MB idle against about 235 MB) it’d be without them. Turn it off if the host can’t spare the RAM.
Related pages
Section titled “Related pages”- Sessions and input model - lifecycle, timeouts, compaction and storage
- Memory model - how the daemon remembers across sessions
- Context structure - what the model sees each turn
- Security model - audiences, approvals and the tool authorizer
- Design philosophy - why it’s built this way
Resources
Section titled “Resources”- Akka.NET documentation - the actor framework behind netclaw’s concurrency
- Microsoft.Extensions.AI - the
IChatClientabstraction for LLM providers - Model Context Protocol specification - the standard for external tool servers
- SignalR documentation - real-time communication between the CLI and the daemon
- SQLite documentation - the persistence and memory backend