Agent = Orchestration(Data, Decision, Direction)
The C# reference implementation of The Standard for Agents.
The mark is the thing itself: three arcs — Data, Decision, Direction — orbiting a single core. One brain at the center, the three natures turning around it.
- Data — what the agent has (skills, memory, knowledge) · verb: Recall
- Decision — what the agent thinks (one brain, wrapped in a Gate and a Judge) · verb: Think
- Direction — what the agent does (act internally, act externally, or return) · verb: Act
Orchestration is not a fourth nature. It is the composition operator — the loop.
Built for everyone who ships agents: the individual who wants the simplest thing that works, the small team that needs it reliable, and the enterprise that needs it accountable — the same agent grows all the way up, never a rewrite.
The sessions are live and ongoing on Hassan Habib's YouTube channel:
- Create Your First AI Agent from Scratch — start here: a working Tri-Nature agent, from nothing, in one sitting.
- PeerLLM Skills: Build, Test & Optimize Agent Skills Across Any LLM — the Data nature in practice: skills as Markdown, evaluated across models.
- The Standard sessions playlist — the engineering discipline every line of this framework is built on, from brokers to orchestration.
Subscribe there for the sessions on each new capability as it ships.
dotnet add package Standard.AgentsThe Tri-Nature is the whole model at every size. An agent is always Data · Decision · Direction turning in a loop — so a ten-second one-liner and a bank's compliance agent are the same shape. You never learn a second framework to scale up: you add capability, one opt-in line at a time, to natures that are already there. Enterprise isn't harder — it's the same agent with more power and more detail.
You have a brain (a URL, a key, a model). That is a working agent.
var agent = new StandardAgent(apiUrl: "https://api.peerllm.com/v1/", apiKey: key, model: "LLooMA2.0");
string answer = await agent.ProcessPromptAsync("What is 47 * 89?");No skills, no tools, no guardians — all opt-in. Nothing to configure. It is already talking.
A startup that needs something that works: a persona, the ability to act, a conscience on the way in and out, and a memory that survives restarts. Same builder, a few more lines.
var agent = new StandardAgent(url, key, "LLooMA2.0") // your brain
.Skills("Skills") // who it is — Markdown, not code
.Tool(new CalculatorTool()) // what it can *do*
.Gate(apiUrl: url, apiKey: key, model: "LLooMA2.0") // screen the request (Decision)
.Judge(apiUrl: url, apiKey: key, model: "LLooMA2.0") // review the answer (Decision)
.Memory("memory.txt"); // remember across runs (Data)A 20-year professional shipping into a bank. Every line above is still here — you added, you did not rewrite. What's new is power: data that never leaves in the clear, a person between the agent and anything irreversible, effects that cannot happen twice, and an audit trail a regulator can read.
using Standard.Agents.Models.Loggings; // TraceVerbosity
var agent = new StandardAgent(url, key, "LLooMA2.0")
.Skills("Skills")
.Tool(new WireTransferTool())
.Gate(apiUrl: url, apiKey: key, model: "LLooMA2.0")
.Judge(apiUrl: url, apiKey: key, model: "LLooMA2.0")
.Memory("memory.txt")
.Constitution("Constitution/ethics.md") // one law above both guardians
.Redact() // PII tokenized before the brain, restored after
.LogTo("log.txt", TraceVerbosity.Full) // full turn-by-turn decision trace
.Audit("audit.jsonl") // structured decision log → your SIEM
.Telemetry("teller-agent") // OTel spans + metrics — GenAI semconv, no packages
.Principal(() => currentUser.Id) // authorization has a subject
.OnPolicy(Authorize) // your rules decide, per act and per identity
.RequireApproval("wire_transfer") // a person, before the act — never after
.EffectLedger("ledger") // run once, even across a crash
.ScreenToolOutput() // tool results are untrusted input
.Sessions("sessions") // conversation that survives the process
.Budget(maxCostUsd: 0.25m, costPerThousandTokens: 0.002m) // bound what one prompt may spend, at your rate
.Resilience(retries: 3) // survive a blip without paying twice
.CompensateOnFailure(); // unwind a run that died halfwayEverything below the blank line is opt-in, each with a sane default and none of them a new concept. That is the collapsible substrate: the public API, the loop, and the Tri-Nature never change; power lives in the brokers and the deployment. Delete any line and the agent still runs, with that capability absent rather than half-configured.
The full walkthrough — one capability per step, every snippet runnable — is in docs/how-to.md.
Every capability answers Local, External and Custom, and the verbs say which:
.Knowledge("Knowledge") // Local — point at what you already have
.UseKnowledge(new PostgresKnowledgeBroker(cs)) // External — a provider package
.OnKnowledge(async query => await MyStore(query)) // Custom — your own code, inlineTwenty capabilities, the same three verbs each — other agents among them: .Agents("Fleet")
reads a folder of agent documents, .UseAgents(...) takes a provider's registry, and
.OnAgents(...) lets your code decide the fleet. A capability offered fewer ways than that fails
the build — it is a test, not a convention, because the erosion is otherwise invisible until
there are six of them.
And what the agent integrates with is always plural: tools, MCP servers, skill sources, and
registered agents all accumulate — a second .Mcp(...) adds a server (each with its own
optional auth: none, an API key, or an OAuth bearer token), .McpProcess("npx", [...]) adds one
that runs as a local process, a second .Skills(...) adds a folder, and calls route to whichever
source's catalog owns the name, first registered winning. MCP servers built with any of the
official SDKs work as written, over HTTP or standard input and output.
There is a fourth door, and no mainstream framework has it: the entire configurable surface as one JSON document, one key per capability, the same names as the builder verbs. Any platform that can push a form into a JSON body can define an agent — guardians, budgets, perimeter, approvals, redaction, telemetry and all:
var agent = StandardAgent.FromJson(formBody); // data
// .Tool(new CalculatorTool()) // code still chains — tools ARE code{
"brain": { "apiUrl": "https://api.peerllm.com/v1/", "apiKey": "k", "model": "LLooMA2.0" },
"ruleGate": ["password"], "redact": true, "maxTurns": 5,
"requireApproval": ["wire_transfer"], "budget": { "maxCostUsd": 0.25, "costPerThousandTokens": 0.002 },
"telemetry": "form-built-agent"
}A key the agent does not know refuses to compose, with the key named — a typo'd "buget"
must never produce an unbounded agent that looks configured. Tools stay code because they are
code — except MCP, where a tool is a URL or a command, which is data. And the deployment half is one file:
drop an agent.json beside Standard.Agents.Host and the hosted agent composes entirely from
it — no C# anywhere
(docs/how-to.md §16).
Stream the agent thinking and answering — each event is tagged, and the answer arrives live:
await foreach (AgentStreamEvent streamEvent in agent.StreamPromptAsync("What is 47 * 89?"))
{
switch (streamEvent.Type)
{
case AgentStreamEventType.Thinking: /* the model deliberating / tool reasoning */ break;
case AgentStreamEventType.Narration: /* "Let me check..." — screened, user-voiced */ break;
case AgentStreamEventType.Response: /* the answer, token by token */ break;
case AgentStreamEventType.Tool: /* a tool ran, and its result */ break;
case AgentStreamEventType.Status: /* lifecycle: turns, gate, judge */ break;
case AgentStreamEventType.Usage: /* tokens spent so far, after every call */ break;
}
}Streaming is not a lesser path. Budgets, cancellation, sessions, the perimeter and compensation all
hold here exactly as they do on ProcessPromptAsync — a control you can step around by changing
method is not a control. Pass a session id to stream inside a conversation:
agent.StreamPromptAsync(prompt, sessionId, cancellationToken).
And when you need the events and how the run ended — every exposer that yields a pending tool call back to its own caller does — the streamed outcome (SPEC.md §4.14) ends the choice between them:
AgentRunStream run = agent.RunStreamAsync(request, cancellationToken);
await foreach (AgentStreamEvent streamEvent in run)
{
/* every event, live — narration included */
}
AgentOutcome outcome = run.Outcome; // the same outcome RunAsync returns: status,
// result, pending effect with its call idAnd what an agent carries is not what every run is offered: a selector names the subset of tools each run's task actually needs, so a greeting is offered nothing and an agent with twenty tool servers never puts twenty catalogs in front of "Hello" (SPEC.md §4.15):
agent.OnSelectTools((task, described) =>
new ValueTask<IReadOnlyList<string>>(
NeedsTheWeb(task) ? new[] { "web_search" } : Array.Empty<string>()));And when the Brain is not fully mediated by the loop — a custom brain, a gateway, a model
router that could carry side-channel knowledge of the catalog — .EnforceSelection() makes
the offering binding at the Direction perimeter: an unoffered tool is denied and the run
recovers, exactly as a policy denial is handled.
No DI container. Compose() hand-wires the whole graph — SPEC.md §9: "DI is OPTIONAL. A
hand-wired composition root is fully conformant."
An agent you expose serves callers who each want something different. A request carries its own
inference options, and one composed agent serves them all concurrently — one composition, one
HttpClient, every run keeping its own values:
string answer = await agent.ProcessPromptAsync(new PromptRequest
{
Prompt = "capital of France, as JSON",
Temperature = 0.2,
ResponseSchemaJson = """{"type":"object","required":["city"]}"""
});What is established and hard-configured takes precedence, always — configured → request →
framework default, resolved once at the boundary. A caller can never widen the boundary the
deployment set: a configured Contract discards the request's schema, a configured temperature
cannot be moved, and the opaque ProviderOptionsJson passthrough is stripped of every core-owned
key. Caller-declared tools (CallerTools) are vocabulary, not capability: a call naming one ends
the run AwaitingInput with the call riding the session as a pending effect, for the caller to
execute and answer. See
docs/per-request-inference.md and how-to §16.
The core Standard.Agents is deliberately
provider-independent: a hosted brain, and local-file skills, memory, and knowledge, with no
provider SDK in the box. It is not literally package-free: it ships three runtime dependencies
(RESTFulSense for the HTTP client, Xeption for exception data, and
Microsoft.Extensions.Logging.Abstractions), and their transitive graph is inventoried,
license-checked and held to a committed budget on every build, so it cannot grow unnoticed. Reach further with an opt-in package — each one backs a single
nature with a real provider through the same broker seam, so it's one line and nothing else
about your agent changes. Mix them freely: a local brain with cloud data, a registry of skills
with a Redis memory.
| Package | Backs | Provider |
|---|---|---|
Standard.Agents.Decision.Brains.LlamaSharp |
Brain · Decision | a local GGUF model via llama.cpp — no API, no network |
Standard.Agents.Data.Skills.PeerLLM |
Skills · Data | versioned skills from the PeerLLM registry, pulled at runtime |
Standard.Agents.Data.Memory.Redis |
Memory · Data | memory in Redis, keyed per agent / user / session |
Standard.Agents.Data.Knowledge.Postgres |
Knowledge · Data | knowledge in PostgreSQL, ranked tsvector full-text |
Standard.Agents.Data.Knowledge.MsSql |
Knowledge · Data | knowledge in SQL Server, FREETEXT full-text |
The Gate and Judge take your own delegate too —
.OnGate/.OnJudgedrive them with any in-process model, no package needed.
// fully local — one GGUF drives brain, gate and judge, no network anywhere
var llama = new LlamaSharpGeneratorBroker("model.gguf");
var agent = new StandardAgent()
.UseGenerator(llama)
.OnGate(llama.GenerateAsync)
.OnJudge(llama.GenerateAsync);
// production — skills from the registry, knowledge in Postgres, memory in Redis
var agent = new StandardAgent(url, key, "LLooMA2.0")
.UseSkills(new PeerLLMSkillBroker("hassanhabib/my-skills", SkillSync.Hybrid))
.UseKnowledgePostgres(pgConnectionString)
.UseMemoryRedis("localhost:6379", key: $"agent:{userId}");Pick each nature's home independently; the code above doesn't change when you do — that's the whole promise of the broker seam.
The diagram is generated from
assets/the-standard-agent-architecture.svg
— edit the SVG, re-render the PNG.
The 1·3·9 is the core shape of an agent. One loop, three natures, and beneath them the foundations — the nine that have been there since the beginning:
| Data | Skill, Knowledge, Memory |
| Decision | Brain, Gate, Judge |
| Direction | InternalTool, ExternalTool, Return |
ReturnService has no broker. It is the dead end — the terminal Direction hands the result back
and touches nothing.
Fifteen is what actually ships. Six foundations joined the nine, and the reason each one had to be a foundation is the same: it is a distinct resource with its own failures, and a resource reached without a foundation has no validation, no exception mapping, and its failures get blamed on the caller.
| Tier | Count | Members |
|---|---|---|
| Management | 1 | RunManagementService — the only loop: Recall → Think → Act |
| Coordination | 3 | Data · Decision · Direction — the three natures |
| Orchestration | 6 | Retrieval, Recollection / Inference, Guardian / Perimeter, Execution |
| Foundation | 15 | the nine, plus Usage and Contract, and Session, Policy, Approval, EffectLedger |
| Broker | 14 + 6 | fourteen nature brokers, four utility, two decorating |
Eleven of the fifteen are always there — the agent proper, the nine plus Usage and
Contract. The other four
arrive with the enterprise capabilities; leave them off and it is the same shape with less in it.
Six foundations under Direction alone breaks the 2–3 rule, and a nature holding six is a nature holding two concepts. So each nature splits into two regions, named for concepts rather than contents: Retrieval is what was authored and ranked, Recollection is what accumulated; Inference asks the model and reads its answer, Guardian is the conscience on either side of it; Perimeter asks may this happen, Execution does it.
Usage is why Decision has two regions rather than one. Measuring what a model call cost is the
same concept as making it — and without it, .Budget() could only bound a run whose provider
volunteered its own numbers, which is to say not the text protocol at all.
Contract is the third guardian, beside the Gate and the Judge. The Judge asks whether an answer
is good enough; the Contract asks whether it is the right shape — and a draft that fails its
declared contract is revised the same way one the Judge rejects is, with the validator's complaint
handed back verbatim.
Every tier holds two or three of the tier directly below it. Management over coordinations, coordination over orchestrations, orchestration over foundations, foundation over exactly one broker. Both bounds cost something when broken: more than three means the service is doing too much; fewer than two means it composes nothing and is a layer for its own sake.
The rest of the placement rule, all of it enforced by
TierDisciplineTests
rather than by convention:
-
A foundation wraps exactly one nature broker. The role may be versioned —
IGeneratorBrokerV1is the same seam asIGeneratorBrokerunder a newer contract — but it is one role. A second broker in a foundation is a capability that wants its own foundation. -
Nothing above the foundation tier takes a broker at all, beyond the three utilities.
-
Utility brokers — logging, time, audit, telemetry — are held by any tier. None of them can change what the agent decides or does, which is exactly why they are exempt from the count. Usage is not one of them: a budget stops a run.
-
Decorating brokers — redaction, resilience — are wrapped around another broker at composition, so no service holds them.
-
Every tier composes the tier directly below it — management over coordinations, coordination over orchestrations, orchestration over foundations. Adjacency is a rule of its own, not a by-product of the count: a service reaching past the tier below it borrows another nature's internals, and no test of counts or brokers will ever see that.
Flow is forward only. A tier never calls the tier above it, no foundation calls a sibling, and
there are no exceptions. Screening a tool result — .ScreenToolOutput() — is the loop asking
Decision for a verdict, because what may re-enter the context between turns is the loop's question
and the loop is the only place that sees both natures.
Two rulebooks, and they compose:
- SPEC.md owns contracts and behavior. Normative, language-neutral.
- The Standard owns structure, exceptions,
and process — brokers, foundations, orchestrations, the
Xeptionmodel, FAIL/PASS TDD.
They do not collide: SPEC.md §1 states that "conformance is about contracts and behavior, not file layout or language idiom."
The theory is settled in THE-TRI-NATURE-OF-AGENT.md before any code is written. Build to it.
Standard.Agents/ the library
|-- Brokers/{Skills,Generators,Tools,...} one flat folder per seam — twenty in all
|-- Models/Foundations/{Entity}/Exceptions
|-- Models/Orchestrations/Agents AgentContext, AgentStatus, ToolExchange
|-- Models/Orchestrations/Effects AgentEffect, AgentPrincipal, CompensationOutcome
|-- Models/Brokers/Generators/V1 the native tool-calling contract
|-- Services/Foundations/{Entity}s fifteen; one broker each, Return none
|-- Services/Orchestrations/{Nature}/... six regions, grouped by nature
|-- Services/Coordinations/{Nature} Data, Decision, Direction
|-- Services/Managements RunManagementService — the loop
|-- Tools/ ITool, AgentTool — the fractal bridge
Standard.Agents.Tests.Unit/ unit tests, mirroring the service tree
Standard.Agents.Conformance/ the vector runner
Standard.Agents.Evals/ the quality runner - golden cases, thresholded metrics
Standard.Agents.Host/ the same agent definition as a web service
Standard.Agents.Demo/ a console agent you can run
conformance/vectors/ language-neutral behavioral vectors
conformance/profiles/ what each readiness level requires
evals/golden/ golden quality cases the build certifies against
docs/how-to.md one capability per step, every snippet runnable
docs/evals.md the quality metrics and how to golden-set your own agent
docs/hosting.md the agent behind HTTP - runs, streams, heartbeat
docs/support.md stability, deprecation, supply chain
docs/generator-contracts.md text protocol vs native tool calls
Agent behavior involves an LLM and is non-deterministic, so it cannot be asserted directly.
conformance/ instead pins the deterministic contracts — the
loop, reply interpretation, tool routing, and the feed-back of results into Data — by scripting
the Brain. Every double replaces a broker, never a service: the whole 1·3·6·15 under test is the
real library.
dotnet test # unit tests
dotnet run --project Standard.Agents.Conformance # spec certification; exit 0 = conformantReadiness is claimed per profile, and the claim is checkable by anyone from a clone:
dotnet run --project Standard.Agents.Conformance -- --profile Critical| Profile | What an agent at this level has |
|---|---|
| Core | conversation, skills, knowledge, memory, simple tools |
| Reliable | guardians that see what they guard, durable decision log, run isolation, cancellation, timeouts |
| Enterprise | identity-aware authorization, approval before irreversible acts, run-once effects, budgets, ranked retrieval, per-request inference |
| Critical | conversation and effects that survive a process, compensation, native tool calls that round-trip, adversarial evaluation — a fooled Brain still cannot act outside the perimeter |
All four certify. Exit 0 means certified; the runner is the authority, not this table.
Beside contract certification sits quality certification: golden eval cases measuring task completion, groundedness, retrieval precision and recall, tool selection, refusal correctness and revision effectiveness — thresholded, deterministic, and run on every build (docs/evals.md):
dotnet run --project Standard.Agents.EvalsAn agent satisfies ITool, so an agent can be a tool of another agent. Theory Ch.4 — turtles up:
var outerAgent = new StandardAgent().Brain(...)
.OnAgents(() => new ValueTask<IReadOnlyList<RegisteredAgent>>(
[new RegisteredAgent("billing", "Handles refunds and invoices.", billingAgent)]));A registered agent materializes as a tool, so a handoff is an act and the perimeter that governs acts governs handoffs — allow-lists can forbid one, approvals can put a human before one. Three flavors of multi-agent flow, all configured, none requiring new machinery:
- Sub-agent —
ACTION: billing: <task>: the answer comes back for the outer brain to synthesize, and the handoff is grounded by default ("The user asked: {prompt} — Your task: {input}"), with the template deciding exactly what crosses. - Transfer —
TRANSFER: billing: the specialist's answer is the run's answer, verbatim. A transfer that does not deliver comes back marked[did not complete]and the outer brain keeps working — a refusal is never presented as the user's answer. - Chain — a deterministic sequence is your code calling agents in order; every link keeps its own guardians, budget and perimeter.
The fleet is data too: an "agents" key registers a folder of agent documents or inline agent
documents — the file is the agent, name and advertisement included
(docs/how-to.md §17).
It is also how a guardian scales: a compliance sub-agent is a distinct conscience, rather than
the same brain grading itself.
This repo follows The Standard's practices:
- One issue per method. One branch per issue.
- Branch:
users/[username]/[CATEGORY]-[entity]-[action], where the action speaks the language of its layer — brokersinsert/select, foundationsadd/retrieve. - Foundations and up are test-driven, two commits per test:
[TestName] -> FAIL, then[TestName] -> PASS. A FAIL commit must have been run and observed failing. - Brokers carry no unit tests — they are thin and hold no logic. Commit as
BROKERS: [Description]. - PR title:
[CATEGORY]: [Description Of Work Completed].
PeerLLM's LLooMA 2.0 — the network-native model of a people-first,
decentralized AI network — runs its production agentic loop on this framework. Every tool
decision a community of independent hosts makes flows through StandardAgent.RunAsync, with the
framework's spec'd behavior, conformance vectors, and safety guarantees underneath — and the
production learnings flow back into the framework, in the open.
Read the story of the shift: LLooMA 2.0, Rebuilt on The Standard Agent.


