//  LIBRARY

CxAgent.Core

The agent loop without a terminal.

Everything under cxagent's UI ships as a package: a conversation, the agent running it, tool execution, sub-agent delegation, permissions, MCP, plugins and resumable stores. You supply where the text goes; there is no UI dependency. cxagent is one consumer of it, and a Spectre.Console front end is another, in about a hundred lines.

dotnet add package CxAgent.Core

Targets net8.0, net9.0 and net10.0. MIT licensed.

The mental model

A session is a conversation. An agent is the loop that runs one turn of it. You supply where words go. Everything else follows from those three sentences.

You construct far less than people expect. SessionManager.Create builds what sessions share — the resume database, the usage archive, the log directory, the permission rules store, the command registry — and Open builds everything per-session. You never construct an Agent or a tool.

An app is four calls

using CxAgent.Core.Sessions;
using CxAgent.Core.Llm;
using CxAgent.Core.Storage;

using var manager = SessionManager.Create(new AppPaths(configDir));

var session = manager.Open(
    workingDirectory,                                        // the permission boundary
    resolution,                                              // which model — see below
    new SessionPorts { Observer = sink, ToolObserver = jobs });   // where words and tool activity go

if (session.Submit("summarise this folder") is Session.SubmitOutcome.Started started)
    await started.Turn;

Submit is synchronous. It returns a receipt rather than a task, because there are three outcomes and they are three different things for a caller to do: Started (a turn began — await it), Queued (a turn was already running, and this joined the queue), and NoAgent (no model is wired).

What is actually yours to write

You supplyWhat it is
ISessionObserverwhere words go — the one thing you must implement
IToolObservertool activity; a no-op is fine
a prompt hook + a PermissionPolicyhow a human is asked, and which folder to judge against
a modelfrom code, from cxagent's own config.json, or a test double

The text an observer receives is markdown, and Core escapes what it interpolates — so a path like my_test_file.cs arrives with its underscores intact. Core writes no colour tags, so there is nothing to strip.

Permissions

The engine is in here. The only thing left to you is asking a human. Trust per folder, the working-directory boundary, edit modes, stored rules, the read-only command list, the Auto classifier and the wrapper that gates every tool are all Core. What Core cannot do is put a question on a screen, so you supply one delegate: given this request, what does the human say?

Two halves, and one without the other is silent. buildGate gives the decider a way to ask; SessionPorts.Policy gives it the folder and edit mode to judge against. Omit the policy and every request is refused rather than guessed at; pass no buildGate at all and nothing is gated — a legitimate headless arrangement, but a choice rather than something to inherit by forgetting.

Tools

The agent gets read, write, edit, glob, grep, shell, http and web fetch without you registering anything, plus todowrite, ask_user, agent and skill. Each is wrapped in a permission gate at construction, so there is no path that runs a tool ungated.

You can add your own — a deploy(env), a queryOurWarehouse(sql) — with one line of wiring. They are offered beside the built-ins, dispatched behind them, and gated on the way through. Or offer fewer: a ToolSelection narrows what an agent is handed, and a withheld tool is refused if called by name rather than merely hidden.

ToolSelection = new ToolSelection([Tool.Inherited, Tool.Not.RunShell]),

Reference

The examples: ToolAgent (a console app with an injected tool), SpectreAgent (the same, with a rendered UI), ReadOnlyAgent (taking tools away — a selection you can watch hold), CalculatorPlugin and CalculatorAbiPlugin (a plugin end to end, managed and in one file of C).