Custodia is a Copilot Studio agent that turns an incoming public-records request into a properly named eDiscovery case, a bounded KQL search, and an item-count estimate — in one conversation, restated and confirmed before anything is created, and never exceeding the reviewer's own Purview role.
Custodia is a delegated-auth connector and agent template, not a standing service. Every call it makes runs as the signed-in reviewer, through their own Purview eDiscovery role — the agent can never see or do more than the person driving it already could in the Purview portal. There is no service identity, no app-only token, and no shared credential anywhere in this design.
Custodia uses generative AI to construct search queries and summarize results. It can make mistakes — verify case setup, query logic, and item counts independently before relying on them for a statutory response. It never decides whether a record is responsive, exempt, or privileged, and it has no tool that can apply that determination even if asked. This site is a demonstration of the repository's design; deploy it in your own tenant and test it against synthetic data first. Not affiliated with Microsoft. Microsoft, Microsoft Purview, Microsoft Graph, and Copilot Studio are trademarks of Microsoft Corporation.
Custodia doesn't replace the FOIA team's judgment — it removes the tedious, error-prone setup that happens before judgment is even possible: naming the case correctly, writing a bounded query, and knowing roughly how big the request is before you commit to it.
Every case follows the same pattern — request number, year, short subject — instead of whatever a reviewer typed under deadline pressure.
Describe the request in your own words; Custodia writes the bounded KQL, always anchored to the requested date range.
Item count and mailbox/site hits come back before anything is collected, so scope decisions are made against a known size, not a guess.
Case name, query, date range, and scope are read back in full before every create. One confirmation authorizes exactly one action — never a chain.
Purge, delete, legal-hold changes, and document tagging aren't just discouraged — they're not defined in the connector. The agent has no tool that could do them.
Responsiveness, exemptions, and privilege are never decided by Custodia — and it can't apply a document tag even if you ask it to.
This is a real transcript shape — the operations, confirmations, and async estimate behavior all match how the connector and agent instructions actually work. Case, search, and counts below are illustrative, not from a real tenant.
Hello,
Under the Public Records Act, I am requesting copies of all correspondence regarding the Riverside Avenue rezoning proposal, including any discussion of the related zoning variance, exchanged between January 1, 2026 and June 30, 2026.
Please let me know if you need anything further to process this request.
New request came in — #00187, Riverside Ave rezoning. Set up the case for the correspondence January through June 2026.
Before I create anything, here's exactly what I'll set up:
FOIA-2026-00187-riverside-rezoningNothing is created until you confirm.
⏸ Waiting on your confirmationConfirmed.
Case created — FOIA-2026-00187-riverside-rezoning.
Now the search. This is a separate action from creating the case, so I need a separate confirmation:
FOIA-2026-00187-riverside-rezoning-search-01("Riverside Ave" OR "rezoning" OR "zoning variance") AND (received>=2026-01-01 AND received<=2026-06-30)none — I'll attach the mailboxes you name rather
than searching the whole tenant.Confirmed — add the Planning Dept shared mailbox and my own.
Search created. Running an estimate before we talk about collecting anything:
Estimate complete:
That's an estimate, not a collection — nothing has been pulled into a review set, and nothing has left Purview. Say the word if you want to collect these into a review set next; that's a separate, more heavily guarded confirmation.
v1 is deliberately the simplest thing that works: a Copilot Studio custom connector calling Microsoft Graph directly, with on-behalf-of delegated auth doing all the access-control work. No Azure Function, no hosting, no compute in the middle.
Graph scopes: eDiscovery.ReadWrite.All and User.Read,
both delegated. No application (app-only) permission exists anywhere in the app
registration.
Each reviewer authenticates with their own credentials. There is no maker-provided connection — get that wrong and the whole permission model silently collapses onto one identity.
Purge, delete, legal-hold changes, and document tagging aren't blocked by a
prompt — they simply don't exist in apiDefinition.swagger.json.
Custodia never downloads, stores, or forwards the exported file. Once the export
operation succeeds, Microsoft Graph attaches a downloadUrl to that
operation, and Custodia surfaces it in the reply — the reviewer clicks it and
downloads the package directly from Purview, authenticated as themselves. The link is
time-limited and specific to that one export.
An Azure Function backend is documented as a future option for code-enforced approval workflows on export — not built, because it isn't needed for read + create.
Each distinct tool (list cases, create a search, etc.) asks for a one-time
permission the first time a reviewer uses it in a session. That's the on-behalf-of
consent model working as intended — the agent is asking to borrow the reviewer's own
Graph token, not a blanket grant. It won't ask again for that tool once granted.
Verified directly: in a fresh session, a never-used tool prompts once, and a
previously-used tool runs silently — including after the token would otherwise have
expired, once the connector's scope includes offline_access.
Allow grants the connector a token scoped to that one tool, for that
reviewer, for that session — not a standing grant to every tool or every case.
Deny stops that specific call; Custodia reports that it couldn't complete the
action rather than retrying silently or substituting another identity. Denying one
tool doesn't block the others — a reviewer who denies CreateSearch can
still use ListCases normally.
This is separate from the "restate and confirm" prompts before a create/update —
those come from agent/instructions.md and repeat every time by design.
The one-time "Allow" card is Microsoft Entra ID granting the connector a token; it has
nothing to do with whether the action is safe to repeat.
That's not expected — check the connector's Security tab. The most common cause is
a missing offline_access scope (no refresh token is issued, so the
short-lived access token expires between calls) or a comma-separated Scope
field instead of space-separated. Both are documented in
connector/apiProperties.json and
agent/greeting-and-prompts.md.
Guardrails in agent/instructions.md are the second layer. The connector
surface — what operations even exist — is the first, and it's the one that actually
holds under a jailbreak attempt or a confused-deputy prompt.
Hand the repository to whoever administers your tenant and Power Platform environment. They don't need to be a developer to run it.
Creates the Entra app registration, delegated Graph permissions, admin consent, and the Power Platform custom connector — no portal clicking required.
Paste in agent/instructions.md, add the connector as an action, and
require each user to sign in with their own credentials.
Assign the Purview eDiscovery Manager role deliberately, per reviewer — then test the permission boundary before pointing it at a real request.
Full setup guide, safety scope, evaluation sets, and the Phase 2 Azure Function option — all documented in the README.