A modular .NET agentic platform for software delivery. A central orchestrator coordinates specialist agents — Requirements → Code → Tests → QA — over a provider-agnostic LLM gateway, on a multi-tenant runtime with row-level isolation, driven from a Blazor desktop UI.
The AgentOS.NET desktop — a Blazor Server window manager with an authentic GNOME shell (Activities overview, virtual workspaces, Dash). Every capability is a window: Insights, the visual Workflow editor (where runs start), Board, Settings, Terminal, Files.
AgentOS.NET turns a plain-English user story into reviewed, test-backed C# code. Five agents collaborate under an orchestrator; a QA agent scores requirement ↔ code ↔ test consistency and loops until the result converges or an iteration cap is hit. Every model call is metered (tokens, cost, latency), every tool call passes a policy gate and is recorded as evidence, and every setting is changed in the desktop — not in a config file.
The platform is a modular monolith: each feature (LLM gateway, pipeline, identity, tenants,
settings, tools, integrations, MCP, workspaces, sessions, remote agent) is a self-contained IModule
with its own DI surface, EF Core context and Postgres schema, so any one can later ship as a package.
Status: pre-1.0. The pipeline, gateway, modular runtime, multi-tenant identity and the desktop are working and deployed on Azure Container Apps; public surfaces may still change before
v1.0.
Contents: Quick start · Screenshots · Why AgentOS.NET · Concepts · Architecture · Modules · LLM gateway · Configuration · Multi-tenant · API · Extending · Deploy · Contributing
| I want to… | Read |
|---|---|
| Run it locally (standalone or full stack), secrets, CI | docs/SETUP.md |
| Test against real GitHub / Azure DevOps / LLMs (the lab server) | docs/LAB.md |
| Know what to set at deploy vs in the UI | docs/CONFIG.md |
| Understand the architecture | docs/architecture.md · ADRs |
| Deploy to Azure | docs/DEPLOYMENT.md · infra/README.md · runbooks: Keycloak, DataProtection DR |
| Contribute / conventions | CONTRIBUTING.md |
| Build UI | docs/design/design-system.md |
| See where it's going | ROADMAP.md · execution plan |
| Report a vulnerability | SECURITY.md |
Prerequisites: .NET 10 SDK (pinned in global.json). Docker only for the full stack.
git clone https://github.com/hoangsnowy/AgentOS.NET.git
cd AgentOs
dotnet run --project src/AgentOs.WebOpen http://localhost:5180. You are signed in automatically as a Development user (with admin and platform-admin rights). Open Workflow → Run: with no API key configured, agents answer with canned, schema-valid output that is labelled Offline everywhere — the run, the chip, the Run Log — so you can explore the whole flow before spending a token.
Standalone is the degraded mode: there is no database (settings and runs live in memory and reset on restart) and no real identity provider. Use it for UI work and first contact; use the full stack for anything involving persistence, sign-in or multiple tenants.
dotnet run --project infra/AgentOs.AppHost| What | Where |
|---|---|
| Aspire dashboard | https://localhost:17050 |
| AgentOS.NET desktop | https://localhost:5180 — sign in as operator / operator (realm agentic) |
| Api + Scalar UI | https://localhost:5080/scalar/v1 |
If the first run misbehaves, it is almost always one of three known causes — a stale Docker volume
(Postgres password or old Keycloak realm), or HTTP 431 from an overfull localhost cookie jar (use an
incognito window). Fixes are in docs/SETUP.md.
In the desktop: Settings → AI → Model keys (or press Ctrl+K and type "add key") — one card per
provider (Anthropic, Azure OpenAI, OpenAI, Gemini, xAI): paste the key, Save, then Test (stored
encrypted, per tenant). Then pick each agent's model in Settings → Agent profiles;
the next run uses it, no restart. Keys can also be seeded from the environment
(Llm__Claude__ApiKey, user-secrets) — see docs/CONFIG.md.
dotnet build AgentOs.slnx -c Release
dotnet test --solution AgentOs.slnx -c Release # xUnit v3 on Microsoft.Testing.Platform
dotnet test --project tests/AgentOs.Tests --filter-class "*CostCalculatorTests" # one classUI end-to-end tests (Playwright) run against a running Web:
# terminal 1
ASPNETCORE_ENVIRONMENT=Development dotnet run --project src/AgentOs.Web --urls http://localhost:5199 --no-launch-profile
# terminal 2
RUN_AGENTOS_E2E=true AGENTOS_URL=http://localhost:5199 dotnet test --project tests/AgentOs.E2E.TestsRUN_AGENTOS_E2E_REAL=true runs the real-auth suite against the full Aspire stack instead.
All shots are the standalone Web (dotnet run --project src/AgentOs.Web).
Light and dark are first-class (top bar → appearance toggle, or System → Appearance):
| Light | Dark |
|---|---|
![]() |
![]() |
- Modular by construction. Every feature is an
IModulediscovered by reflection; hosts wire the whole platform with oneservices.AddModulesFromAssemblies(cfg, …)call. Modules depend onDomain+SharedKernel; the few cross-module references are explicit (see Architecture). - Provider-agnostic LLM gateway. Claude, Azure OpenAI, OpenAI, Google Gemini, xAI Grok, any
OpenAI-compatible server, Microsoft Agent Framework and a paired dev-machine agent are interchangeable
ILlmClients; each tenant brings its own keys. Pick per agent in the desktop — no code change, no restart. - Measured. Every call records tokens in/out, cost and latency per run and agent. Prices come from
a built-in table of official list prices (Claude 5.x, GPT-6/5.x, o-series, incl. prompt-cache and
long-context tiers); an unknown model is charged at a fail-closed ceiling, never
$0, so it cannot slip past the budget gate. - Governed. Tools run only through
IToolGateway→ per-tenant policy → evidence log. Production pins the riskiest switches (remote auto-approve, tool enforcement, offline fallback, build sandbox) as operator locks the UI cannot flip. Third-party text in prompts is fenced as untrusted input. - Multi-tenant from day one. Keycloak OIDC
tenantclaim →ITenantContext; row-level isolation through EF Core global query filters; per-tenant keys, settings and audit. - Honest. Canned offline output is labelled Offline end to end and never opens a PR; a standalone run says what it can't persist.
- Resilient gateway. Multi-key pools with round-robin, HTTP 429 cooldown honouring
Retry-After, and optional inter-provider failover chains. - Cloud-ready. A .NET Aspire AppHost runs the full stack locally;
azd updeploys it to Azure Container Apps.
| Concept | Lives in | Notes |
|---|---|---|
| Module | IModule (SharedKernel) |
One assembly = one module. Optional IEndpointModule (HTTP), IInitializableModule (startup hook), ISettingsContributor (settings it owns). |
| Agent | Modules.Pipeline.Agents |
A specialist — Requirement, Coding, Testing, QA, Orchestrator (+ IssueWork, Decomposer). Talks to models only through ILlmClientFactory. |
| LLM client | Domain.Llm.ILlmClient |
SendAsync(LlmRequest, ct) → LlmResponse, registered as a keyed service under its provider name. |
| Setting | Domain.Configuration.SettingDescriptor |
Key, kind, scope, default, validation, UI label. Resolved lock → tenant → platform → seed → default (ADR-0011). |
| Tool | Domain.Tools.ITool |
Invoked by models via the gateway, always through IToolGateway (policy → invoke → evidence). |
| Tenant context | SharedKernel.Identity.ITenantContext |
Tenant id, user id, roles for the request. DbContexts filter by TenantId; repositories stamp it on write. |
src/
├── AgentOs.Domain/ # Provider-neutral DTOs + contracts: ILlmClient, ITool/IToolGateway, settings, pipeline artifacts
├── AgentOs.SharedKernel/ # IModule contracts + ModuleLoader, ITenantContext, plugins (IAgentOsPlugin)
├── AgentOs.Modules.AppConfig/ # Settings registry + encrypted per-tenant KV store (schema: config)
├── AgentOs.Modules.Llm/ # Gateway: keyed providers, key pool + 429 failover, cost, model capabilities
├── AgentOs.Modules.Pipeline/ # Agents, prompts, orchestrator, graph executor, run history (schema: pipeline)
├── AgentOs.Modules.Identity/ # Keycloak JWT auth, HttpTenantContext, Admin/Member/PlatformAdmin policies
├── AgentOs.Modules.Tenants/ # Tenant registry, Keycloak admin client, invitations, audit (schema: tenants)
├── AgentOs.Modules.Tools/ # Tool registry, per-tenant policy, invocation evidence (schema: tools)
├── AgentOs.Modules.Integration/ # GitHub PR service + sandboxed build verifier (exposed as tools)
├── AgentOs.Modules.Mcp/ # MCP client: upstream MCP servers' tools join the tool registry
├── AgentOs.Modules.Workspaces/ # Connected repos/boards + connectors (schema: workspaces)
├── AgentOs.Modules.Sessions/ # Remote-runner registry, pairing, session-run feed (schema: sessions)
├── AgentOs.Modules.RemoteAgent/ # SignalR hub + RemoteAgentLlmClient (work runs on a paired dev machine)
├── AgentOs.Api/ # Minimal API + Scalar + MCP server at /mcp — composition root
├── AgentOs.Web.Shell/ # Desktop shell: window manager, Dash, toasts, Btn/Field/Panel/Icon UI kit
├── AgentOs.Web/ # Blazor Server desktop + product apps — composition root
├── AgentOs.RemoteAgent/ # Standalone dev-machine agent that pairs to the hub
└── AgentOs.ServiceDefaults/ # OpenTelemetry, health checks, HTTP resilience, Kestrel limits
infra/AgentOs.AppHost/ # .NET Aspire orchestration (+ infra/keycloak realm & theme, azd hooks)
samples/AgentOs.Plugins.Sample/ # A working out-of-tree plugin
tests/ # xUnit v3 (1,000+ test cases) + Playwright E2E
tools/agentos-vscode/ # VS Code extension: pair the editor as a runner
Cross-module references are explicit and minimal: Llm, Pipeline, Tools and Workspaces → AppConfig (settings + per-tenant store); Tenants → Identity (JWT scheme); Integration → Llm + Tools (its tools register through the tool registry). Hosts reference every module.
Agents call tools (build_verifier, MCP-exposed GitHub / filesystem actions, custom ITools)
through the gateway: LlmRequest.Tools = ["build_verifier"] makes PooledChatLlmClient resolve each
name in IToolRegistry, adapt it into a Microsoft.Extensions.AI.AIFunction, and run the tool-call
loop with FunctionInvokingChatClient. Every invocation passes IToolPolicy (gate) and
IToolInvocationLog (evidence), so refused and successful calls alike are auditable.
AgentOs.Modules.Mcp connects to upstream MCP servers and registers their tools as
{server}.{tool} — local and remote tools share one registry. The Api also serves MCP at /mcp
(Streamable HTTP) with run_pipeline, list_runs and get_run, so any MCP host (Claude Desktop,
VS Code, another orchestrator) can drive AgentOS.NET.
UserStory ─► RequirementAgent ─► RequirementSpec ─► CodingAgent ─► CodeArtifact
│
▼
PipelineResult ◄── QaAgent ◄── TestingAgent
│
└── run history (pipeline.pipeline_runs + run_metrics)
QA scores the triple and decides "ship it" or "iterate" (capped by NMax, default 3). The Workflow
editor runs the same agents as a graph, including Parallel, Merge, If/Else, Loop and Human nodes.
| Module | Schema | Endpoints | Notes |
|---|---|---|---|
| AppConfig | config |
/settings* |
Settings registry (descriptors, precedence, locks, audit) over an encrypted per-tenant KV store. |
| Llm | — | /llm/test |
Keyed ILlmClient per provider, key pools, failover, cost calculator, model capabilities. |
| Pipeline | pipeline |
/pipeline, /pipeline/stream, /requirement, /code, /test, /qa, /runs* |
Agents, orchestrator, graph executor, run history (jsonb artifacts). |
| Identity | — | — | ITenantContext + Admin/Member/PlatformAdmin policies. Scheme per host (Api = JWT bearer, Web = cookie + OIDC). |
| Tenants | tenants |
/tenants*, /invitations* |
Tenant registry, member lifecycle via the Keycloak admin API, signup + invitations, audit. |
| Tools | tools |
— | Tool registry, per-tenant policy (allowlist), invocation evidence. |
| Integration | — | — | IGitHubPrService + IBuildVerifier (container sandbox), registered as tools. |
| Mcp | — | /mcp/servers |
MCP client for upstream servers; the Api also serves /mcp. |
| Workspaces | workspaces |
/workspaces* |
Connected repos/boards + IWorkspaceConnector (GitHub, Azure DevOps). |
| Sessions | sessions |
/sessions*, /runners*, /pair/* |
Remote-runner registry, pairing handshake, live session-run feed. |
| RemoteAgent | — | /hubs/remote-agent (SignalR) |
Dispatches work to a paired dev-machine agent (no server API tokens). |
Agents depend only on ILlmClient; LlmClientFactory resolves a keyed implementation by provider:
| Provider | Backend | Owning module |
|---|---|---|
Claude (alias Anthropic) |
Anthropic SDK, pooled over multiple keys | Modules.Llm |
AzureOpenAI |
Azure.AI.OpenAI, pooled over multiple keys |
Modules.Llm |
OpenAI · Gemini (alias Google) · xAI (alias Grok) |
Bring-your-own-key vendors over the OpenAI protocol — fixed endpoints, tenant-scoped keys, built-in prices | Modules.Llm |
OpenAICompatible (aliases Ollama, vLLM, Local) |
Any /v1/chat/completions server via the OpenAI SDK — self-hosted / air-gapped, your own prices |
Modules.Llm |
MAF |
Microsoft Agent Framework over Azure OpenAI | Modules.Llm |
RemoteAgent |
SignalR-dispatched dev-machine agent | Modules.RemoteAgent |
Offline |
Keyless canned output — only at the end of a failover chain when offline fallback is on | Modules.Llm |
Routing per call: Llm:ForceProvider (if set) → the agent profile's provider → Llm:Provider.
Llm:Fallbacks adds inter-provider failover (e.g. Claude → AzureOpenAI) above each provider's own
key-pool failover. Keys and routing are read per request for the caller's tenant (short cache),
so a key saved in Settings is used by the next call.
Default agent profiles (code defaults — change them in Settings → Agent profiles):
| Agent | Provider | Model |
|---|---|---|
| Orchestrator | Claude | claude-haiku-4-5 |
| Requirement | Claude | claude-sonnet-4-6 |
| Coding | AzureOpenAI | gpt-4.1 |
| Testing | AzureOpenAI | gpt-4o-mini |
| QA | Claude | claude-haiku-4-5 |
| IssueWork / Decomposer | Claude | claude-sonnet-4-6 |
The model picker lists every priced model for the chosen provider with its list price (e.g.
claude-haiku-5-5 · $0.1 / $0.5 per 1M tokens), plus Custom… for Azure deployment aliases.
UI-first (ADR-0011). Everything product-related is set in the desktop and applies on the next call:
- Home → Schedule… — run a story once or on a schedule (every day / weekdays / weekly / cron, UTC; at most every 15 minutes). Scheduled runs go through the same budget gate and run limits as any run, and every change is audited.
- Settings → AI → Model keys — one card per provider: key, endpoint / default model, and a Test button.
- Settings → AI → Routing — default and forced provider, offline fallback.
- Settings → AI → Agent profiles — the model per agent (the provider follows the model) and the QA-loop cap; provider, temperature and max output tokens under Show advanced.
- Settings → Platform (
platform-adminonly) — remote auto-approve, fail-closed tool policy, allowed self-hosted Git hosts, build sandbox (mode, image, quotas), offline fallback. - Settings → Prompts — per-tenant system prompts for the Requirement, Coding, Testing and QA agents.
- Settings → Audit log — who changed what (settings, keys — never their values —, members, sign-in), CSV export.
Your repo's own rules. When a Board ticket runs against a repo, every agent follows the repo's agent
instructions — .agentos/instructions.md, AGENTS.md, CLAUDE.md, .github/copilot-instructions.md,
.cursorrules (budgeted to ~4k tokens, fenced below the agent's own rules); the session feed names the
files it used.
appsettings.json holds bootstrap keys only — database, sign-in, logging, operator locks. Any setting
can still be seeded from the environment (Agents__Coding__Model=gpt-5.4, Llm__Claude__ApiKeys__0=…).
The full reference — what you set at deploy vs in the UI, precedence, Production locks — is
docs/CONFIG.md.
ConnectionStrings:DefaultConnection (Postgres) is the only required wiring; each module owns its
DbContext, schema and migration history and migrates at startup. Aspire provides it on the full stack.
The Api requires a database by default; to run it alone:
dotnet user-secrets set "ConnectionStrings:DefaultConnection" \
"Host=localhost;Port=5432;Database=agentos;Username=postgres;Password=postgres" --project src/AgentOs.Api
# …or boot it stateless (no-op repositories):
export Persistence__RequireDatabase=false # PowerShell: $env:Persistence__RequireDatabase = "false"
dotnet run --project src/AgentOs.Api # Scalar UI at https://localhost:5080/scalar/v1| Host | Authentication | Tenant context |
|---|---|---|
| Api | Keycloak RS256 bearer, validated against the realm JWKS | HttpTenantContext reads the tenant claim + realm roles |
| Web | Cookie + OpenID Connect against Keycloak; Auth:DevAutoLogin (Development only) for standalone runs |
Same HttpTenantContext; anonymous requests fall back to the default tenant |
Roles: admin (tenant admin), member, and platform-admin (platform-wide settings; the seeded
operator has it). POST /tenants provisions a tenant + admin user in Keycloak. Aspire imports the
agentic realm from infra/keycloak/, so a multi-tenant smoke test is one command away.
Row-level isolation: pipeline runs, run metrics and orchestrations, sessions/runners and workspaces
carry a TenantId with an EF global query filter on ITenantContext.TenantId; writes stamp the
tenant. Settings and keys are stored per (tenant, key) and always read for an explicit tenant.
Everything except /, /health, /alive, /health/llm and /version requires an authenticated
principal: the Api validates a Keycloak bearer token; the Web uses a cookie session
(/account/login → OIDC). The complete, always-current list is the Scalar UI at /scalar/v1.
The most used routes:
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/pipeline · /pipeline/stream |
signed in | Run the full flow (SSE progress on /stream) |
POST |
/requirement · /code · /test · /qa |
signed in | Run one agent on its own |
GET |
/runs, /runs/{id} |
signed in | Persisted runs |
GET |
/tenants/me |
signed in | Resolved tenant, user, roles |
GET POST |
/tenants, /tenants/{id}/members |
Admin | Tenants and members |
GET POST DELETE |
/settings* |
Admin | Legacy runtime-config endpoint (see CONFIG.md) |
POST |
/llm/test |
signed in | Probe the configured provider with a minimal call |
| — | /mcp |
signed in | AgentOS.NET as an MCP server |
GET |
/health · /alive · /health/llm |
none | Readiness, liveness, provider-key readiness |
- Implement
ILlmClientin a module that owns the SDK reference. - Register it as a keyed service under a canonical name:
services.AddKeyedSingleton<ILlmClient, MyProviderClient>("MyProvider");
- Add prices for its models to
CostCalculator(unpriced models are charged at the fail-closed rate).
Use the bundled agent-scaffold skill (/agent-scaffold {Name}) — it generates the contract,
implementation, DI registration and xUnit stubs. Give it an agent profile in AgentsOptions so it
appears in Settings → Agent profiles.
Declare a SettingDescriptor in your module's ISettingsContributor (key, kind, scope, default,
validation, label) and read it with ISettings at the point of use. It appears in the desktop
automatically — don't add an appsettings key.
- Create an
AgentOs.Modules.Xclass library referencingDomain+SharedKernel. - Implement
IModule(+IEndpointModule/IInitializableModuleas needed) inXModule.cs. - If it persists state, add an
XDbContext(schemax), a design-time factory and a migration:dotnet ef migrations add Initial \ --project src/AgentOs.Modules.X --startup-project src/AgentOs.Modules.X \ --output-dir Persistence/Migrations --context XDbContext
- Add the assembly to each host's
AddModulesFromAssembliescall.
For an extension that ships outside the solution, implement IAgentOsPlugin
(src/AgentOs.SharedKernel/Plugins/) and drop the assembly into the host's plugins/ folder — no
compile-time reference. A plugin has the same DI surface as a module (tools, LLM providers, desktop
windows). Use /plugin-scaffold {Name}; samples/AgentOs.Plugins.Sample is a working example.
azd up # provision + deploy to Azure Container Apps from the Aspire AppHostA post-deploy hook aligns the Keycloak realm with the cloud URLs, rotates the client secret and seed
passwords, and ensures the platform-admin role. Production applies operator locks for the riskiest
settings. Full guide: docs/DEPLOYMENT.md and infra/README.md.
Issues and PRs are welcome — see CONTRIBUTING.md and SECURITY.md.
- CI (
.github/workflows/ci.yml) runs on pushes tomain/develop/refactor/**and on PRs tomain: Release build (warnings are errors) → tests → a coverage floor → the dev-auth UI E2E that opens every desktop app. CodeQL and dependency review run alongside. The real-auth full-stack E2E is a local suite (RUN_AGENTOS_E2E_REAL=trueagainst the Aspire stack). - Commits follow Conventional Commits.
- Project-specific Claude Code skills live in
.claude/skills/(agent-scaffold,prompt-tune,cost-report,design-review,plugin-scaffold).








