How it works

One checkpoint between your assistants and your systems.

Your AI assistants stop talking to HR, finance and Drive directly. They talk to Aggrete, and Aggrete talks to those systems. Because it is the only way in, nothing gets around the rules, and because assistants already know how to talk to it, nobody installs anything on their laptop.

Every request is checked against two things: your rules, and what that person has already looked up recently. A rule makes the decision, not another AI, so the same request always gets the same answer and you can always see why.

1 · Who is asking
auth.py
It knows who is asking. People sign in with their company account, so every decision is about a real, named person.
The caller is derived from the OAuth token (JWT via JWKS/PEM) over HTTP. On stdio, identity is advisory.
2 · Remember
accumulator.py · entities.py
It remembers what they have seen. Each answer is noted: which system it came from and which people were in it, for a set time.
Person IDs are extracted from each result and tallied per user, per domain, with a TTL. Email is the canonical key across connectors.
3 · Decide
policy.py · pre_call / post_call
It decides. If the request crosses a line, it is refused before anything is fetched. If the line only shows up in the answer, the answer is withheld.
The call is matched to a policy domain and checked. A denied pre-call request never reaches the upstream; post-call denials redact the result.
4 · Record
audit.jsonl
It writes it down. One line per decision, and a refusal tells the person which rule applied and how to ask for an exception.
One JSON object per decision, to stderr and a file. The refusal carries the clause and a remediation path, not an opaque error.
Architecture

Where the pieces sit.

Your systems only accept requests that come from Aggrete, and Aggrete holds the logins to them, never the person or their assistant. A console, if you want one, only reads the two files Aggrete writes.

Assistants
Claude, Copilot, ChatGPT, Cursor
People sign in once with their company account. After that, everything their assistant does on their behalf goes to Aggrete, never straight to a system.
Aggrete proxy
mcp.yourco.com
Knows who is asking, what they have already seen today, and your rules. Says no before anything is fetched, and writes one line for every decision.
Your systems
HR, finance, Drive, Slack, CRM
Only reachable through Aggrete. Connect the systems you have; ready-made connectors for Drive, Slack, GitHub and more are included.
Policy & state
coc.yaml · Redis
Your rules live in one file you can read and version. What each person has looked up is kept in memory for a trial, or in a shared store when you run several copies.

The full diagram, the Drive connector and the sign-in flow are in the README.

Policy

Rule types.

Eight kinds of rule, each for a different way a request can go wrong. Before means it is decided before anything is fetched, so the data never moves. After means it can only be judged once the answer exists, so the answer is withheld. Any rule can be limited to certain people or dates.

rulewhenwhat it does
domain_joinbeforeStops harmless answers from adding up to a harmful one.The budget is fine, the staff list is fine, the rota is fine. Together, about the same people, they name who is leaving. The last piece is refused.Refuses the call that would complete a forbidden set of domains for one person (e.g. personnel + budget + rotation), with entity overlap required by default.
domain_blockbeforeA place assistants never go.Documents under legal hold stay closed to every assistant, whoever is asking.A domain that assistants may never reach (legal hold, privileged material).
wallbeforeOnly these people, and only until this date.The restructuring plan is for the planning team until the announcement. For everyone else the tool does not even appear.Who may reach a domain, and until when: embargoes, investigation walls, privilege, restricted health/absence data.
min_groupafterA figure about a handful of people is really about one person.Average pay for 200 engineers is a statistic. Average pay for two executives is their salaries.Aggregate-only answers: a result naming fewer than k people is treated as one person's data.
self_comparisonafterYour record or your team's, but not yours next to theirs.Approving your team's timesheets is your job. Lining yours up against a colleague's is not.The requester's own record placed next to colleagues' in the same domain, the precondition for benchmarking teammates.
entity_budgetafterNobody needs four thousand customer records in an afternoon.Looking up accounts is selling. Looking up all of them is an export, even though no single lookup was wrong.Caps the number of distinct people one user may accumulate in a domain over the window.
flowbeforeAfter reading a stranger's text, nothing leaves.An assistant that has just read a web page or an outside email cannot send, post or share until that task is over, so hidden instructions have nothing to steal.Prompt-injection shield: once a session has read untrusted content, it may not reach an egress domain. Any write counts as egress.
arg_matchbeforeThe same button, allowed or not, depending on what is asked of it.Export my team's accounts: fine. Export the whole company's: refused. Same tool, different request.Decides a call from its arguments, not just its kind: allow an export scoped to your own team, refuse the same export scoped to the whole company. Operators: equals, in, regex, gt, lt, exists, missing.

Anything that changes or sends something (create, update, upload, post, send) counts as a way out of the building, and a rule can apply to those alone. Rules come grouped into protection packs you switch on and off. Every rule can do one of three things when it fires: warn (let it through and note it, the safe way to start), refuse, or ask a person: the request waits, the rule's owner signs off, and it goes through for a few hours with their name on the record. In the policy file these are alert, deny and approve; a rule targets writes only with applies: write; every rule must ship with a test that passes and a test that is refused or held.

Beyond the rule types.

The rules decide what a request may add up to. These are the safeguards that run around every request. Each is a fixed check, not a judgment call, each is switched on in the config, and each writes to the same record.

safeguardwhat it does
checkAsk before you act."Could I pull these three things?" gets a yes or no, the rule, and how to proceed, without touching any data.Ask whether a sequence of calls would be allowed before running any of them: the decision, the rule, the clause and the fix, with nothing fetched. A built-in aggrete__check tool.
tool_integrityNotices when a tool quietly changes, or lies about itself.A connector that was approved last month and behaves differently today is flagged. One with hidden instructions in its description is blocked.Fingerprint every upstream tool on first sight; flag a later change to its description or schema (a rug pull), and scan descriptions for hidden instructions (poisoning). Alert or block.
rate_limitA speed limit per person.Stops a runaway assistant from making thousands of calls, and the bill that comes with them.A per-user ceiling on calls per window, shared across replicas via Redis. A denial-of-wallet and abuse control.
scan_inboundPasswords and keys never travel onward.If someone pastes a secret into a request, it is stopped or masked before any system sees it.Scan tool arguments for credential-shaped strings and block or mask them before they reach an upstream.
audit_forwardYour security team sees it where they already look.Every decision is copied to their monitoring tools as it happens. The sealed local record stays the official one.Stream each audit row to Splunk, Elastic, Datadog or syslog as it is written, off the hot path. The hash-chained local log stays the system of record.
approvalsA person can sign off.Instead of a flat no, the request waits, the rule's owner gets a message, and a yes lasts a few hours with their name on it.Human in the loop. A rule with action: approve holds the call, notifies by Slack-compatible webhook or command, and waits. Decide with aggrete approve <id>, POST /approvals/<id>/approve using the approver's own token, or the console. Configured approvers and the rule's owner may decide.
metricsDashboards and health checks.How much was allowed, refused and held, and whether every connected system is reachable, in the formats monitoring tools read.Prometheus /metrics derived from the same rows as the audit log, so the graphs and the record agree; /healthz and /readyz for the platform; OTLP export to any OpenTelemetry collector; OCSF API Activity events for the SIEM.
conformanceA test anyone can rerun.One command proves it does what this page says, and maps the results onto the security checklists buyers ask about.aggrete conformance runs sixteen checks against the real components and maps them onto the OWASP MCP Top 10, the OWASP Agentic Top 10, CoSAI and AIUC-1. --url runs the same scenarios black-box against any gateway. See the report.
aggrete-lintCatches rule mistakes before they go live.A rule that would never fire, an embargo that has already expired, a serious rule that only warns.Static checks on the policy for fail-open and dead rules: a critical rule that only alerts, an expired embargo, an unreachable domain. Exits non-zero for CI.

Inside the gateway you already run.

Some companies already route their assistants through a central gateway. You do not have to put Aggrete in front of it as one more stop. The gateway can simply ask Aggrete, before and after each request, "is this allowed?", and gets the same answer, the same masking and the same record as if Aggrete were in the path.

if you runhow it connects
agentgatewayPlugs into agentgateway's own policy hook, so it sees every request and every answer.aggrete extmcp --port 9001 serves agentgateway's native ExtMCP hook over gRPC: both phases, tools/list filtering, rewritten arguments and redacted results. One mcpGuardrails processor in the gateway config, with metadata: {user: jwt.email, tool: mcp.tool.name}. pip install "aggrete[agentgateway]".
anythingA simple web address any system can ask: here is who, which tool, and what for. Allowed?POST /v1/decide with a subject, a tool and its arguments (request phase), then the result (response phase). Answers allow, deny, hold or rewrite.
an AuthZEN PEPThe industry-standard way to ask an authorization question, so tools that already speak it need nothing custom.POST /access/v1/evaluation, OpenID AuthZEN 1.0 with the MCP mapping: subject.id, resource.id = tool, arguments in resource.properties.
Docker MCP GatewayTwo hooks Docker's gateway already offers: one before a request, one after.Two http interceptors, before and after, at /adapters/docker/*. A refusal comes back as the tool result; a redaction replaces it.
IBM ContextForgeLoads as a ContextForge plugin.python -m aggrete.adapters as an external plugin for tool_pre_invoke and tool_post_invoke.

One condition: the gateway has to ask about every request, before and after, and say who it is for. What it does not ask about cannot be governed. Details: docs/ADAPTERS.md.