Skip to main content
Glama
io.github.JC7333/mandatia

io.github.JC7333/mandatia

by JC7333

Agent Mandate Registry (AMR)

The legal mandate layer for AI agents.

AMR is an open-source MCP server that registers, verifies, and audits legal mandates authorizing AI agents to act on behalf of principals. It provides a cryptographically chained audit trail of who authorized which agent to do what, until when: evidence that supports the human-oversight and record-keeping duties of deployers under EU AI Act Article 26, with eIDAS 2.0 electronic mandates in mind. AMR is a technical tool: it does not by itself make a system compliant.


Quickstart for agents

This section is written so that an AI agent (Claude Code, Cursor, any MCP client) can install AMR and reach a first verified mandate on its own: no account, no email, no person to ask. Everything runs on your machine; AMR makes no network call (two opt-in exceptions, both off by default: report_issue once the operator sets AMR_ISSUE_TOKEN, and the did:web key check once the operator sets AMR_DID_WEB_RESOLVE=1).

1. Run the server (needs uv).

uvx mandatia

The PyPI package is named mandatia; the Python module and the amr command keep their names. uvx amr installs a different, unrelated project: do not use it. To run the latest code of the repository instead:

uvx --from git+https://github.com/JC7333/amr amr

AMR runs with version 1 and version 2 of the MCP Python SDK (mcp>=1.2,<3); no extra version pin is needed.

Or clone the repository and run uv sync && uv run amr.

2. Declare it in your MCP client.

Claude Code:

claude mcp add amr -- uvx mandatia

Cursor (.cursor/mcp.json) and other clients that read an mcpServers object:

{
  "mcpServers": {
    "amr": {
      "command": "uvx",
      "args": ["mandatia"],
      "env": {
        "AMR_DB_PATH": "/absolute/path/amr_mandates.db",
        "AMR_SIGNING_KEY_PATH": "/absolute/path/amr_signing_key.pem",
        "AMR_PUBLIC_KEY_PATH": "/absolute/path/amr_signing_key.pub"
      }
    }
  }
}

To run the repository code instead, the arguments are ["--from", "git+https://github.com/JC7333/amr", "amr"]. Set the three paths: by default AMR writes its database and its signing key in the current directory of the client. Keep the .pem private key out of version control.

3. Reach a first verified mandate. The tools, in the order an agent uses them:

  1. create_mandate: register who authorizes what (principal_id, agent_id, scope, legal_basis_regulation, legal_basis_article, duration_hours; add allowed_actions / forbidden_actions for a perimeter; only issue_action_token enforces it).

  2. verify_mandate(agent_id, action): authorized: true while the agent has an active mandate. It does not check action against the perimeter; issue_action_token does.

  3. log_action(mandate_id, action): append an entry to the hash-chained journal.

  4. issue_mandate_attestation + get_registry_public_key: a signed document a third party checks offline with python -m amr.verify_attestation.

  5. revoke_mandate(mandate_id, reason): the stop button; verify_mandate then answers authorized: false. Tokens and attestations already issued stay valid until they expire, except for a receiver that re-checks the token with check_action_token.

  6. get_proof(mandate_id): the full audit bundle (mandate, actions, integrity). If the agent already did something it should not have, contest_action(mandate_id, action_id, reason, response_days) records that the act is disputed, and list_action_contests reminds the deadline the operator chose.

  7. report_issue(title, description, version, context): report a problem with AMR itself. Without AMR_ISSUE_TOKEN it makes no network call and returns a pre-filled link to open; with it, it files the issue on GitHub. See docs/report-issue-spec.md.

The same flow, runnable and checked by the test suite, is in examples/quickstart_agent.py:

git clone https://github.com/JC7333/amr.git && cd amr && uv sync
uv run python examples/quickstart_agent.py   # prints every step, ends with "OK"

Machine-readable summaries for agents: AGENTS.md and llms.txt. To report a problem, call report_issue or open an issue at https://github.com/JC7333/amr/issues (never paste real mandate contents or personal data into it).


Related MCP server: forge-agent-gate

Why AMR?

Modern AI agent infrastructure covers many concerns — but the legal mandate layer is missing:

Concern

Existing tools

AMR

Infrastructure & orchestration

✅ LangChain, AutoGen, CrewAI

—

Security & sandboxing

✅ Docker, RBAC, network policies

—

Permission & access control

✅ OAuth, IAM, API keys

—

Observability & tracing

✅ OpenTelemetry, Langfuse

—

Legal mandate & accountability

❌ Not covered

✅ AMR

EU AI Act Article 26 (Regulation (EU) 2024/1689) requires deployers of high-risk AI systems to assign human oversight and to keep the logs the system generates, to the extent they are under their control, for at least six months (Art. 26(6)). It applies from 2 December 2027 for Annex III systems and 2 August 2028 for Annex I systems (Regulation (EU) 2026/1744). Non-compliance with Article 26 can be fined up to €15 million or 3% of worldwide annual turnover, whichever is higher (Art. 99(4); for SMEs and start-ups, whichever is lower, Art. 99(6)). AMR records who authorized an agent, for what and until when, and what it did, in a tamper-evident form that this oversight can rely on; it complements the system's own logs, it does not replace them.


Structural hard-stop enforcement

AMR does not only record mandates — it enforces them. Agent runtimes must obtain a signed action token from AMR before performing any action. The token is issued if and only if a valid, non-expired, non-revoked mandate authorizes that specific action for that specific agent. No mandate → no token → no action.

Tokens are JWS (RFC 7515) signed with Ed25519 (EdDSA), include replay prevention via the jti claim and the issued_tokens audit table, and carry the exact action hash and scope digest they authorize. Consumers verify offline with the AMR public key and MUST check aud, exp, and amr_action_hash.

Perimeter enforcement and delegation control

A mandate can carry a structured perimeter: the action types the agent may request (allowed_actions), the ones it may never request (forbidden_actions, which always win and also reach sub-types: forbidding agent.create forbids agent.create.batch), whether it may delegate (delegation_allowed), and an optional amount ceiling (max_amount with currency, applied to amount_actions). The token issuer then checks the requested action against the perimeter, not only the existence of the mandate:

  • an action type outside allowed_actions, or matching forbidden_actions, gets no token (reason: "action_out_of_scope", with a detail and the mandate that refused it in refused_by);

  • a sub-mandate (parent_mandate_id) is created only if the parent is active, allows delegation, and was granted to the agent now delegating; the sub-mandate must stay inside the parent's allowed actions and limit, inherits the parent's prohibitions, and cannot outlive the parent;

  • every token issued under a sub-mandate re-checks the whole chain: revoke (revoke_mandate) or let expire the parent, and every sub-mandate below it stops getting tokens;

  • an optional cumulative ceiling (period_max_amount over a rolling window of period_days days, same currency and amount_actions) caps the total of the amounts of the tokens issued under the mandate and every sub-mandate below it in the window: for the actions that carry an amount (those reached by amount_actions, every action when it is empty, and any action that declares an amount), an agent cannot split a large purchase into small ones, nor spend the same budget again through a sub-agent (detail: "period_amount_above_limit:<max> <CUR>/<days>d", with the mandate whose budget is spent in refused_by). A token counts as soon as it is issued, used or not; a refused token counts for nothing. A sub-mandate's own ceiling may not exceed the parent's. The parent's ceiling is not copied into the sub-mandate's perimeter (it binds the sub-mandate all the same, at issuance): read the whole chain, as get_proof and the attestation do, to see every ceiling that applies. A sub-mandate that sets a ceiling under a parent with a per-action limit binds the union of both amount_actions (an empty list on either side means every action);

  • optional risk classes (risk_classes) bound how long a token stays usable, by what is at stake: each class names action types (reached with their sub-types, like forbidden_actions) and a max_token_seconds from 1 to 86400, for example {"name": "irreversible", "actions": ["invoice.pay"], "max_token_seconds": 30}. A token for an action reached by a class lives at most that long; the shortest class wins, in the mandate and in every mandate above it in the delegation chain, so a sub-mandate can shorten the window, never lengthen it: it inherits the parent's classes (shown in its own perimeter) and may reuse a class name only with a shorter or equal max_token_seconds (else limit_exceeds_parent). The matched class names are sealed in the token as amr_risk_classes. A class never lengthens a token beyond AMR_TOKEN_LIFETIME_SECONDS. Mandates without classes keep their exact hash and tokens. A class may also set "registry_check": true: its tokens then carry amr_registry_check: true, and the receiver re-checks them with check_action_token at the moment of acting, so a revocation in the meantime is seen. A sub-mandate inherits the flag and cannot drop it. Classes without it keep their exact form and hash.

Example: an assistant agent told never to create sub-agents.

create_mandate(..., allowed_actions=["report.*"], forbidden_actions=["agent.create"])
issue_action_token(..., action_type="agent.create")
→ {"denied": true, "reason": "action_out_of_scope", "detail": "forbidden:agent.create"}

Root mandates without a perimeter keep working as before (free-text scope, existence and validity checks only, same hashes). Perimeters without a cumulative ceiling keep their exact canonical form, so their hashes, perimeter digests and principal signatures are unchanged; schema v7 only adds the token_amounts table. Limits of the cumulative ceiling: tokens issued before schema v7 carry no recorded amount and are not counted (an older AMR cannot issue tokens on a capped branch: it refuses the unknown perimeter fields); the ceiling binds one mandate and its branch, not every mandate of the same principal. Two changes are deliberate and breaking for sub-mandates: a new sub-mandate requires a parent that allows delegation (so a free-text parent can no longer delegate), and tokens under any sub-mandate, including ones created before v1.1, now require every parent to be active and unexpired.

Deployment rule. These controls hold only if agents cannot register their own mandates. create_mandate and register_principal_key are principal-side tools: expose them to the principal (or its back office), never to the agents it governs. An agent that can call create_mandate can mint itself a fresh root mandate for any principal without a key, and one that can call register_principal_key can claim a principal that has not registered a key yet. Principal signatures (below) close the first gap once each principal has registered its key; binding keys to a verified identity is on the roadmap.

Principal signatures: the principal signs the mandate

A principal can register its own Ed25519 public key once (register_principal_key). From then on, the registry accepts a mandate in its name only if the principal signed it: prepare_mandate_signature returns the exact bytes to sign, and create_mandate takes principal_signature and signed_at. The signature is sealed into the mandate's chain_hash, cannot be used twice in the same registry, and is kept in the Proof Pack with the public key and the signed bytes, so anyone can re-check it offline. Set AMR_REQUIRE_PRINCIPAL_SIGNATURE=true to refuse every unsigned mandate.

What a valid signature proves: the mandate was signed with the key first registered for that principal_id. For an ordinary principal_id the registry does not check who registers a key (first registration wins), so register each principal's key at onboarding, through the principal's own channel. An organisation can instead use a did:web:<its domain> identifier: the key is then accepted only if the domain publishes it in https://<domain>/.well-known/did.json, and the SHA-256 of that document is sealed with the key (opt-in network access, AMR_DID_WEB_RESOLVE=1). This proves control of the domain at registration time, not a qualified signature. Consumers that rely on a did:web principal must check did_web_unverified: false in the Proof Pack, the attestation or the insurer dossier.

Behaviour change (did:web keys): with the default configuration, registering a key for a did:web: identifier is now refused (did_web_resolution_disabled) instead of "first registration wins"; keys already registered stay valid and are reported as did_web_unverified. A scheme:method: prefix written with non-ASCII look-alike characters is refused (principal_id_lookalike). Mandates of principals without a key behave as before (same hashes; the result gains "principal_signature": null); mandates created before a key was registered stay valid. See docs/principal-signature-spec.md, including its known limits.

See docs/token-issuance-spec.md for the full specification.

Sector templates

A template is a ready-made, versioned perimeter for one kind of agent, with a catalog that says in plain words what each action type means. The first one, agent-sante-rgpd, covers an assistant agent in a medical practice: it may read the record, handle the schedule, draft letters and patient messages for a human to send, and order supplies under a ceiling; it may never prescribe, diagnose, change or export the record, contact a patient itself, or create a sub-agent.

from amr.templates import get_template

template = get_template("agent-sante-rgpd")
await create_mandate(..., **template.create_mandate_kwargs())

The mandate records template_id, template_version and template_digest in its metadata. That metadata is a label, not sealed into the chain hash; the perimeter is sealed and is what the token issuer enforces. Templates only fill in existing create_mandate arguments: existing mandates and their hashes are unchanged. They are a Python API for now (no MCP tool lists them yet). Like every perimeter, the template holds only if the agent cannot call create_mandate itself (see the deployment rule above). See docs/templates/agent-sante-rgpd.md.

When a body (committee, board) authorized the agent, the mandate can record its decision (organ_decision: body, title, date, optional reference, designated persons or roles); the health template accepts it, create_mandate seals it into the chain hash (root mandates with a closed list of allowed actions only), and verify_mandate and get_proof return it. Mandates without it keep their exact hash.

