Agent governance
Register the AI agents you operate and the ones you bought, trace what they do run by run, approve the tool servers they call, and prove you can stop any of them.
An agent is not another AI tool. A tool is something your staff use; an agent is something that acts on your systems, unattended, on somebody's behalf - whether you built it or bought it. That carries a different set of obligations, and they are the ones ISO/IEC 42001, EU AI Act Article 14, and the NIST AI RMF all converge on: a named owner, an approved level of autonomy, a record of what it actually did, and a stop button somebody has pressed.
Joopler governs that in four parts: the register, the run trace, the tool-server approval, and discovery for the agents nobody registered.
Most companies will end up buying far more agents than they build, and a bought agent will never call an API you control to ask permission. Those are governed differently - through the credentials they hold rather than through a pre-action check - and that is covered in Agents you bought below.
The agent register
Register every agent at Agents in the left nav. Each one records:
- Key - the name the runtime reports itself as, and the identity everything else hangs off.
- Purpose and owner - the owner is one person, not a team. This is who gets called.
- Autonomy - suggests only, acts with human approval, or autonomous within its limits.
- Limits - which tools it may call, a ceiling on steps and write actions per run, daily call and spend caps, and the data-sensitivity threshold above which a human must approve.
An agent that is running but not registered is reported at the top of the page and
fails the agent-inventory-complete control. The register is meant to match
reality, not intentions.
Give the agent its own credential
On the agent's page, choose Issue credential. You get a jpt_ token, shown
once.
That token is bound to that one agent. It can report that agent's runs and ask about that agent's actions, and nothing else - it cannot file telemetry as a different agent, and it reaches no other part of the API. Rotating replaces the previous token immediately, so there is never more than one live at a time.
Trace a run
The unit of governance is the run, not the call. A stream of tool-call rows tells you an agent was busy; a run tells you what it did, in what order, on whose behalf, and where somebody said yes.
Four HTTP calls, or the SDK once it is published. The HTTP contract is stable and is what the SDK wraps, so starting here costs nothing later.
@joopler/agent-sdkis not on npm yet. It ships inside the platform today. If you want it ahead of the public release, ask us and we will send you the package; otherwise use the HTTP calls below, which are the same contract.
import { JooplerAgent } from "@joopler/agent-sdk";
const joopler = new JooplerAgent({
baseUrl: "https://api.joopler.com",
token: process.env.JOOPLER_AGENT_TOKEN!,
// "open" keeps the agent working if Joopler is unreachable (telemetry is lost);
// "closed" treats an unanswerable check as a refusal.
onUnreachable: "open",
});
const run = await joopler.startRun({
runId: crypto.randomUUID(),
user: "ava@company.com",
trigger: "schedule",
goal: "Triage new support tickets",
});
try {
// Checks policy FIRST, executes only if allowed, records the step either way.
const issue = await run.guardedCall(
{ server: "github", tool: "create_issue", write: true, args: { title } },
() => github.createIssue({ title }),
);
await run.end("ok");
} catch (err) {
await run.end("error", String(err));
}The same thing over plain HTTP, which works today with no dependency:
curl -X POST https://api.joopler.com/v1/agents/runs \
-H "Authorization: Bearer $JOOPLER_AGENT_TOKEN" \
-H "content-type: application/json" \
-d '{"runId":"2026-09-11-triage-001","trigger":"schedule","user":"ava@company.com"}'
curl -X POST https://api.joopler.com/v1/agents/runs/2026-09-11-triage-001/steps \
-H "Authorization: Bearer $JOOPLER_AGENT_TOKEN" \
-H "content-type: application/json" \
-d '{"steps":[{"kind":"tool","server":"github","tool":"create_issue","write":true,"outcome":"ok","durationMs":210}]}'
curl -X POST https://api.joopler.com/v1/agents/runs/2026-09-11-triage-001/end \
-H "Authorization: Bearer $JOOPLER_AGENT_TOKEN" \
-d '{"status":"ok"}'Every step is scanned for secrets, prompt injection, and data classification, then signed into the evidence ledger. Arguments are never stored - only a hash of the redacted text plus what the scan found - so the trail is verifiable without holding your content. Closing the run signs one summary record covering the whole thing, which is the record an auditor verifies.
Agent tool calls also continue to appear on the AI usage dashboard next to your other AI traffic, so nothing lives in a parallel universe.
Enforce before the action
POST /v1/agents/check is the decision point. Call it before executing a
tool: a verdict that arrives afterwards is a log entry, not a control. One call
applies every limit at once - the agent's lifecycle, its tool list, the approval
state of the server the tool belongs to, the arguments, the run's budget, and the
approval threshold.
It answers one of three ways:
allow- proceed.deny- do not proceed, with a machine-readablecode(tool-blocked,write-cap,mcp-drifted, and so on).needs-approval- the action is legitimate and waiting on a human. Send it again withapprovedByonce somebody has approved it.
Inline enforcement follows the same Growth-plan rule as the rest of the gateway:
below Growth the decision is still made and recorded but always returns allow,
and the response says enforced: false. Stopping an agent is never gated - a
kill switch that depends on a billing tier is not a kill switch.
The stop button
On the agent's page, Stop agent does two things in one act: the register refuses everything that agent asks for from then on, and its credential is revoked so it cannot even reach the API.
Test the stop button runs the same thing as a drill and records the test. Each
agent needs a test within the last twelve months to pass
agent-kill-switch-tested, because an untested stop button is a claim rather than
a control.
This only applies to an agent you operate, where the stop actually stops something. On an agent marked as bought, the test is refused rather than recorded, and points you at a containment drill instead. Banking a passing control for a kill switch that does not reach the agent would be worse than having no control at all.
Agents you bought
An agent you purchased runs on somebody else's infrastructure. You cannot put a pre-action check in front of it, and pressing Stop agent in Joopler would stop nothing. What you do control is the credentials it holds into your systems, and that is the whole governance surface.
Register one at Agents -> Add agent -> Bought. Rather than describing it, pick the product from the catalogue - the support, research, coding and workflow agents actually on the market are already in there, with their category, the systems they typically reach, and whether the vendor hosts them. Picking from the catalogue fills in the defaults, so the register entry is a click rather than a form.
Discovery finds the ones nobody registered. It reads your identity graph for non-human principals that look like an agent product, your own cloud for model callers, and your assistant configuration for tool servers, then matches what it finds against the same catalogue. Each finding is presented for approval rather than investigation: accept it and it becomes a register entry with the vendor already identified.
Permissions, measured against what you approved
Once a bought agent is registered, Joopler reconciles the credentials it holds on each provider and compares them to the scope you approved - not to what it had yesterday. A vendor that quietly widens what its agent can touch shows as drift, scored by how far the new reach goes:
| New reach | Severity |
|---|---|
| Directory or identity access | High |
| The ability to write | Medium |
| Read access | Low |
Permissions being removed never raises the severity. And a provider Joopler could not read this cycle is excluded from the comparison rather than treated as empty, so a connector outage never reads as "your agent lost its access."
Containment
For an agent you cannot instrument, containment means revoking its credential where that credential actually lives, and the steps differ by provider. The agent's page gives you the real revocation path for each one, ordered fastest first, and tells you which take effect immediately and which wait for a token to expire - an IAM policy change is evaluated on the next request, while an OAuth grant revocation is not felt until the current access token ages out.
Marking a credential revoked in Joopler records the action you took, not its effect. The evidence says so explicitly. Confirming the effect is a separate step, and the difference between them survives into the record.
The containment drill
Stopping a bought agent is a first-class exercise, filed the same way as any other: a date, a signer, and a statement of what was done.
curl -X POST https://api.joopler.com/v1/agents/$KEY/containment/drill \
-H "Authorization: Bearer $CLERK_TOKEN" -H "content-type: application/json" \
-d '{"statement":"Revoked the OAuth grant in Google Workspace and confirmed the
next scheduled run failed to authenticate.","signedBy":"ava@example.com"}'It is graded. A drill where the revocation took effect and somebody observed it records as proven. A drill where the action was taken but the effect was never observed records as attested, and the two are different words in the evidence. A drill you did not really run does not quietly become a passing control.
Tool servers
Every MCP or tool server your agents can reach is listed at Tool servers. A server appears there the moment an agent calls it, when a host reports its manifest, or when discovery finds it in a config file.
Approval is recorded against a fingerprint of the server's tool definitions, not its name. A tool's description is an instruction the model follows, so a server that keeps its tool names and rewrites what those tools say has changed its behavior while looking identical to any inventory tracking names. Joopler flags it as changed since approval, names which tools were redefined, added, or removed, and - if the agent's policy says so - refuses to let the agent call it until somebody reviews the new definitions.
Report a manifest at connect time:
await joopler.reportServer({
key: "files",
transport: "stdio",
tools: await mcpClient.listTools(),
});Finding agents nobody registered
The network is the weakest place to look for an agent. A local MCP server over stdio never crosses it, a model invoked inside your own VPC never leaves it, and at the TLS layer an agent and a person calling the same provider look identical. So Agent discovery looks in the four places agents actually show themselves, in the order they are worth trusting:
- Your identity graph. Machine credentials and client-credential apps holding AI grants. An agent has to hold credentials to act, and a credential is far more findable than a packet. Pulled live from your identity provider.
- Your cloud control plane. Bedrock and equivalent model activity by
principal, pulled nightly from CloudTrail for a connected AWS account. This
traffic never egresses, so no proxy, secure web gateway, or CASB can see it.
Note that
InvokeModelis a CloudTrail data event: post those from Athena or CloudWatch Logs if you record them. - Endpoint and repo configuration. MCP host configs
(
claude_desktop_config.json,.cursor/mcp.json,.vscode/mcp.json,.mcp.json) and agent frameworks in dependency manifests, posted from your MDM or a repo scan. This is the only way a local stdio server is found at all. - Traffic behavior. Computed from your own AI telemetry. Destination tells you nothing here, so this reads cadence, hours, volume, and client - a hint worth a look, not a verdict.
Anything you collect yourself goes to the same endpoint:
curl -X POST https://api.joopler.com/v1/agent-discovery/scan \
-H "Authorization: Bearer $CLERK_TOKEN" -H "content-type: application/json" \
-d '{"files":[{"location":"MBP-ava","path":"claude_desktop_config.json","source":"jamf","content":"{...}"}]}'Each finding is either registered as an agent or dismissed with a reason, so the inbox empties by being acted on. A dismissal survives re-scans.
The controls this produces
| Control | Passes when |
|---|---|
agent-inventory-complete | No agent is producing telemetry that is not in the register |
agent-owner-assigned | Every live agent names an accountable person |
agent-autonomy-approved | No fully autonomous agent runs with no limits at all |
agent-activity-logged | Runs are recorded and signed into the evidence ledger |
agent-kill-switch-tested | Every agent's stop button was exercised in the last 12 months |
mcp-servers-approved | Every tool server in use is approved and unchanged since |
These map to ISO/IEC 42001 (A.3.2, A.4.2, A.6.2.6, A.6.2.8, A.9.2, A.10.2, A.10.4), EU AI Act Articles 12, 14, 19, 25 and 26, and NIST AI RMF GOVERN 1.6, GOVERN 1.7, GOVERN 2.1, GOVERN 3.2, GOVERN 6.1, MAP 3.5, MAP 4.1, MANAGE 2.4, MANAGE 4.1 and MEASURE 2.4. They are evaluated nightly and on demand.
AI governance
See and control every AI interaction, with signed evidence for ISO 42001 and the EU AI Act.
Ship your agent with governance built in
For companies that SELL agents. Your customer gives you a credential; your agent reports into their compliance workspace. You write it once, every customer gets their own evidence.