Skip to content

Latest commit

 

History

76 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Governed Support Ops Agent

A reference build for governing what an AI agent is allowed to do: deterministic, policy-as-code authorization on every tool call, checked before the action runs and logged after. Not output guardrails.

ci evals license mcp

Live microsite: demo.sarthak-gupta.com


Most "agent safety" filters what the model says. This governs what the agent is allowed to do. Every tool call (a refund, a deletion, an account change) is checked against policy-as-code that runs outside the model, returns a deterministic allow or deny, routes destructive actions to a human, and writes a record you can replay.

It's a runnable reference build, not a product to install. The agent works a simulated support queue end to end: reads Notion, checks HubSpot, files GitHub issues, posts to Slack, and stops at a human gate before anything customer-facing. The Cedar policies, the eval suite that proves each one fires, and the audit log are all here to read and diff.

The agent proposes deleting a customer account; Cedar hard-forbids it and returns the reason chain.

A customer asks the agent to delete their account. The agent plans deleteAccount(ACC-5), agent-shield evaluates it against Cedar before any call leaves the process, and policy 06 hard-forbids it with a reason chain mapped to OWASP ASI10. Replay this and three more on /refusals.

Isn't this just guardrails?

Guardrails put the safety in a prompt or a text filter, and a model can be talked out of a suggestion. This authorizes the agent's actions instead. The check runs outside the model, returns a deterministic allow or deny on the specific call and its inputs, and leaves a record. Guardrails are one layer. This is the layer that decides whether the action happens at all.

Why this isn't another chat demo

Three things a chatbot wrapper does not do:

  • A policy decision point authorizes every action. Each tool call is checked against Cedar policies in packages/policies/ (the exact files the microsite renders) by a decision point that runs outside the model and returns a deterministic allow or deny. A deny comes back with a human-readable reason chain mapped to an OWASP Agentic threat, not a shrug.
  • There is a kill-switch and a circuit breaker. An operator can halt an in-flight run at the next step boundary, and a runaway loop trips a cost ceiling before it bills you for an overnight retry storm.
  • Indirect prompt injection is handled, not hoped away. Untrusted content the agent retrieves, like a poisoned knowledge-base page, is scanned and quarantined before it reaches the planner, so injected instructions never become actions.

A runaway sub-goal loop billing the model each iteration; the circuit breaker halts the run at the $0.50 ceiling.

The cost overlay from /traces: a runaway loop bills a model call each iteration, and the circuit breaker halts the run the moment cumulative cost crosses $0.50.

Two artifacts in one repo

  1. The Governed Support Ops Agent. A runnable, end-to-end demo: watches a simulated Zendesk queue, reads Notion docs, checks HubSpot, files real GitHub issues, posts Slack updates, and requires human approval before any customer-facing action.

  2. @sarthak/agent-shield. The governance layer extracted from the demo as a standalone reference: Cedar policies + audit log + kill-switch + MCP scope check + circuit breaker, behind one public API: shield({ policies, audit, killSwitch, scopeCheck, breaker }), with wrap() guarding each workflow step and authorize() gating each tool action. The agent in this repo is its reference integration.

Both are shown on the microsite (demo.sarthak-gupta.com): agent demo on /, agent-shield on /shield.

Architecture

                      ┌──────────────────┐
                      │  Zendesk ticket  │
                      └────────┬─────────┘
                               ▼
  apps/agent · Mastra v2 workflow
    ingest → triage → policy-check → approval-gate → execute → audit
                                          │             │         │
        Slack approval ◀──────────────────┘             │         └──────────▶ Langfuse
        (suspend / resume)         approve / reject      │ tool calls           (OTel gen_ai.* spans)
                                                         ▼
  agent-shield · policy decision point: authorizes every governed tool call
    Cedar policies · audit log · kill-switch · scope-check · circuit breaker
                                                         │
                                                         ▼
    zendesk (mock)    notion (mock)    hubspot (mock)    github (real API)

The same structure renders as a hand-authored SVG on the microsite /architecture page, sourced from app/lib/architecture.ts.

Status

Complete and runnable end-to-end. A Mastra v2 workflow wrapped by agent-shield (the policy decision point) takes a ticket through classify, retrieve, policy-check, human approval, governed execute, and audit. Every governed tool call gets a deterministic allow or deny before it runs, with a full OpenTelemetry trace tree in Langfuse.

Capability State
8 Cedar policies mapped to the OWASP Agentic Top 10 (ASI01-ASI10)
8 scenarios fire end-to-end: allow, approval gate, PII redaction, delete refusal, indirect injection, cost breaker, kill-switch, cross-tenant
MCP servers: zendesk / notion / hubspot mocks + real-API GitHub (full 2025-11-25 spec)
Slack approval with suspend/resume on a Postgres checkpoint
OTel GenAI spans to Langfuse, agent.policy.decision.reasons on every governed step
Eval suite: custom 21/21, OWASP-ASI 10/10, InjecAgent subset 200/200
Microsite: 11 routes, every page real (policies, traces, refusals, evals, shield, trust, tenants)