Third-party verification: the receiver checks the mandate alone

An agent can show whoever receives it (a seller, a hospital, an insurer) a signed mandate attestation (issue_mandate_attestation). The receiver checks it alone, offline, with the registry's public key fetched once (get_registry_public_key): no account, no call to the registry. The attestation states that the mandate exists, who granted it (and whether the principal signed it; with include_principal_payload the receiver re-checks that signature itself), the agent, the scope and structured perimeter, the expiry, and that the mandate and every mandate above it were active when the attestation was issued.

python -m amr.verify_attestation attestation.jws --public-key registry.pub --audience seller:acme
# registry.pub: the PEM (or the JSON) returned by get_registry_public_key
# {"valid": true, "reason": "ok", "attestation": {"principal_id": "...", "perimeter": {...}, ...}}

What it does not prove: that the mandate is still active now. An attestation lives at most AMR_ATTESTATION_LIFETIME_SECONDS (default 15 minutes) and never outlives the mandate or a parent; a revocation made after issuance reaches an offline receiver only when the attestation expires. It is a bearer document: the receiver must make sure it is dealing with the agent named in agent_id. Pass audience to address it to one receiver. Mandate metadata is not disclosed unless include_principal_payload is set (it is inside the signed bytes). The registry trusts its own database: protect the file. Existing mandates and their hashes are unchanged; issuances are logged in a new append-only table, issued_attestations (schema v5). See docs/mandate-attestation-spec.md.

Public registry: online verification without an account

AMR runs over stdio next to each operator, so nobody else can query it. The public registry is a separate, read-only verification point: the operator exports a signed snapshot of the registry, and a small WSGI application (standard library only) serves it. A receiver checks a mandate identifier or pastes an attestation, in a browser (HTML) or from a script (JSON), with no account.

python -m amr.public_registry export --out amr_public_registry.jws   # read-only, signed EdDSA
python -m amr.public_registry serve --export amr_public_registry.jws --public-key registry.pub
curl http://127.0.0.1:8080/mandates/<mandate-id>
# {"status": "active" | "revoked" | "expired" | "cut_by_parent" | ..., "verification_ms": 0.2, ...}

The snapshot holds, per mandate, the agent, a SHA-256 reference of the principal (never the identifier in clear), the perimeter, dates, recorded status, revocation date and chain hash; never the scope text, legal basis, metadata or revocation reason. The application verifies the snapshot with the pinned registry key before serving it (and serves it raw at /snapshot for receivers who verify it themselves; trust in the JSON answers is trust in whoever runs the app), has no write path, keeps no log and never reads the client's address. Every result gives the snapshot date and the time the verification took. Limit: a revocation made after the snapshot shows only at the next export; set AMR_PUBLIC_SNAPSHOT_MAX_AGE_SECONDS to refuse answers from an older snapshot. Deployment and domain are the operator's step. Existing mandates and their hashes are unchanged; no schema change. See docs/public-registry-spec.md.


Vision: from registry to enforcement

AMR started as a mandate registry — it stores, verifies, and audits mandates. This is necessary but not sufficient.

The problem: if the mandate check is a voluntary call, agents skip it. The system degrades to advisory compliance within weeks of real-world deployment.

AMR now provides structural enforcement: it is the layer that issues execution authorizations (issue_action_token). No valid mandate → no authorization → no action possible. The system refuses to work without the mandate — not because someone recommends checking, but because the architecture makes it impossible to bypass.

Layer

What it does

Status

Registry

Store, verify, audit mandates. SHA-256 chain. MCP native.

✅ Live

Enforcement

Mandate-gated execution authorization (issue_action_token, check_action_token). No mandate = no action.

✅ Live

Ecosystem

AMR authorizes before the action. Certification proxies prove what happened during.

Compatible today

The open-source registry stays free. The enforcement layer is the commercial product.


Quick Start

git clone https://github.com/JC7333/amr.git
cd amr
uv sync
uv run amr

Claude Desktop Integration

Add AMR to your Claude Desktop config file:

Location: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "amr": {
      "command": "uv",
      "args": ["--directory", "C:\\dev\\amr", "run", "amr"]
    }
  }
}

Restart Claude Desktop. All AMR tools will be available immediately.


Tools

create_mandate

Register a new legal mandate authorizing an AI agent to act on behalf of a principal.

Parameters:

Parameter

Type

Description

principal_id

str

Principal granting the mandate. E.g. "user:audric"

agent_id

str

Agent receiving authorization. E.g. "agent:korvex-advisor-v1"

scope

str

What the agent is authorized to do

legal_basis_regulation

str

Regulation or contract. E.g. "EU AI Act Art.26"

legal_basis_article

str

Specific article or clause. E.g. "Art.26(1)(a)"

duration_hours

float

Duration in hours (max 8760 = 1 year)

legal_basis_jurisdiction

str

Jurisdiction code (default "EU")

parent_mandate_id

str | null

UUID of parent mandate for delegation chains (parent must allow delegation)

metadata

dict

Optional string key-value pairs for annotations

allowed_actions

list[str] | null

Action types the agent may request: "invoice.pay", "invoice.*" or "*"

forbidden_actions

list[str] | null

Action types never allowed (win over allowed_actions). E.g. ["agent.create"]

delegation_allowed

bool

Whether the agent may grant sub-mandates (default false)

max_delegation_depth

int | null

With delegation_allowed: how many levels of sub-mandates may exist below this mandate, 1 to 16 (1 = the agent may delegate, its sub-agents may not). A deeper sub-mandate is refused with delegation_depth_exceeded; every ancestor's limit is checked. Absent: no limit from this mandate. Added to the sealed perimeter only when set, so existing mandates keep their hash. The limit is sealed with the mandate and checked each time a sub-mandate is created below it (a sub-mandate cannot declare more levels than its ancestors leave); it is not re-checked when a token is issued, and it binds only chains that declare their parent: the principal signature is what stops an agent from opening a separate root. Perimeters with this key cannot be read by older AMR versions.

max_amount

str | null

Ceiling per action, decimal string. E.g. "500"

currency

str

ISO 4217 code of max_amount (default "EUR")

amount_actions

list[str] | null

Actions that must carry an amount under the ceiling (empty: all)

period_max_amount

str | null

Cumulative ceiling over period_days, for this mandate and all its sub-mandates, decimal string. E.g. "2000"

period_days

int | null

Rolling window of period_max_amount, 1 to 366 days (required with it). E.g. 30

risk_classes

list[dict] | null

Classes {name, actions, max_token_seconds, registry_check?} bounding the token lifetime of the actions they reach, here and in every sub-mandate; registry_check: true asks the receiver to re-check with check_action_token. E.g. [{"name": "irreversible", "actions": ["invoice.pay"], "max_token_seconds": 30, "registry_check": true}]

principal_signature

str | null

Principal's base64url Ed25519 signature of the prepare_mandate_signature payload (required once the principal has a registered key)

signed_at

str | null

ISO 8601 timestamp with timezone placed in the signed payload (within 15 minutes of the registry clock)

Returns:

{
  "status": "created",
  "mandate_id": "550e8400-e29b-41d4-a716-446655440000",
  "principal_id": "user:audric",
  "agent_id": "agent:korvex-advisor-v1",
  "scope": "Compare and recommend financial products to retail clients",
  "perimeter": null,
  "parent_mandate_id": null,
  "principal_signature": null,
  "expires_at": "2026-05-13T20:00:00+00:00",
  "chain_hash": "a3f5c2d1e8b9..."
}

perimeter holds the structured perimeter when one was given (it is sealed into chain_hash). Perimeter and delegation errors return {"error": "...", "code": "..."} with a stable code: perimeter_invalid, parent_mandate_not_found, parent_mandate_<status>, parent_mandate_expired, delegation_not_allowed, delegator_mismatch, sub_mandate_requires_perimeter, perimeter_exceeds_parent, limit_exceeds_parent, expires_after_parent. A broken chain above the parent returns parent_mandate_<status>, parent_mandate_expired or delegation_chain_too_deep (more than 16 levels).

principal_signature is {"key_id", "signed_at"} for a signed mandate. Signature errors: principal_signature_required, principal_signature_incomplete, principal_key_not_registered, signed_at_invalid, principal_signature_stale, principal_signature_invalid, principal_signature_replayed.


register_principal_key

Register a principal's Ed25519 public key, once, with a proof of possession.

Parameter

Type

Description

principal_id

str

The principal. E.g. "user:audric"

public_key

str

Raw 32-byte public key, base64url

proof_signature

str

Signature of {"context":"amr.principal_key.v1","principal_id":...,"public_key":...} (canonical JSON)

Returns {"status": "registered", "principal_id", "key_id", "registered_at", "chain_hash", "domain_proof"}. domain_proof is null, except for a did:web:<domain> principal: its key is accepted only if https://<domain>/.well-known/did.json lists it (needs AMR_DID_WEB_RESOLVE=1), and domain_proof holds did_document_url, did_document_sha256 and verified_at. See docs/principal-signature-spec.md, section 1 bis.


prepare_mandate_signature

Same arguments as create_mandate (plus optional signed_at, default now). Writes nothing; returns {"payload", "payload_b64url", "signed_at"}: the exact bytes the principal signs before calling create_mandate.


prepare_mandate_pdf

Same arguments as prepare_mandate_signature. Writes nothing; returns the mandate as a PDF for the principal to read and sign with its own signature tool (for example a PAdES signature): {"pdf_base64", "filename", "pdf_sha256", "attachment_name", "payload_sha256", "payload", "payload_b64url", "signed_at"}. The PDF carries the exact payload of prepare_mandate_signature as the attached file amr-mandate.json and prints its SHA-256 on every page. The same arguments and signed_at always give the same PDF. The registry does not sign this PDF and, in this version, does not check a signature made on it: the mandate is still accepted on the principal's Ed25519 signature of the same payload, with signed_at within AMR_PRINCIPAL_SIGNATURE_MAX_AGE_SECONDS (default 900 seconds) of the registry clock; generate, sign and register within that window (see docs/principal-signature-spec.md, section 2 bis).


verify_mandate

Check whether an AI agent has a currently active, non-expired mandate (for a sub-mandate, every mandate above it must be active and unexpired too). It does not check the action against the perimeter: issue_action_token does.

Parameters:

Parameter

Type

Description

agent_id

str

Agent to check. E.g. "agent:korvex-advisor-v1"

action

str

Action the agent wants to perform

context

str

Optional context for audit logging

Returns:

{
  "authorized": true,
  "mandate_id": "550e8400-e29b-41d4-a716-446655440000",
  "reason": "Active mandate found",
  "scope": "Compare and recommend financial products to retail clients",
  "perimeter": null,
  "parent_mandate_id": null,
  "expires_at": "2026-05-13T20:00:00+00:00"
}

log_action

Log an agent action under an active mandate. Cryptographically chained to previous actions.

Parameters:

Parameter

Type

Description

mandate_id

str

UUID of the mandate under which the action was performed

action

str

Description of the action performed

evidence

str

Optional reference or evidence. E.g. "email:msg_abc123"

result

str

Optional outcome. E.g. "Delivered"

Returns:

{
  "status": "logged",
  "action_id": "7f3e9b2a-...",
  "mandate_id": "550e8400-...",
  "action": "Generated investment recommendation for Product X",
  "chain_hash": "c8d7e6f5..."
}

The mandate must be active, not revoked and not expired. For a sub-mandate, so must every mandate above it: revoke or let expire a parent and its whole branch stops logging. The refusal then says why and which mandate broke the chain: {"error": "...", "reason": "parent_mandate_revoked", "refused_by": "<parent id>"} (also parent_mandate_expired, parent_mandate_<status>, parent_mandate_not_found, delegation_chain_too_deep).


revoke_mandate

The principal's stop button. Records a final revocation of a mandate, with the reason the principal gives. Append-only: a chained row in mandate_revocations and a revoked mandate event; the mandate row is not modified and its hash does not change.

Parameters:

Parameter

Type

Description

mandate_id

str

UUID of the mandate to revoke

reason

str

Why, as it will be recorded (1 to 500 characters)

principal_signature

str | null

Ed25519 signature of the revocation payload (required if the principal has a registered key)

signed_at

str | null

The signed_at placed in the payload

Only the mandate's own principal revokes it (for a sub-mandate, the agent that delegated it). Same signature policy as create_mandate; build the exact bytes to sign with prepare_revocation_signature(mandate_id, reason, signed_at=None).

Effect, as soon as it returns: issue_action_token and issue_mandate_attestation refuse the mandate (mandate_status_revoked) and every sub-mandate below it (parent_mandate_revoked); verify_mandate no longer returns it; no sub-mandate can be created under it; log_action refuses it and every sub-mandate below it; get_proof and export_insurer_dossier report it. Tokens already issued cannot be called back and stay valid until their exp: the response gives their number and the last expiry, unless the receiver re-checks the token with check_action_token when it acts (see below). Mandate attestations already issued also stay valid until their exp (15 minutes by default) for a receiver that checks them offline: the response gives their number and last expiry too, for the mandate and its sub-mandates.

Returns:

{
  "status": "revoked",
  "mandate_id": "550e8400-e29b-41d4-a716-446655440000",
  "principal_id": "org:clinic",
  "reason": "agent exceeded its order limit",
  "revoked_at": "2026-10-23T20:30:01.123456+00:00",
  "principal_signature": null,
  "chain_hash": "a3f1...",
  "outstanding_tokens": {"still_valid": 1, "last_expires_at": "2026-10-23T20:34:12+00:00"},
  "outstanding_attestations": {"still_valid": 0, "last_expires_at": null}
}

A token lives at most AMR_TOKEN_LIFETIME_SECONDS (300 s by default) and never past the expiry of its mandate or of any mandate above it in the delegation chain, nor beyond the shortest risk class (risk_classes) that reaches its action type in that chain. The caller's lifetime_override_seconds can only shorten it. Breaking change for callers that asked for a token longer than the default: they now get the default. After a revocation, the window in which an already issued token still works is therefore at most that lifetime, whatever the depth of the chain. See docs/revocation-spec.md.


contest_action

The agent has already acted, and the principal disputes the act. Revoking the mandate stops what comes next; contest_action records that one past act, logged with log_action, is contested, when, why, and by whom. Append-only: a chained row in action_contests (one per action; it cannot be withdrawn or edited) and an action_contested mandate event. The action log, the mandate and every hash already recorded stay as they are.

Parameters:

Parameter

Type

Description

mandate_id

str

UUID of the mandate the action was logged under

action_id

str

action_id returned by log_action

reason

str

Why the act is contested (1 to 500 characters)

response_days

int

Days until respond_by (1 to 365), chosen by the operator

contested_by

str | null

The contesting principal; default: the mandate's principal

principal_signature

str | null

Ed25519 signature of the contest payload (required if the contesting principal has a registered key)

signed_at

str | null

The signed_at placed in the payload

contested_by may be the mandate's principal or the principal of any mandate above it in the delegation chain (an organization disputes what a sub-agent did), whatever the status of those mandates. Same signature policy as create_mandate and revoke_mandate; build the bytes with prepare_contest_signature (same arguments, plus signed_at=None).

respond_by = contested_at + response_days is the operator's own deadline, for example the delay its counsel gives before keeping or using what was ordered could be read as accepting it. AMR computes no legal deadline and asserts none. A contest does not undo the act and does not revoke the mandate: call revoke_mandate for that.

Returns:

{
  "status": "contested",
  "mandate_id": "550e8400-e29b-41d4-a716-446655440000",
  "action_id": "0b6f7c1e-3d0a-4c55-9a43-5d1f1f0e2a10",
  "action_chain_hash": "9c2e...",
  "contested_by": "org:clinic",
  "reason": "order placed above the agreed quantity",
  "response_days": 14,
  "contested_at": "2026-11-01T21:30:00+00:00",
  "reminder": {"respond_by": "2026-11-15T21:30:00+00:00", "days_left": 14, "deadline_passed": false},
  "principal_signature": null,
  "chain_hash": "5be0..."
}

Errors (code): invalid_input, mandate_not_found, action_not_found (also when the action belongs to another mandate), contester_not_in_chain, action_already_contested, and the principal signature codes of revoke_mandate.

Reminder: list_action_contests(mandate_id=None, contested_by=None, due_within_days=None) (read-only; give mandate_id, contested_by or both) returns the contests, nearest respond_by first, each with reminder.days_left (negative once passed) and reminder.deadline_passed. Call it on a schedule, for example each morning with due_within_days=3. get_proof lists the contests of a mandate in action_contests and re-checks them in chain_integrity.contests_intact. See docs/action-contest-spec.md.


check_action_token

For the receiver of an agent's action: re-check a token with the registry at the moment of acting, before an irreversible step. Required when the token carries amr_registry_check: true (a risk class with registry_check); possible for any token. Read only.

Parameter

Type

Description

token

str

The JWS returned by issue_action_token

audience

str

Who you are; must equal the token's aud

expected_action_hash

str | null

SHA-256 of the canonical action you are about to perform; must equal amr_action_hash

max_token_age_seconds

int | null

Your own, stricter bound (1 to 86400): refuse a token issued longer ago. Never extends exp

It verifies the signature (EdDSA only, this registry's key), iss, aud, exp, nbf, that the token is one the registry issued, then that the mandate and every mandate above it are still active, not revoked and not expired.

{"valid": true, "reason": "ok", "jti": "...", "mandate_id": "...", "agent_id": "agent:billing",
 "action_type": "invoice.pay", "registry_check_required": true,
 "expires_at": "2026-10-29T21:30:30+00:00", "checked_at": "2026-10-29T21:30:12.400000+00:00"}
{"valid": false, "reason": "mandate_status_revoked", "jti": "...", "mandate_id": "...",
 "checked_at": "2026-10-29T21:30:14.100000+00:00"}

The registry opens its database read-only and writes nothing for a check, and the answer is not signed: it is not evidence that anyone checked. Call the registry over your own channel; never accept a check result relayed by the agent whose token it is. Once a mandate with registry_check exists, an older AMR version cannot read its perimeter (it fails closed). See docs/token-issuance-spec.md, Registry Check at Execution.


get_proof

Generate a complete Proof Pack — a full audit bundle for a mandate including all actions and cryptographic chain verification.

Parameters:

Parameter

Type

Description

mandate_id

str

UUID of the mandate to prove

format

str

Output format (currently "json")

Returns:

{
  "proof_pack_version": "0.1.0",
  "generated_at": "2026-04-13T18:00:00+00:00",
  "mandate": { "...": "full mandate details" },
  "delegation_chain": {"broken": false, "reason": null, "refused_by": null},
  "actions": [ "...list of all logged actions..." ],
  "events": [],
  "action_contests": [],
  "chain_integrity": {
    "actions_chain_valid": true,
    "principal_signature_valid": null,
    "revocation_signature_valid": null,
    "contests_intact": true,
    "total_actions": 5,
    "total_events": 0
  },
  "compliance_notes": {
    "ai_act_art26": "This Proof Pack documents the mandate chain for AI Act Art.26 compliance.",
    "retention": "Logs must be retained for minimum 6 months per AI Act Art.26(6)."
  }
}

mandate.principal_signature holds the principal's signature, public key, the key's proof of possession and the signed bytes (or null); principal_signature_valid re-checks them against the stored mandate. It is false if the signature does not match, or if it is missing although the principal had a key when the mandate was created; null for a mandate whose principal had no key then.

Once revoke_mandate has run, mandate.status reads "revoked" (the stored value stays in mandate.stored_status) and mandate.revocation holds revoked_at, reason, the principal's signature of the revocation (or null) and its chain hash; revocation_signature_valid re-checks that signature against the principal's key (null when there is nothing to check).

delegation_chain says whether the chain above a sub-mandate still holds: {"broken": true, "reason": "parent_mandate_revoked", "refused_by": "<parent id>"} when a mandate above it is revoked, expired, not active or missing (the sub-mandate's own mandate.status does not change: the cut is above it); {"broken": false, "reason": null, "refused_by": null} otherwise, and always for a root mandate.


issue_mandate_attestation

Parameter

Type

Description

mandate_id

str

UUID of the mandate to attest

agent_id

str

Must match the mandate's agent

audience

str | null

Optional receiver it is addressed to

lifetime_seconds

int | null

Optional shorter lifetime (never longer than the configured maximum)

include_principal_payload

bool

Also carry the principal's signature and signed bytes (discloses metadata); default false

Returns {"attestation", "jti", "issued_at", "expires_at", "key_id", "issuer"}, or {"denied": true, "reason", "mandate_id"}, for example mandate_not_found, agent_mismatch, mandate_status_<status>, mandate_status_revoked, mandate_expired, parent_mandate_<status>, parent_mandate_revoked, principal_signature_invalid, with refused_by when a parent is the cause (full list in docs/mandate-attestation-spec.md).

get_registry_public_key

No parameters. Returns {"issuer", "key_id", "algorithm": "EdDSA", "public_key_pem", "public_key_jwk"}: what a receiver needs to verify attestations offline.

export_insurer_dossier

Export a signed dossier of one mandate over a period: the piece an insurer or an auditor asks for about an AI agent. Read-only; facts from the registry only.

Parameters:

Parameter

Type

Description

mandate_id

str

UUID of the mandate

period_start

str | null

ISO 8601 date-time start (naive = UTC; a bare date is refused); defaults to the mandate's creation

period_end

str | null

ISO 8601 date-time end (naive = UTC; a bare date is refused); defaults to now

format

str

"json" (default) or "html" to also get a printable page

The dossier holds: the mandate (principal, agent, scope, declared legal basis, dates); its perimeter (allowed and forbidden action types, right to delegate, amount limit); whether it is in force at generation time, and why not (mandate_revoked, mandate_expired, or a broken chain above it such as parent_mandate_revoked); its sub-mandates, each with depth, perimeter, in-force status and the number of actions it logged in the period (a cut mandate cuts its whole branch); the actions the mandate's own agent logged in the period; the status events and action tokens of the period, for the mandate and its sub-mandates; the integrity checks (action log chain, principal signature, last chain hash); and a fixed list of the dossier's limits (for example: refused token requests are not recorded, so the dossier lists issued tokens, not blocked actions).

Returns:

{
  "dossier": { "dossier_version": "1.0", "period": {}, "mandate": {}, "status": {},
               "integrity": {}, "sub_mandates": [], "events": [], "actions": [],
               "tokens": [], "limits": [] },
  "dossier_sha256": "hex SHA-256 of the canonical dossier",
  "signature": { "jws": "...", "algorithm": "EdDSA", "key_id": "...",
                 "public_key_pem": "..." },
  "html": "only when format is \"html\""
}

The signature is a JWS (RFC 7515, EdDSA / Ed25519, same registry key as action tokens) whose payload is the canonical JSON of the dossier. Verify it offline:

from amr.proof.insurer_dossier import verify_dossier

dossier = verify_dossier(jws, registry_public_key_pem)  # raises if anything is off

The export never creates a signing key: on a registry without one it returns an error. Get the registry public key out of band: public_key_pem in the export is a convenience, and a dossier that carries its own key proves nothing about who signed it. The HTML page is a self-contained print view (no external resources; print to PDF from the browser) that shows the dossier's SHA-256 and the key id; only the JSON signature is proof. See docs/insurer-dossier-spec.md.


How It Works

┌─────────────────────────────────────────────────────────────────────┐
│                         PRINCIPAL                                   │
│              (user:audric, org:acme-corp)                           │
└──────────────────────────┬──────────────────────────────────────────┘
                           │  create_mandate()
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│                    MANDATE (SHA-256 chained)                        │
│  id · principal · agent · scope · legal_basis · expires_at         │
│  chain_hash = SHA256(mandate_data + prev_hash)                      │
└──────────────────────────┬──────────────────────────────────────────┘
                           │  verify_mandate() → authorized: true
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│                          AGENT                                      │
│              (agent:korvex-advisor-v1)                              │
└──────────────────────────┬──────────────────────────────────────────┘
                           │  log_action() × N
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│                ACTION LOG (append-only, SHA-256 chained)            │
│  action_1 ──hash──▶ action_2 ──hash──▶ action_3 ──hash──▶ ...     │
│  Each action links to the previous via chain_hash                   │
└──────────────────────────┬──────────────────────────────────────────┘
                           │  get_proof()
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│                       PROOF PACK (JSON)                             │
│  mandate + actions + chain_integrity + compliance_notes             │
│  Ready for regulatory audit, eIDAS submission, or archival          │
└─────────────────────────────────────────────────────────────────────┘

Security

  1. Pydantic v2 validation — All tool inputs are validated before any DB interaction. Unknown fields are rejected (extra="forbid"). Duration, string lengths, and UUID formats are enforced at the model layer.

  2. Parameterized SQL only — Every database query uses ? placeholders. No string interpolation or f-strings in SQL. Enforced by ruff S608 rule on every commit.

  3. Append-only tables — The mandates and action_logs tables are never UPDATEd or DELETEd. The audit trail is immutable by design. Status changes use a separate mandate_events table.

  4. SHA-256 cryptographic chain — Each mandate and each action log entry is linked to the previous one via a SHA-256 hash (git-style). Any tampering invalidates all subsequent hashes, making the trail tamper-evident.

  5. stdio transport only — AMR runs as a local MCP server over stdio. No listening port, no inbound network exposure, no outbound HTTP from tools, with one opt-in exception: report_issue sends one HTTPS request to the GitHub API, and only when the operator has set AMR_ISSUE_TOKEN. The attack surface is minimal.


Regulatory Context

Regulation

Relevance to AMR

EU AI Act Art.26

Deployers of high-risk AI systems must use them as instructed, assign human oversight and keep the logs the system generates. AMR records the mandate behind each agent and its actions, as evidence for that oversight. Applies from 2 December 2027 (Annex III systems) and 2 August 2028 (Annex I systems), Regulation (EU) 2026/1744.

EU AI Act Art.26(6)

The logs a high-risk system generates must be kept for at least six months. AMR's append-only SQLite database and Proof Pack export support this requirement.

eIDAS 2.0

Electronic mandates for AI agents acting on behalf of natural or legal persons. AMR's mandate chain maps to the eIDAS electronic delegation trust framework.

Fines

Non-compliance with Article 26 can be fined up to €15 million or 3% of worldwide annual turnover, whichever is higher (Art. 99(4); for SMEs and start-ups, whichever is lower, Art. 99(6)).


Roadmap

Status

Features

Shipped

Mandate registry · SHA-256 chain · Proof Pack JSON · structured perimeters · sub-mandates and delegation chains · revocation · contested actions · signed action tokens (enforcement) and check at execution · offline mandate attestations · signed insurer dossier (JSON and printable HTML) · principal signatures and did:web keys · read-only public registry · sector template (health) · MCP SDK v1 and v2 · PyPI package mandatia

Planned

PDF Proof Pack export · remote transport for MCP clients · PostgreSQL backend · multi-tenant support · REST API alongside MCP · eIDAS 2.0 qualified electronic signatures · cross-border mandate recognition · webhook notifications · dashboard and alerting


Development

# Install all dependencies including dev extras
uv sync --all-extras

# Run the full test suite
uv run pytest -v

# Run with short traceback on failure
uv run pytest -v --tb=short

# Lint and security check (src + tests)
uv run ruff check src/ tests/

# Auto-fix safe issues
uv run ruff check --fix src/ tests/

# Start the MCP server (stdio — normal to wait for input)
uv run amr

Project Structure

src/amr/
├── config.py          # Settings via pydantic-settings (env prefix: AMR_)
├── server.py          # MCP server entrypoint (stdio transport; mcp 1.x or 2.x)
├── models/mandate.py  # Pydantic models: MandateCreate, Mandate, ActionLog
├── crypto/chain.py    # compute_hash() and verify_chain() — stdlib only
├── db/engine.py       # init_db(), get_db(), get_last_mandate_hash()
├── tools/
│   ├── create_mandate.py
│   ├── prepare_mandate_pdf.py   # the mandate as a PDF the principal signs itself
│   ├── verify_mandate.py
│   ├── log_action.py
│   ├── get_proof.py
│   ├── check_action_token.py          # receiver re-checks a token when it acts
│   ├── issue_mandate_attestation.py   # attestation + registry public key
│   ├── revoke_mandate.py
│   ├── contest_action.py   # contested acts and their response deadline (reminder)
│   ├── report_issue.py   # network call, opt-in (AMR_ISSUE_TOKEN)
│   └── export_insurer_dossier.py
├── attestation.py     # offline attestation verifier (no DB, no network)
├── did_web.py         # did:web key check, network opt-in (AMR_DID_WEB_RESOLVE)
├── mandate_pdf.py     # deterministic mandate PDF, payload attached (stdlib only)
├── verify_attestation.py  # python -m amr.verify_attestation
├── proof/insurer_dossier.py  # Signed insurer dossier: canonical form, JWS, print view
└── templates/         # Sector mandate templates (agent-sante-rgpd)
tests/
├── conftest.py              # Isolated temp DB fixture (autouse)
└── test_*.py                # One file per tool or feature (`uv run pytest -q`)

Environment Variables

Variable

Default

Description

AMR_DB_PATH

amr_mandates.db

Path to the SQLite database file

AMR_LOG_LEVEL

INFO

Logging verbosity

AMR_SERVER_NAME

Agent Mandate Registry

MCP server display name

AMR_REQUIRE_PRINCIPAL_SIGNATURE

false

Refuse every mandate not signed by its principal

AMR_PRINCIPAL_SIGNATURE_MAX_AGE_SECONDS

900

Allowed gap between signed_at and the registry clock

AMR_ISSUE_TOKEN

unset

GitHub token that lets report_issue file issues itself (fine-grained, Issues: write, one repository). Read from the process environment only, never from .env. Unset: no network call

AMR_DID_WEB_RESOLVE

unset

1 lets register_principal_key read the DID document of a did:web principal (one HTTPS GET to that domain). Read from the process environment only, never from .env. Unset: did:web keys are refused

AMR_ISSUE_REPOSITORY

JC7333/amr

Repository report_issue files issues on (owner/name)

AMR_ATTESTATION_LIFETIME_SECONDS

900

Longest lifetime of a mandate attestation

AMR_PUBLIC_EXPORT_PATH

amr_public_registry.jws

Signed snapshot written by python -m amr.public_registry export and served by its app

AMR_PUBLIC_KEY_PATH

amr_signing_key.pub

Registry public key; the public registry app pins it to verify the snapshot

AMR_PUBLIC_SNAPSHOT_MAX_AGE_SECONDS

unset

Public registry: answer snapshot_too_old instead of a status past this age


License

Apache License 2.0 — See LICENSE for full terms.

Free to use in commercial products. Compatible with EU AI Act open-source provisions.


⚠️ Disclaimer: AMR is a technical infrastructure tool. It does not constitute legal advice. Compliance with EU AI Act, eIDAS 2.0, or any other regulation remains the responsibility of the operator. Consult qualified legal counsel for your specific regulatory obligations.

Available Tools

18 tools
check_action_tokenA

Re-check an action token with the registry at the moment of acting.

For the receiver of an agent's action, typically before an irreversible one (a payment, a sent document, a signed order), and always when the token carries amr_registry_check: true. The signature only proves the action was authorized when the token was issued; this check also says whether it still is now.

Checks, in order: header (EdDSA only, key of this registry), signature, iss, aud, exp, nbf; the token is one this registry issued (its jti, mandate, action hash and audience match the issued_tokens record); the action hash, if you give one; the token's age, if you set a maximum; then the mandate is still active, not revoked, not expired and held by the token's agent, and so is every mandate above it in the delegation chain. Read only: the database is opened read-only, nothing is written, and the answer is not signed. Call the registry over your own channel: never accept a check result relayed by the agent whose token it is.

Args: token: The compact JWS returned by issue_action_token. audience: Who you are; must equal the token's aud. expected_action_hash: Optional SHA-256 (64 lowercase hex) of the canonical action you are about to perform; must equal amr_action_hash. max_token_age_seconds: Optional stricter bound you set as receiver (1 to 86400): refuse a token issued more than this many seconds ago. It can only tighten the check; a token past its exp is refused whatever you set.

Returns: JSON string. Valid: {valid: true, reason: "ok", jti, mandate_id, agent_id, action_type (if sealed), registry_check_required, expires_at, checked_at}. Refused: {valid: false, reason, detail?, jti?, mandate_id?, refused_by?, checked_at}. Reasons: invalid_input, malformed, wrong_algorithm, key_mismatch, bad_signature, wrong_issuer, wrong_audience, token_expired, token_not_yet_valid, missing_claim, registry_unavailable, unknown_token, token_record_mismatch, action_mismatch, token_too_old, mandate_not_found, agent_mismatch, mandate_status_, mandate_status_revoked, mandate_expired, parent_mandate_, parent_mandate_revoked, parent_mandate_expired, parent_mandate_not_found, delegation_chain_too_deep, internal_error.

NameRequiredDescriptionDefault
tokenYes
audienceYes
expected_action_hashNo
max_token_age_secondsNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations supplied, the description carries the full burden and does: it enumerates the check sequence (header algorithm, signature, iss/aud/exp/nbf, issued_tokens record, action hash, age, mandate and full delegation chain), declares read-only database access, notes the response is unsigned, and warns about out-of-band channel trust.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and trigger, then layered detail; the sectioning (checks / Args / Returns) is easy to scan. The long enumeration of refusal reason strings is partly redundant given an output schema exists, which keeps it from a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a security-critical verification tool with four parameters and an output schema, the description supplies everything an agent needs: purpose, prerequisites (amr_registry_check), check ordering, refusal semantics, and channel-trust guidance. Nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, yet the description documents all four parameters beyond the schema: token is the compact JWS from issue_action_token, audience must equal the token's aud, expected_action_hash is a 64-char lowercase hex SHA-256 matching amr_action_hash, and max_token_age_seconds is bounded 1–86400 and can only tighten the check.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource plus a timing qualifier: 'Re-check an action token with the registry at the moment of acting.' This clearly separates it from issue_action_token (issuance) and verify_mandate (mandate-level verification) in the sibling list.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States exactly when to call it — before an irreversible action (payment, sent document, signed order) and always when amr_registry_check is true — and explains the gap it fills versus signature-only verification. It also gives a negative instruction about never trusting a relayed result from the agent whose token it is.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

contest_actionA

Record that a principal contests an action an agent already performed.

Use it when an act logged with log_action was not wanted: the registry keeps a dated, chained record that the act was disputed, and when, so silence is not the only trace. One contest per action; it cannot be withdrawn or edited.

The contesting principal is the mandate's principal (default) or the principal of any mandate above it in the delegation chain. If that principal has a registered key, the contest must carry its signature: build the bytes with prepare_contest_signature, sign them, pass principal_signature and signed_at. With AMR_REQUIRE_PRINCIPAL_SIGNATURE, every contest must be signed.

response_days sets respond_by = contested_at + response_days. It is the operator's own deadline (for example the delay its counsel gives before keeping or using what was ordered could be read as ratifying it): AMR computes no legal deadline and asserts none. list_action_contests reminds it.

A contest does not undo the action, does not revoke the mandate (call revoke_mandate for that) and changes no hash already recorded.

Args: mandate_id: UUID of the mandate the action was logged under. action_id: action_id returned by log_action. reason: Why the act is contested (1 to 500 characters). Example: "order placed above the agreed quantity" response_days: Days until respond_by, 1 to 365. Example: 14 contested_by: The contesting principal. Default: the mandate's principal. principal_signature: Base64url Ed25519 signature of the contest payload. signed_at: The signed_at placed in the payload (ISO 8601 with timezone).

Returns: JSON string with keys: status ("contested"), mandate_id, action_id, action_chain_hash, contested_by, reason, response_days, contested_at, reminder ({respond_by, days_left, deadline_passed}), principal_signature ({key_id, signed_at} or null), chain_hash. On error: {"error": "...", "code": "..."} with code invalid_input, mandate_not_found, action_not_found, contester_not_in_chain, action_already_contested or a principal signature code.

NameRequiredDescriptionDefault
reasonYes
action_idYes
signed_atNo
mandate_idYes
contested_byNo
response_daysYes
principal_signatureNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: it discloses immutability (one contest per action, cannot be withdrawn or edited), the signature requirement tied to the contester's registered key and the AMR_REQUIRE_PRINCIPAL_SIGNATURE setting, and that no legal deadline is asserted. It also clarifies side-effect boundaries, noting that no already-recorded hash changes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Long but front-loaded and sectioned (behavior, then Args, then Returns); nearly every sentence carries operational information. The Returns block partially restates what the existing output schema already provides, which is the main redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a high-complexity mutation with signature gating, multi-step prerequisites, and immutability, the description covers preconditions, failure codes, defaults, and follow-up tools. An agent could call this correctly and route signing through prepare_contest_signature without consulting anything else.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it documents all seven parameters: mandate_id and action_id provenance, reason length bounds (1-500) with an example, response_days range (1-365) and its computed effect on respond_by, the contested_by default, and the payload linkage between principal_signature and signed_at.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: records that a principal contests an action already performed, scoped to an act logged with log_action. It explicitly differentiates from siblings, noting that revoke_mandate is the revocation path and list_action_contests is the reminder surface.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit triggering condition (use when an act logged with log_action was not wanted) and names alternatives with their purposes (revoke_mandate for revocation, prepare_contest_signature for signing, list_action_contests for follow-up). It also states the exclusions: does not undo the action, does not revoke the mandate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_mandateA

Create and register a new legal mandate authorizing an AI agent to act on behalf of a principal.

A mandate defines the legal basis, scope, and duration of an agent's authorization. Each mandate is cryptographically linked to the previous one (SHA-256 chain).

The optional structured perimeter (allowed_actions, forbidden_actions, delegation_allowed, max_delegation_depth, max_amount, period_max_amount, risk_classes) is enforced by issue_action_token: an action outside it gets no token. Without it, the mandate is a free-text scope only.

The principal can sign the mandate with its own Ed25519 key (registered once with register_principal_key): build the exact bytes with prepare_mandate_signature, sign them, and pass principal_signature and signed_at. A principal with a registered key must always sign; the signature is sealed into chain_hash and kept in the Proof Pack.

When a body (committee, board) authorized the agent, its decision goes in metadata under the reserved key "organ_decision": body, title, decided_on (YYYY-MM-DD, not in the future, judged on the registry's UTC date), optional reference, and authorized_personnel (the persons or roles the decision designates), as the compact JSON of OrganDecision.to_metadata_value(). It needs allowed_actions without patterns and is refused on a sub-mandate. It is validated, sealed into chain_hash, and shown by verify_mandate and get_proof.

Args: principal_id: Unique identifier of the principal granting the mandate. Example: "user:audric" or "org:acme-corp" agent_id: Unique identifier of the agent receiving authorization. Example: "agent:korvex-advisor-v1" scope: Human-readable description of what the agent is authorized to do. Example: "Compare and recommend financial products to retail clients" legal_basis_regulation: Name of the regulation or contract granting authority. Example: "EU AI Act Art.26" or "Service Agreement 2026" legal_basis_article: Specific article, clause, or reference number. Example: "Art.26(1)(a)" or "Mandat conseil 2026-042" duration_hours: Duration of the mandate in hours. Max 8760 (1 year). Example: 720 (30 days), 168 (1 week), 24 (1 day) legal_basis_jurisdiction: Jurisdiction code. Default "EU". Example: "EU", "FR", "US" parent_mandate_id: UUID of a parent mandate for sub-delegation chains. The parent (and every mandate above it) must be active and unexpired; the parent must allow delegation and have this mandate's principal_id as its agent_id; the sub-mandate must declare allowed_actions within the parent's, and cannot outlive it. Example: "550e8400-e29b-41d4-a716-446655440000" metadata: Optional dict of string key-value pairs for custom annotations. Example: {"client_ref": "CL-2026-001", "advisor_code": "ADV42"} The key "organ_decision" is reserved (see above). allowed_actions: Action types the agent may request. Dotted lowercase identifiers, "domain." prefix patterns, or "". Example: ["invoice.read", "invoice.pay"] forbidden_actions: Action types the agent may never request (wins over allowed). Example: ["agent.create", "invoice.delete"] delegation_allowed: Whether the agent may grant sub-mandates to other agents. Default False. max_delegation_depth: Optional, with delegation_allowed: how many levels of sub-mandates may exist below this mandate (1 to 16). 1 lets the agent delegate but not its sub-agents. Absent: no limit set by this mandate. Refused with delegation_depth_exceeded. max_amount: Optional ceiling per action, as a plain decimal string with a dot. Example: "500" or "120.50" currency: ISO 4217 code of max_amount. Default "EUR". amount_actions: Action types that move money: they must carry "amount" and "currency" metadata, the amount at or below max_amount. A pattern also reaches its sub-types. Empty means every action. Example: ["invoice.pay"] principal_signature: Base64url Ed25519 signature by the principal of the payload returned by prepare_mandate_signature for these same arguments and signed_at. Required if the principal has a registered key. signed_at: The ISO 8601 timestamp (with timezone) placed in the signed payload. Must be within 15 minutes of the registry clock by default. Example: "2026-10-18T20:30:00+00:00" period_max_amount: Optional cumulative ceiling, same currency and same amount_actions as max_amount: the total of the amounts of the tokens issued under this mandate and every sub-mandate below it, over the last period_days days, stays at or below it. Plain decimal string. Example: "2000" period_days: Length of the rolling window of period_max_amount, in whole days (1 to 366). Required with period_max_amount. Example: 30 risk_classes: Optional risk classes bounding the lifetime of action tokens: each {"name", "actions", "max_token_seconds"}. A class reaches its action patterns and their sub-types; a token for an action it reaches lives at most max_token_seconds (1 to 86400), the shortest class winning, in this mandate and every sub-mandate below it. A class may add "registry_check": true: its tokens then carry amr_registry_check, and the receiver re-checks the registry with check_action_token when it acts. Example: [{"name": "irreversible", "actions": ["invoice.pay"], "max_token_seconds": 30, "registry_check": true}]

Returns: JSON string with keys: status, mandate_id, principal_id, agent_id, scope, perimeter, parent_mandate_id, principal_signature, expires_at, organ_decision, chain_hash. principal_signature is {key_id, signed_at} or null; organ_decision is the recorded decision or null. On error: {"error": "...", "code": "..."} (code present for perimeter, delegation, organ decision and principal signature errors).

NameRequiredDescriptionDefault
scopeYes
agent_idYes
currencyNoEUR
metadataNo
signed_atNo
max_amountNo
period_daysNo
principal_idYes
risk_classesNo
amount_actionsNo
duration_hoursYes
allowed_actionsNo
forbidden_actionsNo
parent_mandate_idNo
period_max_amountNo
delegation_allowedNo
legal_basis_articleYes
principal_signatureNo
max_delegation_depthNo
legal_basis_regulationYes
legal_basis_jurisdictionNoEU

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden and does: it discloses cryptographic chain linking, that the signature is sealed into chain_hash and stored in the Proof Pack, a 15-minute clock tolerance, and refusal conditions (delegation_depth_exceeded, sub-mandate restrictions, future dated organ decisions) with error codes. This is exactly the behavioral context annotations would otherwise provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Long, but justified by 21 parameters; it is front-loaded with the overall purpose and the realm of each field. The signature flow and organ_decision rules are stated before the args list, so the agent gets context before detail. A few passages (e.g., the organ_decision block) are dense enough to require re-reading, but nothing is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the full lifecycle: creation, validation rules, perimeter enforcement, signing workflow, error codes, and return payload keys. Given the complexity (sub-delegation chains, period-based cumulative limits, risk classes), nothing an agent needs to call it correctly appears to be missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Critical gap-filler: schema coverage is 0% across 21 parameters, yet nearly every one is documented with defaults, ranges (duration_hours max 8760, max_delegation_depth 1-16, period_days 1-366, max_token_seconds 1-86400), formats (plain decimal string, dotted lowercase actions, ISO 8601 with timezone) and examples. The 'Returns' block also compensates for the sparse output documentation. Minor deduction only because nested structures like risk_classes are described in prose rather than enumerated field-by-field, but the example covers it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Create and register a new legal mandate authorizing an AI agent') and immediately defines what a mandate is (legal basis, scope, duration, SHA-256 chain). It also distinguishes itself from siblings by naming issue_action_token as the enforcer and register_principal_key/prepare_mandate_signature as the signing prerequisites.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives rich conditional guidance: when the structured perimeter applies vs. free-text scope, when a principal signature is mandatory, and when/how to use the reserved 'organ_decision' metadata key. The parent_mandate_id entry spells out exactly when sub-delegation is permissible. It stops short of explicit when-not-to-use routing (e.g., revoke_mandate), but for a create tool the conditional rules are unusually thorough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

export_insurer_dossierA

Export a signed insurer dossier for a mandate over a period.

The dossier puts in one signed document what an insurer or an auditor asks about an AI agent: who granted the mandate, its perimeter (allowed and forbidden action types, right to delegate, amount limit), whether it is in force or cut (status, expiry, broken delegation chain), its sub-mandates, the status events, actions and action tokens of the period, and the integrity checks (action log chain, principal signature). Facts from the registry only, plus a fixed list of the dossier's limits. Read-only: nothing is written to the registry.

The dossier is signed with the registry's Ed25519 key as a JWS (EdDSA) whose payload is the canonical JSON of the dossier. Verify it offline with amr.proof.insurer_dossier.verify_dossier and the registry public key obtained out of band.

Args: mandate_id: UUID of the mandate to export. Example: "550e8400-e29b-41d4-a716-446655440000" period_start: Optional ISO 8601 start of the period (naive = UTC). Defaults to the mandate's creation. Example: "2026-10-01T00:00:00Z" period_end: Optional ISO 8601 end of the period (naive = UTC). Defaults to now. Example: "2026-10-31T23:59:59Z" format: "json" (default) or "html" to also get a printable page.

Returns: JSON string with keys: - dossier: dict (dossier_version, generated_at, period, mandate, status, integrity, sub_mandates, sub_mandates_truncated, events, actions, tokens, limits) - dossier_sha256: hex SHA-256 of the canonical dossier - signature: {jws, algorithm "EdDSA", key_id, public_key_pem} - html: printable page (only when format is "html") On error: {"error": "..."}.

NameRequiredDescriptionDefault
formatNojson
mandate_idYes
period_endNo
period_startNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations present, the description carries the full burden and does so thoroughly: read-only with 'nothing is written to the registry', facts sourced from the registry only, signing scheme (Ed25519 key, JWS EdDSA over canonical JSON), a declared digest and public key in the response, an explicit limits list, sub_mandates_truncated signalling, and error shape. An agent knows the safety profile, output contract, and verification path before calling.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in one sentence, then structured into Args and Returns sections that are easy to scan. The Args detail is justified by the empty schema descriptions, but the Returns block partly restates an existing output schema, making the definition longer than strictly necessary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-required-parameter export tool with no annotations and undocumented schema fields, everything an agent needs is present: what is produced, what it is signed with, how to verify it, parameter formats and defaults, and the error path. Nothing material is left to inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it does: mandate_id is documented as a UUID with an example, both period bounds are documented as optional ISO 8601 with naive-UTC semantics, concrete defaults (mandate creation / now), and format is documented as json (default) vs html with its extra payload. This adds meaning well beyond the bare string/null types in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence gives a specific verb+resource+scope: 'Export a signed insurer dossier for a mandate over a period.' The body then enumerates exactly what the dossier contains (mandate, perimeter, status, sub-mandates, events, actions, tokens, integrity checks), which is far more specific than any sibling (get_proof, verify_mandate) and lets an agent distinguish the tool without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It establishes the audience and trigger ('what an insurer or an auditor asks about an AI agent') and explicitly states the read-only nature, plus a follow-up workflow (verify offline with verify_dossier and the out-of-band public key). It stops short of a 5 because it never names sibling alternatives (get_proof, verify_mandate, list_action_contests) or states when this tool is the wrong choice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_proofA

Generate a complete Proof Pack for a mandate, including all actions and chain integrity.

The Proof Pack documents the full mandate chain for regulatory compliance (EU AI Act Art.26, eIDAS 2.0). It includes the mandate details, all logged actions, mandate events, and cryptographic chain integrity verification.

Args: mandate_id: UUID of the mandate to generate a proof pack for. Example: "550e8400-e29b-41d4-a716-446655440000" format: Output format. Currently only "json" is supported. Example: "json"

Returns: JSON string (Proof Pack) with keys: - proof_pack_version: str - generated_at: ISO datetime string - mandate: dict with full mandate details, including principal_signature ({signature, signed_at, key_id, public_key, key_proof_signature, payload, algorithm} or null), organ_decision (the decision of the body that authorized the agent, or null), status ("revoked" once revoke_mandate ran, whatever the stored status, which stays in stored_status) and revocation ({revoked_at, reason, principal_signature, chain_hash} or null) - delegation_chain: dict with broken (bool: true when a mandate above this sub-mandate is revoked, expired, not active or missing, so the mandate is not in force whatever its own status), reason (parent_mandate_revoked, parent_mandate_expired, parent_mandate_, parent_mandate_not_found, delegation_chain_too_deep, or null) and refused_by (the mandate that broke the chain, or null); always unbroken for a root mandate - actions: list of action log entries - events: list of mandate events - action_contests: list of the actions contested under this mandate (contest_action), oldest first, each with action_id, action_chain_hash, contested_by, reason, response_days, contested_at, reminder {respond_by, days_left, deadline_passed}, principal_signature, chain_hash and principal_signature_valid (null when unsigned and the contesting principal had no key then); empty when nothing is contested - chain_integrity: dict with actions_chain_valid (bool), principal_signature_valid (bool; null for an unsigned mandate whose principal had no key when it was created), revocation_signature_valid (bool; null when the mandate is not revoked or was revoked without a signature by a principal that had no key; false when a "revoked" event has lost its revocation record), contests_intact (bool: false when an "action_contested" event has lost its contest record or a contest record has lost its event, when a contest's chain_hash does not recompute from its row and the previous contest, when a contest names an action that is not in this mandate's log or whose hash changed, or when a contest signature does not verify), total_actions, total_events - compliance_notes: dict with ai_act_art26 and retention notes On error: {"error": "..."}.

NameRequiredDescriptionDefault
formatNojson
mandate_idYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and largely meets it: it discloses the construction of the payload, the meaning of broken delegation chains, null-signature semantics, and how contests_intact turns false. What is missing is any statement about side effects (does generating persist anything?), idempotency, or authorization requirements for reading a mandate's full log.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose sentence is front-loaded and efficient, but the subsequent 'Returns' block is an extensive field-by-field enumeration that duplicates the output schema the tool already declares. Given has_output_schema=true, a large share of this text does not earn its place, though the nested null-semantics notes are genuinely informative.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a high-complexity, compliance-critical tool with two simple parameters, the definition supplies more than enough to invoke correctly and to interpret the resulting proof pack. The only material gap is operational context — permissions, whether generation is read-only, and how it relates to the sibling verification/export tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate and does: mandate_id is documented as a UUID with a concrete example, and format is constrained ('currently only "json" is supported') with a default implied by the example. No ambiguity remains about either parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opening sentence names a specific verb and artifact: 'Generate a complete Proof Pack for a mandate, including all actions and chain integrity,' which tells an agent exactly what is produced. It does not, however, differentiate itself from the neighboring export/verification tools (verify_mandate, export_insurer_dossier), which also produce mandate-derived artifacts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied through the regulatory framing ('for regulatory compliance (EU AI Act Art.26, eIDAS 2.0)'), which hints at audit/compliance scenarios. There is no explicit 'use this when...' statement, no exclusion of alternatives such as verify_mandate or export_insurer_dossier, and no prerequisite or side-effect guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_registry_public_keyA

Return the registry's public key, for third parties who verify attestations.

A receiver fetches it once, through a channel it trusts, and compares key_id with the one the registry operator publishes. It then verifies attestations offline.

Returns: JSON string with keys: issuer, key_id, algorithm ("EdDSA"), public_key_pem, public_key_jwk ({kty "OKP", crv "Ed25519", x, kid, alg}). On error: {"error": "..."}.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations supplied, the description carries the behavioral burden and does so well: it discloses the trust-channel requirement, that the fetch is a one-time operation, the key_id comparison step, offline verification, and the error shape ({'error': '...'}). It does not cover authentication requirements or rate limits, but for a public-key endpoint that is a minor gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first sentence, the operational workflow second, and the return shape last. Every sentence is load-bearing and no filler appears.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An agent knows what the tool returns, how to use the result, and what failure looks like. The return fields are also restated in prose despite an output schema existing, which errs on the side of completeness for a zero-param tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4; there is nothing for the description to disambiguate in the input schema and it correctly spends no words on parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (return the registry's public key) plus the audience and goal (third parties who verify attestations). It is clearly distinguishable from siblings such as register_principal_key or issue_mandate_attestation, which write keys or mint attestations rather than expose the registry key for offline verification.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a concrete usage pattern: fetch once through a trusted channel, compare key_id against the operator-published value, then verify attestations offline. That is clear when-to-use context, but it names no alternatives or explicit when-not conditions, so it stops short of the top mark.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

issue_action_tokenA

Issue a signed action token IF AND ONLY IF a valid mandate authorizes it.

This is the hard-stop: no valid mandate → no token → no action possible. When the mandate (or any mandate above it in a delegation chain) has a structured perimeter, the requested action_type must be allowed, must not be forbidden, and must respect the amount limit, at every level of the chain. A cumulative ceiling (period_max_amount over period_days) on any mandate of the chain caps the total amount of the tokens issued under it and every sub-mandate below it in the rolling window, this one included; an issued token counts whether it is used or not. Every parent mandate must also still be active and unexpired. Risk classes of the chain shorten the token's lifetime; the matched class names are sealed into the token as amr_risk_classes. When one of them sets registry_check, the token carries amr_registry_check: true, and the receiver must re-check it with check_action_token at the moment of acting. Returns a JSON string with either {token, jti, expires_at, key_id} on success, or {denied: true, reason: "...", mandate_id: "..."} on denial. Perimeter denials use reason "action_out_of_scope", with "detail" and "refused_by" (the mandate whose perimeter refused the action).

Args: mandate_id: UUID of the mandate the agent invokes. agent_id: Agent identifier, must match mandate.agent_id. action: Description of the action to be authorized. audience: Where the token will be presented (runtime/platform URL or id). action_metadata_json: Optional JSON-encoded dict of additional action metadata. lifetime_override_seconds: Optional shorter token lifetime. It can only shorten: the lifetime is never above settings.token_lifetime_seconds. Whatever is asked, the token expires no later than the mandate and every mandate above it in the delegation chain, and lives no longer than the shortest risk class (risk_classes) reaching action_type in any perimeter of the chain. action_type: Dotted action type checked against the mandate perimeter, e.g. "invoice.pay" or "agent.create". Required when the mandate (or a mandate above it) has a structured perimeter; sealed into the token as amr_action_type. Under a free-text mandate it is sealed but not checked (the token then carries no amr_perimeter_digest).

Returns: JSON string. Success keys: token, jti, expires_at, key_id. Denial keys: denied (true), reason, mandate_id.

NameRequiredDescriptionDefault
actionYes
agent_idYes
audienceYes
mandate_idYes
action_typeNo
action_metadata_jsonNo{}
lifetime_override_secondsNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full behavioral burden and does so thoroughly: it discloses denial semantics and reason codes ('action_out_of_scope' with detail/refused_by), cumulative ceiling accounting ('an issued token counts whether it is used or not'), lifetime shortening across the chain, and claims sealed into the token (amr_risk_classes, amr_registry_check, amr_action_type, amr_perimeter_digest). These are non-obvious consequences an agent could not infer from the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The hard-stop rule is front-loaded and the dense policy prose is justified for a security-critical gate. However, the body already states the return shape ('Returns a JSON string with either {token, jti, ...} or {denied: true, ...}') and the trailing 'Returns:' block repeats it verbatim in substance, which is avoidable redundancy given an output schema exists.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a high-stakes gated-issuance tool with 7 parameters and no annotations, the description covers prerequisites, chain-wide constraints, denial cases, and lifetime bounds. Return values are backed by an output schema, so the description is complete enough for an agent to call it correctly and to interpret refusals.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it documents every one of the seven parameters with meaning beyond the titles: agent_id must match mandate.agent_id, action_type is dotted and required only under a structured perimeter, and lifetime_override_seconds can only shorten with explicit chain and risk-class bounds. This is exactly the compensation the low-coverage case demands.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Issue a signed action token') plus the governing condition ('IF AND ONLY IF a valid mandate authorizes it'). It is clearly separable from siblings such as check_action_token (which re-verifies a token) and verify_mandate (which validates the mandate), so an agent can choose correctly without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives strong conditional context: no valid mandate means no token and no action, and the mandate chain must be active, unexpired, and within perimeter/amount limits. It also mandates a later call to check_action_token when amr_registry_check is set, which is genuine cross-tool routing. It stops short of explicitly naming alternatives for the issuance step itself, so not a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

issue_mandate_attestationA

Issue a signed attestation of a mandate, for the agent to show a third party.

The receiver (a seller, a hospital, an insurer) verifies it offline with the registry's public key (see get_registry_public_key and amr.verify_attestation), without an account and without calling the registry. It states that the mandate exists, who granted it, the agent, the scope and structured perimeter, the expiry, the principal's own signature when there is one, and that the mandate and every mandate above it were active when the attestation was issued.

Issued only if the mandate is active and unexpired, belongs to agent_id, every parent mandate is active and unexpired, and no principal signature in the chain is invalid or stripped. The attestation expires at the earliest of: now plus the lifetime (at most settings.attestation_lifetime_seconds), the mandate's expiry, any parent's expiry. A revocation made after issuance reaches an offline receiver only when the attestation expires: ask for a fresh one for each important act.

Args: mandate_id: UUID of the mandate to attest. agent_id: Agent identifier, must match mandate.agent_id. audience: Optional receiver the attestation is addressed to (e.g. "seller:acme"). The receiver must then name this audience when verifying. lifetime_seconds: Optional shorter lifetime. It can shorten, never extend, the configured maximum. include_principal_payload: For a principal-signed mandate, also include the principal's signature and the exact bytes it signed, so the receiver can re-check it without trusting the registry. Off by default: those bytes contain the mandate metadata, which is then disclosed to the receiver.

Returns: JSON string. Success keys: attestation (compact JWS), jti, issued_at, expires_at, key_id, issuer. Denial keys: denied (true), reason, mandate_id, and refused_by for a problem higher in the delegation chain.

NameRequiredDescriptionDefault
agent_idYes
audienceNo
mandate_idYes
lifetime_secondsNo
include_principal_payloadNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: it states the issuance preconditions (mandate active and unexpired, belongs to agent_id, all parents active, no invalid/stripped principal signature), the expiry computation as the minimum of three bounds, and the critical revocation-visibility limitation for an offline receiver. It also discloses the privacy cost of include_principal_payload.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded purpose sentence, then constraints, then an Args/Returns block, so scanning is easy and every section is useful. It runs long and repeats the 'active and unexpired' condition across two paragraphs, which is a minor redundancy rather than wasted space.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The Returns block summarizes both success keys (attestation JWS, jti, issued_at, expires_at, key_id, issuer) and denial keys including refused_by, even though an output schema exists, and the behavioral preconditions and expiry rules are all present. Nothing an agent needs to call this correctly or interpret a denial is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate and does: it defines mandate_id and agent_id with the matching constraint, audience as an addressed receiver that must be named at verification, lifetime_seconds as shorten-only and never extending the configured maximum, and include_principal_payload's exact contents and default-off disclosure tradeoff.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Issue a signed attestation of a mandate') and immediately names its purpose ('for the agent to show a third party'), which cleanly separates it from siblings like verify_mandate, get_proof, or issue_action_token. The agent knows exactly what artifact it gets back.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly frames the use case (offline verification by a seller, hospital, insurer, without an account or registry call), points to the receiver-side path (get_registry_public_key and amr.verify_attestation), and gives an operational rule: request a fresh attestation for each important act. No exclusions are left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_action_contestsA

List contested actions with the days left before their respond_by (the reminder).

Read-only. Give mandate_id, contested_by, or both. Call it on a schedule (for example each morning) to see which contests reach their response deadline soon. The deadline is the operator's own (response_days of contest_action), not a legal deadline computed by AMR.

Args: mandate_id: Only the contests under this mandate. contested_by: Only the contests recorded by this principal. due_within_days: Only the contests whose respond_by is at most this many days away, including those already passed (0 to 365).

Returns: JSON string with keys: generated_at, count, truncated (true if more than 500 matched; the 500 nearest deadlines are returned) and contests (list, nearest respond_by first, each with mandate_id, action_id, action_chain_hash, contested_by, reason, response_days, contested_at, reminder {respond_by, days_left, deadline_passed}, principal_signature, chain_hash). On error: {"error": "...", "code": "invalid_input"}.

NameRequiredDescriptionDefault
mandate_idNo
contested_byNo
due_within_daysNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden and does so: it declares 'Read-only,' discloses the 500-record truncation and nearest-deadline ordering, explains the deadline is the operator's own response_days rather than a legal deadline computed by AMR, and specifies the error shape. These are exactly the behavioral facts an agent cannot infer from the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose and read-only status, then partitioned into Args and Returns blocks, so it is easy to scan. The Returns block is fairly long and partly duplicates the declared output schema, which costs some tightness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-required-parameter list tool with empty schema descriptions, it covers filtering, date semantics, ordering, truncation, error format, and scheduling use. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, yet every parameter is explained with added meaning the schema lacks — due_within_days is scoped to 0–365 and explicitly includes already-passed deadlines, and the two ID filters are described as restrictive scoping rather than required inputs. This fully compensates for the empty schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List contested actions') plus the distinguishing payload ('days left before their respond_by'), which separates it from the write-side sibling contest_action. An agent knows exactly what comes back without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete usage context: 'Give mandate_id, contested_by, or both' and 'Call it on a schedule (for example each morning) to see which contests reach their response deadline soon.' It does not name an alternative tool or state when not to use it, so it stops short of full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

log_actionA

Log an action performed by an AI agent under an active mandate for audit and compliance.

Each action log entry is cryptographically linked to the previous action (SHA-256 chain), creating a tamper-evident audit trail. The mandate must be active, not revoked and not expired; for a sub-mandate, so must every mandate above it in the delegation chain (revoking or letting expire a parent stops the logging of its whole branch).

Args: mandate_id: UUID of the mandate under which the action was performed. Example: "550e8400-e29b-41d4-a716-446655440000" action: Description of the action that was performed. Example: "Generated investment recommendation for Product X" evidence: Optional supporting evidence or reference for the action. Example: "email:msg_abc123" or "document:report_2026_042.pdf" result: Optional outcome or result of the action. Example: "Delivered" or "Rejected by client" or "Pending review"

Returns: JSON string with keys: status, action_id, mandate_id, action, chain_hash. On error (invalid mandate): {"error": "..."}; when a mandate above a sub-mandate breaks the chain, also "reason" (parent_mandate_revoked, parent_mandate_expired, parent_mandate_, parent_mandate_not_found or delegation_chain_too_deep) and "refused_by" (the mandate that broke it, or null).

NameRequiredDescriptionDefault
actionYes
resultNo
evidenceNo
mandate_idYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations supplied, the description carries the full burden and does substantial work: it discloses that entries are chained via SHA-256 into a tamper-evident trail, that delegation-chain status is enforced, and it enumerates error reasons including parent_mandate_revoked and delegation_chain_too_deep. It stops short of stating idempotency, append-only/immutability guarantees for existing entries, or any rate/throughput limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose, then structured Args/Returns sections, making it easy to scan. It is somewhat verbose — the parent-mandate explanation is restated in both prose and the Returns section — but each block carries distinct information rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still adds value by naming the returned keys and, more importantly, documenting error shapes ('reason', 'refused_by') that a schema would not enumerate as enumerations. For a security-sensitive, mutation-style audit tool, this covers what an agent needs to call it correctly and interpret failures.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it does: every one of the four parameters is described with its purpose, optionality ('Optional supporting evidence'), and a concrete example value. Evidence format hints ('email:msg_abc123', 'document:report_2026_042.pdf') add meaning the bare string-typed schema cannot convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence pairs a specific verb ('Log') with a specific resource ('an action performed by an AI agent under an active mandate') and states the domain (audit and compliance). It is immediately distinguishable from siblings like verify_mandate or issue_action_token, which do not create audit entries.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear preconditions for use: the mandate must be active, not revoked, and not expired, and for sub-mandates every ancestor mandate must satisfy the same. It does not explicitly route to alternatives (e.g., when to contest_action instead, or when an action token is required first), so it stops short of full when/when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

prepare_contest_signatureA

Return the exact bytes a principal must sign to contest a logged action.

Nothing is written. The principal signs payload (UTF-8 bytes; payload_b64url is the same bytes) with its Ed25519 key, then calls contest_action with the same arguments plus principal_signature and this signed_at.

Args: mandate_id: UUID of the mandate the action was logged under. action_id: action_id returned by log_action. reason: Why the act is contested, as it will be recorded (1 to 500 characters). response_days: Days until respond_by (1 to 365), chosen by the operator. contested_by: The contesting principal. Default: the mandate's principal. signed_at: ISO 8601 timestamp with timezone. Default: now (UTC).

Returns: JSON string with keys: payload (str), payload_b64url, signed_at, contested_by. On error: {"error": "...", "code": "..."}.

NameRequiredDescriptionDefault
reasonYes
action_idYes
signed_atNo
mandate_idYes
contested_byNo
response_daysYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations supplied, the description carries the full burden and does well: 'Nothing is written' states the side-effect profile, defaults are given for contested_by and signed_at, and the error shape is disclosed. It omits who is authorized to invoke it and whether the returned bytes are deterministic for a given signed_at, which matters for a signing-prep helper.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The mutation-free guarantee and the sign-then-contest workflow are front-loaded, followed by a compact Args block and a Returns block. Every line adds information an agent needs; there is no restatement of the tool name or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a six-parameter signing-preparation tool this is complete: purpose, workflow, side effects, all parameter semantics with bounds and defaults, and return keys. The Returns section partially duplicates the existing output schema, but the added error shape justifies it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate and it does: every one of the six parameters is explained, including UUID typing for mandate_id and action_id, a 1 to 500 character bound on reason, a 1 to 365 day bound on response_days, the default principal for contested_by, and the ISO 8601 with timezone requirement for signed_at.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence names a specific verb and resource — returning the exact bytes to sign for contesting a logged action — and immediately distinguishes this from the sibling contest_action, which is the tool that actually performs the contest. An agent can tell the two apart without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description lays out the full call sequence: prepare here, sign the payload with an Ed25519 key, then call contest_action with the same arguments plus principal_signature and the returned signed_at. That is explicit routing guidance rather than inference. It stops short of stating any exclusion or precondition for when this step is unnecessary.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

prepare_mandate_pdfA

Return the mandate as a PDF for the principal to sign with its own signature tool.

Takes the same arguments as create_mandate (and prepare_mandate_signature). Nothing is written. The PDF shows the mandate and carries, as the attached file amr-mandate.json, the exact bytes prepare_mandate_signature returns for the same arguments and signed_at; the SHA-256 of these bytes is printed on every page. The same arguments and signed_at always give the same PDF.

The registry does not sign this PDF and does not check a signature made on it in this version: the principal signs it with its own tool (for example a PAdES signature), and keeps the signed file. An agent cannot sign in the principal's place. create_mandate accepts signed_at only within AMR_PRINCIPAL_SIGNATURE_MAX_AGE_SECONDS (default 900 s) of its clock: generate, sign and register within that window, or generate a new PDF. The PDF matches a registered mandate only if that mandate was created with the same arguments, the same signed_at and a principal signature.

Args: signed_at: ISO 8601 timestamp with timezone to place in the content. Default: now (UTC). Pass the same value to create_mandate. (all other arguments: see create_mandate)

Returns: JSON string with keys: pdf_base64 (standard base64 of the PDF), filename, pdf_sha256, attachment_name ("amr-mandate.json"), payload_sha256 (the value printed in the PDF), payload, payload_b64url, signed_at. On error: the same error as prepare_mandate_signature, or {"error": "...", "code": "mandate_pdf_too_large"} (content over 256 KiB or more than 200 pages).

NameRequiredDescriptionDefault
scopeYes
agent_idYes
currencyNoEUR
metadataNo
signed_atNo
max_amountNo
period_daysNo
principal_idYes
risk_classesNo
amount_actionsNo
duration_hoursYes
allowed_actionsNo
forbidden_actionsNo
parent_mandate_idNo
period_max_amountNo
delegation_allowedNo
legal_basis_articleYes
max_delegation_depthNo
legal_basis_regulationYes
legal_basis_jurisdictionNoEU

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations supplied, the description carries the full behavioral burden and does: it discloses the embedded attachment (amr-mandate.json), the per-page SHA-256, deterministic output for identical arguments and signed_at, that the registry neither signs nor verifies signatures on this PDF in this version, and that an agent cannot sign on the principal's behalf. It also documents error behavior, including the mandate_pdf_too_large code and its 256 KiB / 200 page thresholds.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded purpose sentence, then a structured Args/Returns/error layout, with no filler. It is dense and the time-window discussion spans several sentences that could be tightened, but each carries a distinct constraint an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex, 20-parameter, non-mutating generation tool the description covers the workflow, determinism guarantees, cross-tool coupling, and failure modes well. The remaining gap is that it explains only one of twenty parameters, relying on the sibling create_mandate definition for the rest.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% across 20 parameters, so the description must compensate. It gives real meaning only for signed_at (ISO 8601, timezone required, default now UTC, must be reused in create_mandate); the other 19 parameters are delegated wholesale to "see create_mandate," leaving constraint-bearing fields like max_amount, period_days and legal_basis_* undocumented here.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Return the mandate as a PDF") plus the intended consumer ("for the principal to sign with its own signature tool"), which immediately separates it from create_mandate and prepare_mandate_signature.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says it takes the same arguments as create_mandate / prepare_mandate_signature and that "Nothing is written," then gives the operative ordering constraint: generate, sign and register within AMR_PRINCIPAL_SIGNATURE_MAX_AGE_SECONDS (default 900 s) "or generate a new PDF." It also defines what must match for a PDF to correspond to a registered mandate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

prepare_mandate_signatureA

Return the exact bytes a principal must sign to grant this mandate.

Takes the same arguments as create_mandate. Nothing is written. The principal signs payload (UTF-8 bytes; payload_b64url is the same bytes) with its Ed25519 key, then calls create_mandate with the same arguments plus principal_signature and this signed_at. Any difference in the arguments makes the signature invalid.

Args: signed_at: ISO 8601 timestamp with timezone to place in the payload. Default: now (UTC). Example: "2026-10-18T20:30:00+00:00" (all other arguments: see create_mandate)

Returns: JSON string with keys: payload (str), payload_b64url, signed_at. On error: {"error": "...", "code": "..."}.

NameRequiredDescriptionDefault
scopeYes
agent_idYes
currencyNoEUR
metadataNo
signed_atNo
max_amountNo
period_daysNo
principal_idYes
risk_classesNo
amount_actionsNo
duration_hoursYes
allowed_actionsNo
forbidden_actionsNo
parent_mandate_idNo
period_max_amountNo
delegation_allowedNo
legal_basis_articleYes
max_delegation_depthNo
legal_basis_regulationYes
legal_basis_jurisdictionNoEU

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it declares 'Nothing is written' (critical for a name that sounds like a mutation), states the strict invariant that any argument difference invalidates the signature, and documents the error shape. It omits prerequisites such as whether the principal key must first be registered via register_principal_key.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose in the first sentence, then workflow, then Args/Returns blocks. Efficient for a complex tool, with only minor redundancy in the parenthetical '(UTF-8 bytes; payload_b64url is the same bytes)'.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still summarizes return keys and the error object, which is helpful but not required. For a 20-parameter cryptographic preparation tool the essential contract (identical-arguments invariant, signed_at format, no side effects) is covered; the main gap is the undisclosed precondition that the principal's signing key must already be registered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% across 20 parameters, so the description must compensate. It documents signed_at thoroughly (ISO 8601 with timezone, default now UTC, worked example) and adds the crucial semantic that all arguments are signature-bound, but it punts the remaining ~19 parameters to 'see create_mandate', leaving their meaning outside this definition.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Return the exact bytes a principal must sign to grant this mandate.' The second sentence ('Takes the same arguments as create_mandate. Nothing is written.') immediately distinguishes it from the sibling create_mandate, which an agent might otherwise confuse it with.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a clear operating context: call this, have the principal sign payload with its Ed25519 key, then call create_mandate with identical arguments plus principal_signature and this signed_at. It does not name the other preparation siblings (prepare_mandate_pdf, prepare_revocation_signature, prepare_contest_signature) or state exclusions, so it stops short of explicit when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

prepare_revocation_signatureA

Return the exact bytes a principal must sign to revoke one of its mandates.

Nothing is written. The principal signs payload (UTF-8 bytes; payload_b64url is the same bytes) with its Ed25519 key, then calls revoke_mandate with the same mandate_id and reason plus principal_signature and this signed_at.

Args: mandate_id: UUID of the mandate to revoke. reason: Why it is revoked, as it will be recorded (1 to 500 characters). signed_at: ISO 8601 timestamp with timezone. Default: now (UTC).

Returns: JSON string with keys: payload (str), payload_b64url, signed_at, principal_id. On error: {"error": "...", "code": "..."}.

NameRequiredDescriptionDefault
reasonYes
signed_atNo
mandate_idYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations present, the description carries the full behavioral burden and does so: "Nothing is written" declares the absence of side effects, and it discloses that the caller must hold the principal's Ed25519 signing key. It also notes signed_at defaults to now in UTC and that the same timestamp must be carried forward to revoke_mandate, which is exactly the coordination detail an agent would otherwise miss.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose, then Args and Returns blocks that are easy to scan, and the clarification that payload_b64url is the same bytes as payload prevents an encoding mistake. The Returns key list partially duplicates the output schema, which is the only mildly redundant element.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite zero annotation coverage and zero schema description coverage, the description covers purpose, side-effect status, signing requirements, sequencing into revoke_mandate, all parameter constraints, and the error shape. For a three-parameter cryptographic preparation tool, nothing an agent needs in order to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate and it does for all three parameters: mandate_id is a mandate UUID, reason is what will be recorded and must be 1-500 characters, and signed_at is an ISO 8601 timestamp with timezone defaulting to now UTC. These constraints exist nowhere in the schema and directly prevent invalid calls.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: returning the exact bytes a principal must sign to revoke one of its mandates. It is clearly distinguished from the sibling signing helpers prepare_mandate_signature and prepare_contest_signature by naming the revoke workflow and the downstream revoke_mandate call.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit two-step workflow: call this to get the payload, sign it with the Ed25519 key, then call revoke_mandate with the same mandate_id and reason plus principal_signature and this signed_at. The alternative is named and the condition that selects it is spelled out, leaving nothing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

register_principal_keyA

Register the Ed25519 public key of a principal, once.

After registration, create_mandate refuses any mandate from this principal that is not signed with the matching private key. The key cannot be replaced or removed in this version. For an ordinary principal_id the registry does not check who registers it: the first registration wins. Expose this tool to principals (or their back office) only, never to the agents they govern, and register each key at onboarding.

did:web principals (principal_id "did:web:"): the key is accepted only if the domain publishes it in https:///.well-known/did.json (W3C did:web). This needs AMR_DID_WEB_RESOLVE=1 in the server's process environment (one HTTPS request per registration; refused otherwise). The SHA-256 of the document read and the verification time are sealed with the key and shown by get_proof. It proves control of the domain at registration time, not a qualified signature.

Proof of possession: proof_signature is the principal's signature of the canonical JSON {"context": "amr.principal_key.v1", "principal_id": ..., "public_key": ...} (sorted keys, no spaces, UTF-8; public_key base64url without padding).

Args: principal_id: The principal the key belongs to. Example: "user:audric" public_key: Raw 32-byte Ed25519 public key, base64url. Example: "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" proof_signature: Base64url Ed25519 signature of the registration payload.

Returns: JSON string with keys: status ("registered"), principal_id, key_id, registered_at, chain_hash, domain_proof ({did_document_url, did_document_sha256, verified_at} for a did:web principal, else null). On error: {"error": "...", "code": "..."} with code principal_id_invalid, public_key_invalid, principal_signature_invalid, principal_key_proof_invalid, principal_key_already_registered, principal_id_lookalike (a scheme prefix such as "did:web:" written with non-ASCII look-alike characters), or for did:web: did_web_resolution_disabled, did_web_invalid, did_web_not_canonical (then canonical_principal_id), did_web_unreachable, did_web_address_refused, did_web_https_required, did_web_redirect, did_web_document_invalid or did_web_key_not_published.

NameRequiredDescriptionDefault
public_keyYes
principal_idYes
proof_signatureYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does most of it: keys are immutable ("cannot be replaced or removed"), first registration wins with no registrar authentication check, did:web triggers one HTTPS fetch and fails otherwise, and the domain proof is scoped to control-at-registration only, not a qualified signature. Minor gap: no disclosure of rate limits or idempotency behavior on retry beyond the already_registered error code.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and constraint, then progressively deeper detail; every paragraph maps to a distinct decision an agent must make. The long enumerated error-code list and return-field inventory run somewhat against conciseness given an output schema already exists.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

A high-stakes, irreversible, security-sensitive write tool with three undocumented schema params — the description covers the authorization model, immutability, did:web resolution requirements, proof-of-possession payload, and failure modes. Nothing an agent needs in order to call it safely is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and the schema has bare titles, so the description does all the work: each parameter is documented with encoding, byte length, and a concrete example, and the exact canonical JSON payload for proof_signature is spelled out (sorted keys, no spaces, UTF-8, unpadded base64url). This is meaning the agent cannot get from the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource+cardinality: "Register the Ed25519 public key of a principal, once." It also states the downstream effect on create_mandate, which separates it cleanly from every sibling in the registry family.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit audience restrictions ("principals or their back office only, never the agents they govern") and a timing rule ("register each key at onboarding"), plus the conditional path for did:web principals and the AMR_DID_WEB_RESOLVE prerequisite. When-not guidance is explicit rather than inferred.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

report_issueA

Report a problem with AMR itself (a bug, a confusing error, a missing feature).

Write only what you observed: the tool name, what you sent, what came back. Never paste real mandate contents, personal data, keys or tokens: nothing from the registry is attached; PEM blocks, JWS-shaped tokens, GitHub tokens and bare Ed25519 private keys are refused, other secrets are not detected.

Without AMR_ISSUE_TOKEN in the server's environment, no network call is made: the result holds new_issue_url, a pre-filled link to open. With it, the issue is created on the AMR repository and its link is returned.

Args: title: Short summary, 5 to 120 characters. Example: "issue_action_token refuses an allowed action" description: What happened and what you expected, 10 to 4000 characters. version: AMR version you run. Example: "0.1.0" context: Optional, up to 1000 characters: MCP client, OS, Python version.

Returns: JSON string with key status: "not_sent" (no token): new_issue_url, issues_url, message; "created": issue_number, issue_url. On error: {"error": "...", "code": "..."} with code issue_report_invalid, issue_contains_secret or issue_not_created (then new_issue_url as a fallback).

NameRequiredDescriptionDefault
titleYes
contextNo
versionYes
descriptionYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are supplied, so the description carries the full disclosure burden and does so unusually well: it states that without AMR_ISSUE_TOKEN no network call occurs and a pre-filled URL is returned instead, and that with the token a real issue is filed publicly. It also discloses the secret-scanning behavior precisely, including its limit (PEM blocks, JWS-shaped tokens, GitHub tokens and bare Ed25519 keys are refused; other secrets are not detected), which is exactly the failure mode an agent needs to know before retrying.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then safety constraint, then environment-dependent behavior, then a compact Args/Returns block, so an agent reading only the first two lines already knows what to do. The Returns block partially restates the output schema (status, error codes), which is mild redundancy, but the code semantics (issue_contains_secret, issue_not_created fallback) justify most of it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a four-parameter mutation tool with no annotations, the definition covers both execution paths (local URL vs. remote creation), the secret-refusal failure mode, and the shape of every success and error outcome. Nothing an agent needs in order to call it correctly, or to explain the result to a user, is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate and it does: each of the four parameters gets length bounds (5-120, 10-4000, up to 1000), a stated purpose, and a realistic example for title and version. It also marks context as optional, which the bare schema only implies via the null default.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: report a problem with AMR itself, and enumerates the problem classes (bug, confusing error, missing feature). Every sibling tool operates on mandates, registry keys, or actions, so an agent can immediately tell this is the meta/feedback channel rather than a mandate operation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage context is clear: use this for defects in AMR tooling, and the write-only-what-you-observed instruction scopes what belongs in a report. It never names an explicit alternative or exclusion (e.g. that mandate disputes belong to contest_action), so routing is inferred from the phrase 'AMR itself' rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

revoke_mandateA

Revoke a mandate now. Final: a revoked mandate cannot be reactivated.

Only the mandate's own principal revokes it (for a sub-mandate, that is the agent that delegated it). If the principal has a registered key, the revocation must carry its signature: build the bytes with prepare_revocation_signature, sign them, and pass principal_signature and signed_at. With AMR_REQUIRE_PRINCIPAL_SIGNATURE, every revocation must be signed. Without a key, anyone who can call this tool can revoke: expose it to principals (or their back office), like create_mandate.

Effect, from the moment this returns: no new token for the mandate or any sub-mandate below it, verify_mandate no longer returns it, and no sub-mandate can be created under it. Tokens and mandate attestations already issued cannot be called back: they stay valid until their exp, given in outstanding_tokens and outstanding_attestations (the mandate and its sub-mandates).

Args: mandate_id: UUID of the mandate to revoke. reason: Why it is revoked, recorded in the registry and the Proof Pack (1 to 500 characters). Example: "agent exceeded its order limit" principal_signature: Base64url Ed25519 signature of the revocation payload. signed_at: The signed_at placed in the payload (ISO 8601 with timezone).

Returns: JSON string with keys: status ("revoked"), mandate_id, principal_id, reason, revoked_at, principal_signature ({key_id, signed_at} or null), chain_hash, outstanding_tokens ({still_valid, last_expires_at}), outstanding_attestations ({still_valid, last_expires_at}). On error: {"error": "...", "code": "..."} with code mandate_not_found, mandate_already_revoked, invalid_input or a principal signature code.

NameRequiredDescriptionDefault
reasonYes
signed_atNo
mandate_idYes
principal_signatureNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations supplied, the description carries the full burden and does so: it declares irreversibility, the authorization model, the requirement to build/sign a payload via prepare_revocation_signature, and precisely what the revocation blocks (new tokens, verify_mandate visibility, new sub-mandates). It also discloses the critical limitation that already-issued tokens and attestations stay valid until exp, surfaced via outstanding_tokens and outstanding_attestations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and its finality, then authorization, then effect, then Args/Returns. Every sentence carries operational weight. It is long, and the Returns block partially restates the output schema, though the enumerated error codes add value the schema likely does not.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a four-parameter, irreversible, signature-aware mutation, the definition covers authorization, signing prerequisites, environment overrides, timing of effect, non-revocable residuals, and error codes. An agent has everything needed to call it correctly without consulting a sibling or guessing at preconditions.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it does for all four parameters: mandate_id as a UUID, reason with a 1-500 character bound and a worked example, principal_signature as a Base64url Ed25519 signature of the revocation payload, and signed_at as the ISO 8601 timestamp placed in that payload. The relationship between signed_at and the signed bytes is the kind of meaning the schema alone could not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Revoke a mandate') and immediately qualifies the scope with 'Final: a revoked mandate cannot be reactivated.' It is unambiguously distinct from siblings like create_mandate and prepare_revocation_signature, which is named as the prerequisite step rather than a substitute.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names who may call it ('only the mandate's own principal revokes it, for a sub-mandate that is the agent that delegated it'), the conditional path when a registered key exists, the AMR_REQUIRE_PRINCIPAL_SIGNATURE environment override, and the fallback exposure model 'like create_mandate'. When-to-use, prerequisite, and alternative paths are all covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

verify_mandateA

Verify whether an AI agent has a currently active and valid mandate to perform an action.

Looks up the most recent active, non-expired, non-revoked mandate for the given agent. For a sub-mandate, every mandate above it must also be active, unrevoked and unexpired. Does NOT check the action against the scope: it returns the free-text scope and, when the mandate has one, its structured perimeter. To enforce the perimeter, request a token with issue_action_token, which refuses out-of-scope actions.

Args: agent_id: Unique identifier of the agent to check. Example: "agent:korvex-advisor-v1" action: Description of the action the agent wants to perform. Example: "send investment recommendation" or "access user portfolio" context: Optional additional context for logging or audit purposes. Example: "client session cs-2026-0042"

Returns: JSON string with keys: authorized (bool), mandate_id (str|null), reason (str), scope (str), perimeter (dict|null), parent_mandate_id (str|null), organ_decision (dict|null: the body, title, date, reference and authorized personnel of the decision that authorized the agent, when the mandate records one), expires_at (str|null). authorized=True means an active mandate exists; the caller should verify that the action falls within the returned scope.

NameRequiredDescriptionDefault
actionYes
contextNo
agent_idYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does disclose meaningful traits: it looks up the most recent active/non-expired/non-revoked mandate, and requires every ancestor mandate of a sub-mandate to be active and unrevoked. It also flags the critical semantic that authorized=True only means a mandate exists, not that the action is in scope. It does not cover side effects (e.g., whether the lookup is logged, given the context param's audit purpose) or failure modes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose and the key caveat before the Args/Returns blocks, and the scope-vs-perimeter warning sits where it matters. The examples under each arg are useful but slightly pad the length for such simple string parameters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complex verification semantics (mandate chain validity, scope versus structured perimeter, organ decision provenance) are covered well for a tool with no annotations. The Returns block partially duplicates the existing output schema, but it adds the crucial interpretation that authorized=True means 'mandate exists, verify scope yourself', which an agent needs to act correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate, and it largely does: all three parameters get meaning plus concrete examples ('agent:korvex-advisor-v1', 'send investment recommendation'). The one gap is that it never says how 'action' influences the result, given the tool explicitly does not scope-check it — an agent may reasonably wonder whether action is used at all.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Verify whether an AI agent has a currently active and valid mandate') and immediately bounds the operation by noting it does NOT check the action against scope. This distinguishes it from the enforcement sibling issue_action_token, so an agent can choose between them without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes enforcement work to issue_action_token ('which refuses out-of-scope actions') and explains that the caller must verify scope itself after authorized=True. It does not differentiate from the other closely-named sibling check_action_token, which leaves one real routing ambiguity for an agent choosing among mandate-verification tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 18 tool updatesv0.1.1
    • First observedcheck_action_token
    • First observedcontest_action
    • First observedcreate_mandate
    • First observedexport_insurer_dossier
    • First observedget_proof
    • First observedget_registry_public_key
    • First observedissue_action_token
    • First observedissue_mandate_attestation
    • First observedlist_action_contests
    • First observedlog_action
    • First observedprepare_contest_signature
    • First observedprepare_mandate_pdf
    • First observedprepare_mandate_signature
    • First observedprepare_revocation_signature
    • First observedregister_principal_key
    • First observedreport_issue
    • First observedrevoke_mandate
    • First observedverify_mandate

TDQS

A4.3/5.0

Scored across 18 tools

Disambiguation4/5

Each tool targets a distinct lifecycle step (create, verify, revoke, log, token, attestation, proof, contest), and the lengthy descriptions explicitly cross-reference siblings to prevent misselection. A few pairs could still be confused at a glance: verify_mandate vs check_action_token vs issue_mandate_attestation, and prepare_mandate_signature vs prepare_mandate_pdf, which differ only by output artifact.

Naming Consistency5/5

Every name is snake_case verb_noun (create_mandate, revoke_mandate, issue_action_token, prepare_contest_signature, list_action_contests, report_issue), with the resource always named explicitly. The prepare_* family and the get_/list_ read tools follow a single, predictable convention throughout.

Tool Count4/5

18 tools is at the upper edge but each maps to a real capability (mandate lifecycle, three prepare-for-signature helpers, token issue/check, attestation, dossier export, contest reminder, issue reporting). The count is justified by the domain's depth, though the prepare_* triples and separate PDF helper add surface an agent must learn.

Completeness4/5

The surface covers the full lifecycle: create, verify, revoke, action logging, token issuance/verification, offline attestation, proof packs, contests, and key registration, with immutable mandates by design. Gaps are minor: there is no tool to list or search mandates by principal/agent, and no direct read of a single mandate short of generating a full proof pack or dossier.

Maintenance

ActivityMaintained
ResponsivenessLimited data

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    F
    maintenance
    Enables AI agents to execute prediction-market trades through a risk-control gateway that enforces signed mandates and generates proof trails for accountability.
    0
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides permission gates and tamper-evident audit logging for AI agent tool executions, with declarative policies, consent ladders, and hash-chained verification.
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    Enables AI agents to execute actions under an accountability layer with identity passports, mandates, a permission gate, and a tamper-evident journal, while requiring human confirmation for irreversible operations.
    7
    76 PyPI
    5
    AGPL 3.0