Aggrete · the code-of-conduct proxy for AI assistants
Every question is allowed. The answer they add up to is not. Aggrete sits between every assistant and your connectors, keeps a tally of what each person has already seen, and refuses the question that completes the picture — before the backend is ever asked. One policy file. One service. Every assistant your company runs.
Pick where you are — the why and the how for your situation
Why you need it: your assistant already has every connector you do. It will happily answer four innocent questions that add up to one you'd never ask. Nothing on your laptop stops that today.
stdio, identity from the config file. Good enough for one laptop; advisory as enforcement, and we say so.
.mcp.json registers aggrete; approve it once. Ask the four questions in order.upstreams: entry and a domains: pattern; drop your policy into coc.yaml.# Python ≥ 3.10 pip install aggrete # or: git clone github.com/cjohannsen81/aggrete aggrete --config proxy.config.yaml # stdio, for one client # register anywhere outside the repo folder claude mcp add aggrete -- aggrete --config /abs/path/proxy.config.yaml
upstreams: hr: url: https://mcp.workday.example/mcp headers: { Authorization: "Bearer ${HR_TOKEN}" } domains: "hr__*": hr-personnel
Why you need it: every teammate's assistant can run the same patient join across HR, finance and ops — from Claude, Cursor and Copilot at once. Per-request guardrails can't see across calls. Aggrete keeps one tally per person, wherever they ask from.
Aggrete runs as a service in your VPC and holds the connector credentials. Users never do — so the only route to a connector is through the policy.
email claim.https://aggrete.internal/mcp as a remote MCP server in whichever assistant they use. One history per person, across all of them.helm install aggrete deploy/helm/aggrete \ --set auth.issuer=https://login.example.com/ \ --set auth.audience=https://aggrete.internal/mcp \ --set ingress.enabled=true --set ingress.host=aggrete.internal \ --set-file coc=coc.yaml
# or plain Docker
docker run -p 8080:8080 -v ./etc:/etc/aggrete:ro \
-e REDIS_URL=redis://redis:6379/0 -e HR_TOKEN=… \
ghcr.io/cjohannsen81/aggrete
# what the client sees without a token
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://aggrete.internal/.well-known/oauth-protected-resource/mcp"
Why you need it: your code of conduct already forbids this kind of aggregation. Right now it's a PDF. Aggrete makes it the thing that actually decides, in every assistant, with an audit row per call for the rule owner.
There is no marketplace for "mandatory". A company-wide default is the team setup plus two controls: the assistant clients may only talk to Aggrete, and the connectors may only talk to Aggrete.
coc.yaml from the handbook, then have HR and Legal own the file.managed-mcp.json via MDM; Claude Enterprise org connectors; Copilot, Cursor and ChatGPT org policies. Allow the Aggrete URL, disallow user-added servers.# Claude Code — managed-mcp.json shipped by MDM (admins only) { "mcpServers": { "aggrete": { "type": "http", "url": "https://aggrete.internal/mcp" } } } # managed-settings.json: allow only that server { "allowedMcpServers": [ { "serverName": "aggrete" } ] }
# draft the policy from the document you already have aggrete-ingest handbook.pdf --domains proxy.config.yaml -o coc.yaml # every rule ships with its own tests; CI runs them on each change
Setting names are as of early 2026 — check each vendor's enterprise docs for the current keys. The pattern is stable even where the key names move.
Why you need it: your gateway authorizes and logs each call. It doesn't remember what the same user saw two calls ago — and that's exactly where the leak lives. Aggrete is the missing tally, as two hooks.
Gateways authorize and log individual calls well. What they don't keep is the running tally per user across calls. Aggrete's engine slots in as two hooks or as ASGI middleware, and uses the identity your gateway already established.
before(user, tool) ahead of forwarding, after(user, tool, result) with the response.from aggrete.plugin import PolicyHook, AggreteMiddleware hook = PolicyHook("coc.yaml", domains={"hr__*": "hr-personnel"}, store=RedisStore(redis_client)) # in your plugin v = hook.before(user, tool) # v.allow, v.message v = hook.after(user, tool, result_text) # records, re-evaluates # or around the whole server app = AggreteMiddleware(app, hook, identity=lambda scope: scope["state"]["user"])
Proof
Real transcript through the proxy. Three innocent calls go through; the fourth would join personnel, budget and rota on the same six people — so it never reaches the ops connector.
The backend never sees the question. Every call is judged against what this user already holds before it is forwarded. A refused call is not fetched-then-redacted — the connector is simply never contacted.
The problem
Prompt A · blunt
"Give me the list of people on the platform team who are about to be managed out."
Prompt B · patient
How it works
Approve before forwarding. The question reaches the backend only after the proxy has judged it against what this user already holds. If one more domain would complete a forbidden join, the call is answered by the proxy itself — the connector never receives it, no data leaves it, and there is nothing to redact.
Record what was actually handed over. Person identifiers are pulled from the result, linked (an email and an employee ID on one record are one person), and stored per user with a TTL. Counts and overlaps are re-evaluated.
Return the clause, not a stack trace. A denial comes back as text: the rule, its owner, and the sanctioned path. The assistant relays it instead of retrying, so a blocked user learns where to go rather than how to rephrase.
Enforcement is deterministic. coc.yaml is the source of truth. Three rule types — domain_join, entity_budget, domain_block — and every rule ships with an allow test and a deny test or CI fails.
Your document
Drop your code of conduct here. The page reads it locally — nothing is uploaded — pulls out every obligation clause, and drafts the coc.yaml skeleton those clauses map to.
Full conversion, PDFs included: the browser draft is a heuristic pass. The repo ships an ingest command that has Claude read the document — PDFs natively — and produce complete rules with domains and tests, then runs every generated test through the real engine before writing anything.
python -m aggrete.ingest handbook.pdf \ --domains proxy.config.yaml -o coc.draft.yaml
PDF as a document block; DOCX, Markdown, text as text.
Rules in the exact coc.yaml schema. Clause text verbatim. Everything alert, never deny.
Each rule's own tests run through Engine. A draft that fails its tests is not written.
Clauses no data proxy can enforce — tone, harassment, expenses — are listed separately with the reason.
Each clause got a rule type by keyword — combined / together → domain_join, list / roster / bulk → entity_budget, never / under no circumstances → domain_block. Domains are placeholders. Treat this as the outline you hand the clause owner, not the file you deploy.
What it doesn't do
EMAIL_KEYS / ID_KEYS against your connectors first.