Authority verification harness for AI agents: replay traces against declared authority envelopes and fail the build when the composition of actions exceeds what any principal authorized.
Project description
Kagua
Unit tests assert your agent did the right thing. Kagua asserts it was ever allowed to.
Kagua replays multi-agent traces against a declared authority envelope and fails the build when the composition of actions exceeds what any principal authorized, even though every individual call passed its own check.
Same output as text
$ kagua check fixtures/workorder/
FAIL Composition / forbidden_composition
forbidden sequence [vendors.get_quote -> payments.approve] completed within t_workorder_442; every call was individually authorized
witness (6 events):
e01 task_start t_workorder_442 - WO-442: HVAC failure, Site 12
e03 delegation human:ops.manager -> agent:coordinator [w_coord] scope=6 tools - root grant to coordinator
e08 delegation agent:coordinator -> agent:procurement [w_proc] scope=6 tools - sub-delegate quote collection
e13 tool_call agent:procurement vendors.get_quote [w_proc] - quote from acme-hvac: $8,400 <- forbidden[0]
e28 delegation agent:coordinator -> agent:finance [w_fin] scope=4 tools - sub-delegate payment processing
e33 tool_call agent:finance payments.approve [w_fin] - approve $8,400 to acme-hvac <- forbidden[1]
every event above passed its own Lifetime/Scope/Principal check; the composition is the violation
coverage: QUALIFIED - no enforcement point declared for this trace
families: Lifetime ok | Scope ok | Principal ok | Provenance n/a | Composition partial | Trajectory n/a
verdict: FAIL (1 finding)
Read that trace again. The coordinator was granted exactly what the envelope declares. Both sub-delegations narrowed scope. Every tool call sat inside its warrant. A per-call policy engine says yes 40 times. And the task still solicited vendor quotes and approved the payment to the winner with no human in between. That gap, agents composing past checks that each pass individually, is what Kagua exists to catch.
Why point-in-time authorization isn't enough
Cedar and OPA decide the point; Kagua verifies the trajectory. A policy engine answers "may this call proceed?", and that's precisely the layer composed abuse defeats, because every point answer is yes. Kagua evaluates the whole task DAG after the fact (or in CI, before deploy). The two are complementary: if you already run per-call policy, composition is the gap you have left.
Three claims, each testable in this repo:
- Point-in-time authorization cannot detect composed abuse.
- A deterministic, replayable check over a causally-ordered trace can, for declared invariants, at declared coverage.
- Nobody ships that artifact today. Gateways enforce, observability describes, evals score quality. None verify authority composition.
Quickstart
pip install kagua
git clone https://github.com/Dnakitare/kagua && cd kagua
kagua check fixtures/workorder/ # exits 1, prints the witness above
Three artifacts, all files, all diffable in git:
- Trace (JSONL): a DAG of events.
delegation,tool_call,task_start,task_end,token_issue,token_revoke,message. Seefixtures/workorder/trace.jsonl. - Envelope (YAML): who the root principals are, what each agent is scoped to do, and which compositions are forbidden. See
fixtures/workorder/envelope.yaml. - Verdict (exit code + JSON): findings with witness sets, a slice of the trace sufficient to demonstrate each violation, plus a coverage grade on every claim.
The rules Kagua checks (v0.1)
| Family | Check | Status |
|---|---|---|
| Lifetime | No event references a warrant outside its validity window; no activity after task_end (zombie authority) |
shipped |
| Scope | Every call inside the transitive scope of its warrant chain; scope never widens across delegation hops | shipped |
| Principal | Every warrant chain terminates at a declared root principal; no orphaned authority | shipped |
| Composition | forbidden_composition sequences over the task DAG |
shipped (sequences only) |
| Provenance | Cryptographically signed delegation hops (Muhuri) | v0.2 |
| Trajectory | Plan subsumption | v0.3+ |
v0.1 honestly covers three of the six families plus forbidden compositions. The general composition engine (budgets, conservation rules in CEL) is v0.2.
Design rules that won't bend:
- Every check is deterministic and produces a replayable witness. No ML in the verifier. An anomaly score is a suspicion; a witness set is a proof.
- Causal order beats wall clock. Ordering comes from parent links; timestamps are a fallback with a tolerance window, because distributed clocks lie.
- Retries have identity. Events carry idempotency keys; a retried call can't count twice.
- Lossy input never produces a silent pass.
Coverage grades: attested vs. qualified
Every verdict says what it can actually claim:
attested: the input source can prove completeness (all egress flowed through a declared enforcement point, like an MCP gateway).qualified: violations found are real; the absence of violations proves nothing beyond the visible trace.
A pass on partial input is worse than no tool if it reads as a clean bill. Kagua states its own coverage boundary on every run.
Ingesting your traces
kagua ingest ./otel-export.json --adapter otel --out trace.jsonl
The OTel GenAI adapter converts tool-execution spans into canonical events and tells you exactly what it couldn't recover:
ingested 7 spans -> 4 events
skipped 2: model/agent invocation spans (no authority semantics)
skipped 1: non-GenAI spans
recovered: 1/3 warrants, 1 delegation records, 1/3 args digests
this input cannot support:
2 of 3 tool calls carry no warrant; those events are unverifiable for Lifetime/Principal
Provenance - not implemented until v0.2 (Muhuri-signed hops)
verdicts over this trace will be QUALIFIED: findings are real, but a pass
covers only what this export saw. OTel sampling drops spans by design;
a sampled trace cannot prove the absence of a violation.
Plain OTel GenAI data is authority-blind. If your instrumentation emits the kagua.* span attributes (kagua.warrant_id, kagua.delegation.subject, kagua.args_digest, ...), the adapter recovers full authority semantics; Datadog ignores them and nothing breaks.
CI
# .github/workflows/authority.yml
jobs:
authority:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Dnakitare/kagua@main
with:
trace: traces/recorded/
fail-on: any
A prompt edit or tool-wiring change that widens effective authority turns the PR red with a Scope-family witness before it deploys. fixtures/scope-drift/ is a worked example: one delegation drifts to include payments.approve and the check fails on the exact granting event.
What Kagua is not
- Not an eval framework. It never scores answer quality.
- Not a gateway. It never sits in the request path.
- Not an anomaly detector. If a check can't produce a replayable witness, it doesn't ship.
Related work
The problem is getting named from several directions at once; none of these ship this artifact, and each is worth reading:
- Authenticated Delegation and Authorized AI Agents (MIT Media Lab, 2025) makes the case for verifiable delegation chains. Kagua is the check layer such chains deserve; Muhuri is our signed-credential half of that story.
- Closure Gaps and Delegation Envelopes for Open-World AI Agents (2026) formalizes delegation envelopes. Kagua's envelope YAML is a practical, git-diffable instance of the same idea, and we take the term from that lineage rather than claiming it.
- Authorization Propagation in Multi-Agent AI Systems (2026) derives requirements for workflow-level authorization, including task-scoped envelopes and aggregation bounds, and notes the field has "not yet converged on a complete architecture". Kagua is a working verifier for the post-hoc slice of those requirements.
- Runtime Compliance Verification for AI Agents (2026) intercepts tool calls at runtime. That's enforcement, the layer we deliberately don't sit in; Kagua verifies after the fact and stays out of the request path.
- Cedar and OPA decide per-call policy. Complementary, as covered above.
Roadmap
- v0.2:
kagua infer(propose an envelope from observed traces, delivered as a PR, never auto-committed), the general composition engine (budget, conservation via CEL), pytest plugin, MCP gateway log adapter. - v0.3: Muhuri-signed delegation hops; Provenance moves from "trust the log" to "verify the chain". Replayable signed verdicts.
- v0.4:
kagua reportwith OWASP Agentic Top 10 and SOC 2 CC-series mappings. Findings and evidence language throughout, never "certified compliant".
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kagua-0.1.0.tar.gz.
File metadata
- Download URL: kagua-0.1.0.tar.gz
- Upload date:
- Size: 35.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04753b7c9a871129bacf16ff311199b8bc028096b35c4bc0c6d730931de24fc0
|
|
| MD5 |
321c4e74ff12af72744b30db5c39987a
|
|
| BLAKE2b-256 |
5a78e101446601a7a0e5c0167b8cae4d0a6b263f7c7f70e009b828a7b3947e55
|
Provenance
The following attestation bundles were made for kagua-0.1.0.tar.gz:
Publisher:
release.yml on Dnakitare/kagua
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kagua-0.1.0.tar.gz -
Subject digest:
04753b7c9a871129bacf16ff311199b8bc028096b35c4bc0c6d730931de24fc0 - Sigstore transparency entry: 2063535194
- Sigstore integration time:
-
Permalink:
Dnakitare/kagua@0a3530ef1b1cebd946a697eabf3211e9f13eb4f6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Dnakitare
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0a3530ef1b1cebd946a697eabf3211e9f13eb4f6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file kagua-0.1.0-py3-none-any.whl.
File metadata
- Download URL: kagua-0.1.0-py3-none-any.whl
- Upload date:
- Size: 30.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79eb15bdef7313f81e5380a1ad8db960a8f20895bb6926e23f3d8b33a9f2be0e
|
|
| MD5 |
d992bc046ebdeeb2ab4d84a34aa99e59
|
|
| BLAKE2b-256 |
2e61c5a8bbe4737e70c7197c7c24493288201bb1cdb6f76bd708b1e1e3b95735
|
Provenance
The following attestation bundles were made for kagua-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Dnakitare/kagua
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kagua-0.1.0-py3-none-any.whl -
Subject digest:
79eb15bdef7313f81e5380a1ad8db960a8f20895bb6926e23f3d8b33a9f2be0e - Sigstore transparency entry: 2063535224
- Sigstore integration time:
-
Permalink:
Dnakitare/kagua@0a3530ef1b1cebd946a697eabf3211e9f13eb4f6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Dnakitare
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0a3530ef1b1cebd946a697eabf3211e9f13eb4f6 -
Trigger Event:
release
-
Statement type: