Skip to content

Add the AI agent path - #2

Merged
karlschriek merged 3 commits into
mainfrom
merge/agent
Jun 5, 2026
Merged

Add the AI agent path#2
karlschriek merged 3 commits into
mainfrom
merge/agent

Conversation

@karlschriek

Copy link
Copy Markdown
Contributor

Closes #1.

Lands the full agent feature described in the linked issue: a registered Agent entity, server-side Missions that match-and-dispatch invocations to it, an Agent process with a Claude sidecar, an MCP surface for the sidecar to use, and per-scope authorization + audit attribution end-to-end.

The three mission types are still quite simple, but the full machinery for much more complex (and useful!) ones are there in this PR.

What ships

Server — entities, REST, dispatch

  • Agent entity alongside Runner: registration, identity, role assignments, lifecycle endpoints. Mirrors Runner as closely as possible — same shape of secured repositories, controllers, DTOs, role enum, and assignment subclasses.
  • AgentRole + per-scope AgentAssignment entities (AgentStackAssignment, AgentNamespaceAssignment, AgentModuleAssignment) + Agent.IsAssignedToAllModules flag for the broad-allow case. Authorization is the supply/demand AND: scope owner controls Mission creation; Agent owner controls Assignment creation; dispatch enforces the conjunction.
  • Mission entities at every scope: OrganizationMission, StackMission, NamespaceMission, ModuleMission. Each carries a MissionType, an optional SidecarName, and per-scope FKs. Created through the same REST + Razor + Terraform-provider channels every other resource uses.
  • Layer-1 match-and-dispatch (MissionMatcher / MissionDispatcher): competing consumers triggered by saga events (ApplyModuleCompleted/Failed/Cancelled, DealWithApprovalStatus declined path). Walks the relevant Missions, resolves matching Agent supply via AgentSupplyResolver, dispatches to a chosen agent.
  • Layer-2 agent endpoints (AgentEndpoints): one named endpoint + DTO + handler per mission type (AutoDiagnose, ApprovalRecommend, SummarizeJob, CancelMission). No generic invoke shim — every mission has an explicit, typed wire shape.
  • AgentHub (SignalR) for the agent's persistent connection, mirroring RunnerHub. Carries dispatch, heartbeat, log streaming, run lifecycle (MissionStarted / MissionHeartbeat / AddMissionLogs / MissionCompleted / MissionFaulted / MissionCancelled).
  • AgentClaimAuditMiddleware plus an agent_id claim issued via OpenIddict, so any REST call an Agent makes is attributable to it. Agents authenticate as Service Principals; the extra claim threads the Agent identity through.

Server — MCP surface

  • MCP server (AddSnapCdMcpServer + MapMcp("/mcp")) exposing Resources, Tools, and Prompts. License-gated to Enterprise; bearer-authenticated.
  • SnapCd.Mcp.Generator — a source generator that emits *McpSurface.g.cs + *McpResourceSurface.g.cs files from [ExposeAsMcpTool] and [ExposeAsMcpResource] attributes on controllers. The MCP surface is generated, not hand-maintained.
  • Prompt registry + skill catalog — Missions are addressable via MCP prompts/list and prompts/get. The mission editor's skill picker consumes the same endpoint, so the UI is automatically in sync with the available skills.
  • Resource URIs follow snapcd://orgs/{orgId}/{entity-plural}/{entityId}/{sub-path}; org scoping is enforced server-side from the bearer claim.

Agent process + Claude sidecar

  • SnapCd.Agent — a long-running .NET process with a Missions orchestrator that:
    • Maintains the SignalR connection to AgentHub (reconnect, heartbeat, buffered log batches).
    • Registers one handler per mission endpoint.
    • Forwards each invocation to a named sidecar over a streaming HTTP transport, streaming log events back as they arrive.
    • Per-run cancellation (an inbound CancelMission cancels the matching sidecar invocation).
    • Bounded concurrency so a burst of triggers can't exhaust the host.
  • SnapCd.Agent.AppHost — .NET Aspire host that wires the Agent + sidecars locally for dev.
  • Claude sidecar (SnapCd.Agent/Sidecars/Claude/) — a FastAPI service that:
    • Resolves the named skill against the MCP prompts/get endpoint (no bundled local skill copies).
    • Hands the rendered prompt + the MCP servers (snapcd + reports + any externally-configured ones) to the Claude Agent SDK.
    • Captures structured reports via an in-process MCP Tools server and folds them into the final SSE result.
    • Streams every assistant turn back as SSE log events; emits a final result event with the run outcome.

Mission types

Three mission types ship in this PR. Each has a typed request DTO, a Layer-2 agent endpoint, a built-in skill that resolves via MCP prompts/get, and a per-mission consumer that drives the invocation.

  • AutoDiagnose — fires when a ModuleJob ends in failure or cancellation (ApplyModuleFailed, ApplyModuleCancelled, DestroyModuleFailed, DestroyModuleCancelled, plus the declined-approval path through DealWithApprovalStatus). The agent walks the failed job's logs, the module's resolved configuration, and recent runs of related modules via MCP, then writes back a structured diagnosis: a free-text explanation plus a DiagnosisCategory enum so the UI can colour-code and the operator can filter. Surfaced in the Module Job's Missions tab and on the Module dashboard.

  • ApprovalRecommend — fires when a ModuleJob reaches awaiting-approval state. The agent reads the proposed plan, the module's history, and any related changes via MCP, then writes back an approve / decline recommendation with reasoning. The recommendation is advisory — the operator still clicks the approve / decline button — but it sits next to the plan diff so it's there when the decision is being made.

  • SummarizeJob — fires when a ModuleJob completes successfully (ApplyModuleCompleted, DestroyModuleCompleted). The agent produces a brief human-readable summary of what actually changed: resources added / modified / destroyed, notable side effects, anything that looked unusual mid-run. Persisted on the run record and rendered as the headline of the Missions tab so a quick scan of a module's history reads as prose, not raw plan output.

All three share the same dispatch and result-capture machinery — the differences are entirely in the skill prompt, the structured-report shape, and the trigger event. New mission types slot in by adding a MissionType enum value, a request DTO + endpoint constant, a consumer, and an MCP prompt — no changes to the dispatch fabric.

Mission shape, audit, UI

  • ModuleJobMission + ModuleJobMissionRun child tables — durable per-attempt records for each mission invocation against a ModuleJob. Idempotent under retry.
  • Agent as a first-class audit principal alongside User and ServicePrincipal — CreatedByAgentId / ModifiedByAgentId columns on audit-tracked tables; PrincipalDiscriminator extended.
  • UI: Missions tab on the Module Job view, rendering each run's status, duration, token usage, tool calls, agent log tail, error detail, and the captured diagnosis category for AutoDiagnose. Snapcd's standard black/white button style throughout.
  • UI: Online badge on the Runners page mirroring the existing Agents page.
  • Sidecar selection — Missions carry an explicit SidecarName so an Agent hosting multiple sidecars can be routed to the right one. Default-sidecar resolution when omitted.

Other generators landing alongside

  • SnapCd.Settings.Generator.{Server,Runner,Agent} — per-component settings JSON-schema generators emit the schema for each component's strongly-typed Settings classes. Not strictly part of the agent path but landed together because they're touched by the same component split.

…ions, dispatch, MCP server, and authorization +semver: minor

  - Mission entities + REST + Layer-1 dispatch + Layer-2 agent endpoints (server)
  - Agent process with Claude sidecar; SignalR-streamed results into ModuleJobMission/Run
  - MCP server surface (Resources/Tools/Prompts) over snapcd:// URIs
  - Mission authorization: AgentRoles, per-scope AgentAssignments, IsAssignedToAllModules
  - Agent as a first-class audit principal alongside User and ServicePrincipal
  - ILicenseInfoProvider in Server.Core abstracts the license-tier lookup; concrete LicenseService lives in Server.Host
  - SnapCd.Mcp.Generator emits the MCP tool/resource surface from [ExposeAsMcpTool] / [ExposeAsMcpResource] attributes on controllers
  - SnapCd.Settings.Generator.{Server,Runner,Agent} emit per-component settings JSON schemas from the strongly-typed Settings classes
  - Create a more tenable Test approach for secured repos
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

All contributors have signed the CLA. ✅
Posted by the CLA Assistant Lite bot.

@karlschriek

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@karlschriek
karlschriek merged commit a3d209a into main Jun 5, 2026
2 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 5, 2026
@karlschriek
karlschriek deleted the merge/agent branch August 4, 2026 21:35
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AI agents for SnapCD

1 participant