See BUILD-SPEC.md for the architecture and the verification gate.

Demo boundaries (what production adds)

This is a reference build. The governance pattern is real; several parts are deliberately demo-grade, and pretending otherwise would defeat the point of an evidence-first artifact:

  • Audit durability. The audit sink here is in-memory, mirrored into Langfuse traces. AuditSink is the interface where a production integration puts durable, append-only storage with explicit delivery semantics — including which action classes must fail closed when the write fails.
  • Policy facts. Tenant, resource sensitivity, and account state come from demo fixtures and call arguments. In production these facts must be resolved from systems the agent cannot influence, with time-of-check/time-of-use handled at that resolution boundary.
  • Approval evidence. The Slack approval suspends and resumes on a Postgres checkpoint, which proves the control flow. A production approval record additionally binds the exact payload digest, policy version, resolved facts, approver identity, expiry, and the execution that consumed it.
  • Scope discovery. Servers declare required scopes via the agent-shield/requiredScopes tool _meta key — an agent-shield convention, not the MCP spec's WWW-Authenticate step-up flow (SEP-2350). Tools that declare nothing are denied by default.
  • Injection detection. The detector is deterministic and signature-based: enough to prove quarantine-before-planner, not a general prompt-injection defense.
  • Cost accounting. The circuit breaker meters spend from hard-coded gpt-4o-mini list prices; production metering belongs at the gateway.

These are the boundaries an Agent Readiness Audit walks through on a real system. Here they are at least written down.

Run locally

Requires Node 22+, pnpm 9, Docker Desktop, ~3 GB free disk for images on first pull.

git clone git@github.com:Sart-Hack/governed-support-agent.git
cd governed-support-agent

corepack enable && corepack prepare pnpm@9.15.0 --activate
pnpm install

cp .env.example .env             # demo secrets, do not deploy
pnpm stack:up                    # pulls + starts Langfuse v3 + Postgres + ClickHouse + Redis + MinIO + Bifrost
pnpm stack:verify                # 7/7 service smokes

pnpm --filter @gsa/microsite dev # microsite on http://localhost:3000
pnpm --filter @gsa/agent dev     # agent CLI in watch mode

Langfuse UI: http://localhost:3001, login demo@example.com / demodemo.

Stack control:

pnpm stack:up        # start all services
pnpm stack:verify    # health-probe each
pnpm stack:bootstrap # register the 3 mock MCP servers as Bifrost clients (idempotent)
pnpm stack:logs      # follow logs
pnpm stack:down      # stop, keep volumes
pnpm stack:wipe      # stop and delete volumes

pnpm mcp:servers     # run the 4 MCP servers (zendesk 7002, notion 7003, hubspot 7004, github 7005)

The MCP servers run as host processes; Bifrost (in Docker) reaches them via host.docker.internal. Start them with pnpm mcp:servers, then pnpm stack:bootstrap registers them with the gateway. Bootstrap is idempotent: it reconciles to a known state every run, so a fresh clone needs only pnpm stack:up && pnpm stack:bootstrap.

Run the agent

With the stack up and pnpm mcp:servers running in another terminal (and an OPENAI_API_KEY in .env):

pnpm demo                  # scenario 1: triage a billing ticket end-to-end,
                           # then print the Langfuse trace link

pnpm --filter @gsa/agent scenario TCK-3        # scenario 3: drafts a customer reply,
                                               # suspends at the Slack approval gate
pnpm --filter @gsa/agent scenario:resume <runId> reject "needs detail"   # revise branch

pnpm --filter @gsa/agent scenario2             # scenario 2: runaway loop hits the
                                               # $0.50 circuit-breaker ceiling
pnpm --filter @gsa/agent kill on               # scenario 7: kill-switch halts in-flight runs

Every tool call is double-gated (Cedar policy + scope-check), every step is audited, and a full trace tree lands in Langfuse. Slack and GitHub run against a local stand-in / mock until you add SLACK_BOT_TOKEN or GITHUB_TOKEN to .env.

Local CI parity

pnpm install --frozen-lockfile
pnpm lint
pnpm typecheck
pnpm test
pnpm --filter @gsa/microsite build
pnpm --filter @gsa/agent build

Same commands run in .github/workflows/ci.yml. ~46s on a clean runner.

Source of truth

  • BUILD-SPEC.md: architecture, scenarios, hour budget, risk register, verification gate. Read it before writing code.
  • CLAUDE.md: orientation for Claude Code sessions, including locked decisions, anti-patterns, cut list, copy guide.

License

Apache 2.0. See LICENSE.

About

Deterministic, policy-as-code authorization for what an AI agent is allowed to do. Every tool call checked before it runs, logged after. A runnable reference build, not output guardrails. https://demo.sarthak-gupta.com

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages