Skip to main content
Glama
CTRLRun

CTRLRun Approval Console

Official
pip install ctrlrun && ctrlrun demo

What it does

The model guesses. CTRLRun does not. The ticket says refund €500. The agent asks for €5,000, one extra zero. The tool is in its list, the arguments are well formed, and the model is completely confident. Nothing above the call disagrees, because nothing above the call is a check: a tool being callable is not permission to call it with those arguments.

Without CTRLRun

With CTRLRun

Nothing checks the amount. The call goes through. €4,500 too much.

Your rule checks the amount. The call never leaves. €0 wrongly paid.

CTRLRun is that check. It reads the arguments about to leave your process and answers what may happen to them: let it run, ask a human, or stop it cold. Four rules do the work, and each one is a test in this repository before it is a sentence here.

Exact means exact

Changed arguments need a new approval.

Once stays once

Same effect key, shared store, no repeat.

Unknown means wait

Confirm the outcome before retrying.

Every answer is kept

Requests, decisions and results, refusals included.

The third one is the half people forget. A correct €500 refund commits at the provider and the reply is lost coming back, so the agent retries. Retry libraries, agent frameworks and tool loops collapse this failed into I do not know what happened. CTRLRun keeps them apart: a lost reply is AMBIGUOUS, never FAILED, and a retry against an AMBIGUOUS effect is refused until a human, or a reconcile hook, says what happened.

$ ctrlrun demo
CTRLRun demo — five ways an agent action goes wrong, and what stops it.
Policy: refunds up to €1,000 are autonomous, up to €10,000 need a human, above that are denied.

1. Duplicate effect after a lost response

   refund €500  →  remote commits  →  response lost  →  effect: AMBIGUOUS
   agent retries the same refund
   ✗ BLOCKED — effect may already have committed; blind retry refused
   remote refund calls: 1
   only a human moves it on:  ctrlrun resolve refund:txn_1 --committed|--failed

2. Approval mutation

   agent proposes refund €2,000  →  human approves apr_0aa78e0380ba55d77a601dc782f57095 (bound to the action hash)
   agent executes refund €5,000  →
   ✗ BLOCKED — approved action ≠ requested action (mismatch)

3. Concurrent agents, same effect

   Agent A  reserve refund:txn_123  →  ACQUIRED  →  executes
   Agent B  reserve refund:txn_123  →
   ✗ BLOCKED — already reserved (in_progress)

4. Approval replay

   approval apr_dbc8bc6f06690cdf2e2c55a4e591ef3b used once  →  consumed
   same approval presented again                            →
   ✗ BLOCKED — single-use approval already consumed

5. Authority escalation

   human €100,000 delegable  →  finance agent €25,000  →  support agent €2,000
   support agent's grant: dlg_5f8d41938a3f29972d5489d676cd9edb
   support agent requests €50,000  →
   ✗ BLOCKED — outside the delegated grant (authority_constraint)
   remote refund calls: 0
   finance agent tries to delegate €50,000 under its own €25,000  →  refused (containment: constraints)
   support agent requests €1,500  →  authority permits it, and the policy asks a human (apr_f86eca24dd80206ab5189ccb1b62aa55)
   two axes, and an action needs both: the stricter of the pair wins

Receipts (8): .ctrlrun/demo/receipts.jsonl
Events:       .ctrlrun/demo/events.jsonl

Read them:    CTRLRUN_STATE=.ctrlrun/demo/state.db ctrlrun receipts

Approval and delegation ids are generated per run; everything else is exactly what the demo prints, and a test fails if the two drift apart. No network, no external service, under a second. pip install ctrlrun && ctrlrun demo runs it locally in about the same time.

Where it stops. It does not detect prompt injection: it contains the consequence rather than reading the cause. It cannot promise exactly-once against a remote it does not control, it refuses to knowingly act twice, and it rolls nothing back. Receipts are chained, so an alteration is detected; a truncation at the end and a forged append are not, because the head that would catch them is a row in the same database, and closing that is what ctrlrun anchor is for. They are not signed: alteration is not authorship. The badge above means the declared guarantees pass in the setup they ran against, and it does not mean secure, safe, compliant, certified or audited: what the badge means · OWASP-AGENTIC-TOP10.md names the four entries this does not address.

If an agent only reads and answers, you do not need CTRLRun. The moment it can send, pay, refund, delete, deploy, grant, revoke, approve, submit, purchase or cancel, you do.

Related MCP server: legitify-mcp

Use it in three steps

The animation above is this section, recorded against the real library: one policy file, two short programs, four commands, nothing staged.

1. Install it.

pip install ctrlrun

2. Write down what the agent may do. One file, ctrlrun.yaml. Amounts are integer minor units, so 50000 is €500. Both ends of every band are bound, because an upper bound alone lets a negative amount through, and a refund of a negative amount is a charge. Anything not listed is denied; there is no default-allow.

schema: ctrlrun.policy/v2

actions:
  stripe.refund:
    effect: "refund:{payment_id}"
    rules:
      - when: { amount_gte: 0, amount_lte: 50000 }
        decision: allow      # up to €500: the agent acts alone
      - when: { amount_gte: 0, amount_lte: 1000000 }
        decision: approve    # up to €10,000: a human decides
      - decision: deny       # above that: never

3. Wrap the call that has the consequence. The decorator names the action, the effect key names the consequence it has in the world, and the context names who is acting. stripe here is a stand-in that records calls instead of making them.

import sys

import ctrlrun


class FakeStripe:
    """Stands in for the provider: it records calls instead of making them."""

    def __init__(self) -> None:
        self.calls: list[tuple[str, int]] = []

    def refund(self, payment_id: str, amount: int) -> dict:
        self.calls.append((payment_id, amount))
        return {"id": f"re_{payment_id}", "amount": amount, "status": "succeeded"}


stripe = FakeStripe()


@ctrlrun.protect("stripe.refund", effect="refund:{payment_id}")
def refund(payment_id: str, amount: int) -> dict:
    return stripe.refund(payment_id, amount)


if __name__ == "__main__":
    with ctrlrun.context(agent="support-agent"):
        print("€500   ->", refund(payment_id="txn_1", amount=50_000)["status"])
        try:
            refund(payment_id="txn_2", amount=500_000)
        except ctrlrun.ApprovalRequired as pending:
            print("€5,000 -> a human decides:", pending.request_id)
            with open("request_id.txt", "w") as handle:
                handle.write(pending.request_id)
        else:
            sys.exit("the €5,000 refund ran without a human; the policy is not in force")
    print("calls that reached the provider:", len(stripe.calls))

The €500 refund runs on its own. The €5,000 one stops and names the request a human answers:

€500   -> succeeded
€5,000 -> a human decides: apr_63e80076f2cccfee52b17491a4b2e125
calls that reached the provider: 1

A human answers from the shell. The grant names the hash of the exact action the human read, and when it lapses. Ids, hashes and dates are generated per run; yours differ.

ctrlrun approve "$(cat request_id.txt)"
granted apr_63e80076f2cccfee52b17491a4b2e125 for sha256:22ec1c398e4b93d080b6cba61e5e11b0e21879552ac5dbf63c192d2b2e6af752
expires 2026-09-13T20:10:11.367Z

The agent presents it, then tries to spend it on something else. The first call is exactly what the human approved, and it runs. The second is the same approval with one digit changed, and it matches nothing:

import sys

import ctrlrun

from agent import refund, stripe

with open("request_id.txt") as handle:
    request_id = handle.read().strip()

with ctrlrun.context(agent="support-agent"), ctrlrun.with_approval(request_id):
    # Exactly what the human read: €5,000 on txn_2.
    print("€5,000 with the approval ->", refund(payment_id="txn_2", amount=500_000)["status"])

    # The same approval, one digit changed.
    try:
        refund(payment_id="txn_2", amount=900_000)
    except ctrlrun.ApprovalMismatch:
        print("€9,000 on that same approval -> refused")
    else:
        sys.exit("a mutated action ran on a human's approval; that is the bug this exists to stop")

print("calls that reached the provider:", len(stripe.calls), "(the €9,000 never left)")
€5,000 with the approval -> succeeded
€9,000 on that same approval -> refused
calls that reached the provider: 1 (the €9,000 never left)

Every attempt, refusals included, left a receipt, and ctrlrun receipts lists them. That is the whole integration: a policy file, a decorator, a context, and with_approval to present a grant. Money is the example, not the scope. A condition is <argument>_<op>, so the same policy language reads role_in: [reader, viewer] or replicas_lte: 10 as easily as amount_lte, and nine domains below have one policy each. Protect your first action walks the same path with every output explained · Policy YAML reference · Cookbook: refunds, deploys, IAM, deletions, email, MCP.

Three ways to use it

You probably do not need an adapter. @protect covers anything running in this process: a raw model call, a LangChain tool, a hand-rolled loop, a cron job. The gateway covers anything that reaches its tools over MCP, in any language.

You have

Use

Needs

Python in this process

the @protect decorator, shown above

nothing beyond pip install ctrlrun

Tools behind an MCP server, in any language

the gateway: pip install "ctrlrun[gateway]"

one command, no change to agent or server code

A framework with its own approval interrupt

an adapter

the framework to have a human-in-the-loop primitive

It works with agents you can and can't modify. WhatsApp, Slack and Teams bots, ChatGPT, Cursor, Codex, OpenAI Agents: any AI agent you have. CTRLRun checks the action, not the agent, so if the agent acts through a tool server or an API you run, the action is checked, and the agent is not rebuilt, redeployed or told. Agents you can't modify says where the boundary goes for each kind.

An adapter exists for one reason: to route an approve decision through the framework's own interrupt, so a human answers where they already answer. There is never a second place to say yes. ctrlrun-langgraph gives prevention, because the resumption carries the arguments and core re-checks them against the hash. ctrlrun-openai-agents gives attribution, because that SDK records that a call was approved and not what its arguments were. None of the three is only for agents: a worker, a webhook handler and a scheduled job cannot tell a first attempt from a retry either.

How it works

Every protected call, whichever way it arrives, goes through the same seven steps. Only then does it reach your systems.

  normalize  →  decide  →  approve  →  reserve  →  execute  →  resolve  →  record
  1. Normalize: one action, one id. The call becomes an Action: a name, canonical arguments (sorted keys, no floats), a resource, the principal. Its SHA-256 is the action hash.

  2. Decide: allow, ask or block. Authority first (may this principal propose this at all, and within what bounds?), then policy (how much autonomy does this action get?). Unknown action, missing policy or missing principal is deny. Silence is never permission.

  3. Approve: bound to this action. A human answers against the action hash. The approval is single-use, expires, and matches nothing but that exact action, so arguments changed after the answer void it and a person answers again. Name a preconditions= provider and the approval is also bound to the resource state it was granted against, rechecked strictly before the reservation: that narrows the window between the answer and the execution, from minutes of deliberation to milliseconds. It does not close it, because the recheck is a network call and cannot run inside the atomic write.

  4. Reserve: claimed once. The effect key, refund:txn_1 or namespace:prod-eu:checkout, is taken in one atomic write. A second caller, in another process or on another host, is refused.

  5. Execute: your code runs. Only NotExecuted, raised by you, means FAILED; every other exception and every timeout means AMBIGUOUS. Deciding which one you are looking at is the hard part, so ctrlrun.transport does it for you: urlopen, HTTPConnection and HTTPSConnection from stdlib urllib and http.client, which raise NotExecuted only where the connection they opened was handed no request byte. After one byte, every failure stays the exception it was, and the outcome is AMBIGUOUS. No setting widens that.

  6. Resolve: unknown is not failed. An AMBIGUOUS effect keeps its key and refuses a retry until ctrlrun resolve, or a reconcile hook that asked the remote, says what happened. Nothing runs twice on a guess.

  7. Record: a receipt either way. A portable JSON receipt: who, what, decision, approval, effect key, outcome, and the hash of the policy that decided it, chained to the receipt before it. Refusals get one too.

Who may ask, and how much. The policy decides the action and cannot see who is asking. Who may ask at all is a second axis, authority: every principal needs a grant, a delegation cannot widen one, and an action needs both axes, the stricter of the pair. Since 0.9 a grant can also carry a budget, a metric with a limit over a rolling window, consumed on reserve inside the same write, so a thousand refunds that each pass amount_lte cannot add up to more than the grant allows. An AMBIGUOUS effect holds its budget until it is resolved, because otherwise an agent that can manufacture ambiguity could manufacture authority. A budget bounds what the next reservation may do; it cannot recall an action already in flight. Authority has the whole model.

State lives in SQLite by default, a file with no server and no ops, and the reservation holds across processes rather than merely across threads. Point it at Postgres when more than one host writes: pip install "ctrlrun[postgres]", one URL, the same guarantees graded by the same suite. Prove it in your own setup with ctrlrun verify, which runs the kernel's own failure scenarios against your policy in a scratch store. It reaches no network: the only sockets it opens are to the store you named and to loopback listeners it bound itself, which is how it grades the transport classifier.

Guarantee

@protect

Gateway

Adapter

Approval binding — An approval is bound to the exact action; a mutated or replayed one is refused.

yes

yes

prevention or attribution, per adapter

One effect, once — One logical effect happens at most once, across threads, processes and hosts.

yes

yes

yes

Unknown is not failed — An unknown outcome is AMBIGUOUS, never FAILED, and blocks a blind retry.

yes

yes

yes

Fail closed — An unknown action, a missing policy or a missing principal is denied.

yes

yes

yes

Authority and delegation — Every principal needs a grant, delegation cannot widen one, and a grant bounds the total.

yes

yes

yes

Receipts — Every executed action leaves a portable JSON receipt of who, what and outcome.

yes

yes

yes

The same shape in nine domains

Nothing in CTRLRun knows what a refund is. An action is a name, canonical arguments, an effect key and a resource, and the three questions asked of it are the same whichever domain it came from: how much autonomy does this action get, did a human approve this exact action, and has this effect already happened. Two things carry your domain, and you write both.

  • The effect key is the only domain knowledge in the system. It is the string that says two calls are the same real-world consequence: refund:{payment_id}, namespace:{cluster}:{name}, grant:{user_id}:{role}, prescription:{patient_id}:{drug}. Name it well and a retry cannot act twice; leave it out and there is nothing for at most once to be about.

  • Conditions are arguments, not amounts. The language is <argument>_<op>, so the same operators read replicas_lte: 10, role_in: [reader, viewer] and to_domain_eq: acme.com as easily as amount_lte. A band is available to a domain that has never issued an invoice.

Domain

Autonomous

A human decides

Never

DevOps

k8s.scale_deployment to 10 replicas

terraform.apply

k8s.delete_namespace

Security operations

firewall.add_deny_rule

firewall.add_allow_rule

edr.disable_protection

Healthcare

appointment.reschedule

patient.export_record

prescription.change_dose

Legal

document.draft_internal

document.file_with_court

contract.execute

HR

pto.approve within a band

payroll.run

employee.delete_record

Insurance

claim.request_documents

claim.approve_payout above a band

policyholder.delete

E-commerce

inventory.adjust within a band

price.update

customer.delete

Public services

eligibility.precheck

benefit.terminate

record.delete

Payments

stripe.refund under €500

stripe.refund above it

stripe.delete_customer

Read any row left to right and it is one rule wearing different nouns. The security row is the one to read twice: adding a deny rule to a firewall is autonomous and adding an allow rule is not, which no amount threshold would have told you. The policy is where your judgement about your domain gets written down; CTRLRun is what makes it hold.

Documentation

docs.ctrlrun.dev is the documentation: concepts, guides, a cookbook, the full reference.

Start here

Why · Protect your first action

The ideas, and doing something with them

Concepts · Guides · Cookbook

Agents and MCP

Agents you can't modify · MCP overview · The gateway in five minutes

Running it for real

Production · Postgres · Recovery · Operations

Every key, flag and error

Reference · FAQ

Compared with

Idempotency keys · Framework human-in-the-loop · Guardrail libraries · Durable workflows · Governance toolkits

What holds, and what does not

Threat model · What verify proves · CLAIMS.md, every sentence mapped to its test · How this is built

Contributing

Issues and pull requests are welcome: CONTRIBUTING.md and CODE_OF_CONDUCT.md have the working agreement, and SECURITY.md is how to report a vulnerability. Every claim in this file has a test behind it, so a change to the prose usually means a change to the suite. CHANGELOG.md and https://docs.ctrlrun.dev/ROADMAP say where it is going. Releases carry PyPI provenance attestations from GitHub Actions.

License

Apache-2.0. The enforcement kernel is and will remain fully open source.

Available Tools

8 tools
approveA

WRITES. Grants one pending approval request, letting the agent run that exact action once. Requires an authenticated human; the answer is recorded under their name and is visible in the receipt the action leaves.

ParametersJSON Schema
NameRequiredDescriptionDefault
request_idYesThe pending request to grant, as returned by list_pending_approvals. The grant is bound to the hash of the action that request already names, so there is no argument here by which a different action could be approved.

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 behavioral burden. It discloses that the operation writes, requires an authenticated human, records the answer under the human's name, and leaves a visible receipt. This is strong context, though it does not cover edge cases like duplicate or invalid requests.

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 description is two focused sentences with no filler. The key action, one-time nature, and human-authorization requirement are all front-loaded and every sentence earns its place.

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 single-parameter tool with no output schema, the description covers what the tool does, its prerequisite, its audit trail, and where the result is visible. An agent has enough context to invoke it correctly and anticipate the side effects.

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 coverage is 100% and the request_id parameter is already well-described in the schema, including its binding to the pre-approved action. The description adds no parameter-specific meaning, so the baseline score of 3 is appropriate.

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 uses a specific verb and resource: it 'Grants one pending approval request' and explains the consequence (the agent may run that exact action once). This clearly distinguishes it from sibling tools like deny and list_pending_approvals.

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?

The description implies when to use the tool: when a pending approval request should be granted. It gives a prerequisite (authenticated human) but does not explicitly contrast with deny or other alternatives, so the usage guidance is mostly inferred rather than stated.

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

denyA

WRITES. Refuses one pending approval request. Requires an authenticated human; the answer is recorded under their name.

ParametersJSON Schema
NameRequiredDescriptionDefault
request_idYesThe pending request to refuse, as returned by list_pending_approvals.

TDQS

A3.9/5.0
Behavior3/5

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

No annotations exist, so the description itself must disclose behavior. It does flag 'WRITES' and states that the answer is recorded under the authenticated human's name, which is important context. However, it stops short of explaining the result or implications of the denial, such as finality or effect on the request state.

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?

Extremely concise: three short clauses that cover the write intent, the action, and a key precondition/audit requirement. Every sentence carries informational weight, with no filler.

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

Completeness3/5

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

With a single parameter and no output schema, a super-rich description is not expected, but the description does not explain what the tool returns or what happens if the request is invalid or already processed. For a mutation tool with zero annotations, this is a notable gap.

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?

The input schema has 100% coverage for the request_id parameter, as it already says 'The pending request to refuse, as returned by list_pending_approvals.' The description adds little beyond that, so the baseline 3 applies; the pointer is useful path does not introduce substantial new semantics.

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 clearly specifies a distinct action ('Refuses one pending approval request') with a specific verb and resource, and it naturally distinguishes from siblings like 'approve'. No ambiguity about what operation the tool performs.

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 contextual guidance: it applies to pending approval requests and notes a mandatory authenticated human. It does not explicitly state when to prefer this over 'approve' or 'resolve', but the opposite action is implied by the sibling naming.

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

effectsA

Read-only. The logical effects this store knows about, one row per effect key. Use this to find the effects whose outcome is unknown, which are the ones resolve can move on and the ones blocking a retry.

ParametersJSON Schema
NameRequiredDescriptionDefault
stateNoReturn only effects in this state, or omit it for every effect. 'reserved' and 'executing' are in flight, 'committed' and 'failed' are settled, and 'ambiguous' is the one that needs a human: the effect may or may not have happened, and a retry stays refused until resolve says which.

TDQS

A4/5.0
Behavior4/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. It openly declares 'Read-only' at the start, which is a key behavioral trait and sufficient for a simple query tool. It does not detail pagination or other edge behaviors, but for a read-only list tool this level is adequate.

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?

Two sentences with no filler. The first sentence front-loads the read-only nature and scope; the second gives a precise use case. Every word earns its place.

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 single-optional-param read tool, the description covers what it does and when to use it, and the schema covers the param semantics. With no output schema, an agent may not know the exact return fields beyond 'one row per effect key', but the simple nature of the tool makes this acceptable.

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 coverage is 100%, with the single 'state' parameter thoroughly described including enum meanings. The tool description itself adds little parameter-specific semantics beyond the schema, so the baseline score of 3 applies. It only hints at which states matter for the resolve/retry workflow.

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?

The description clearly states the resource (logical effects) and verb (list/read) with a specific granularity ('one row per effect key'). It avoids tautology and conveys a concrete purpose, though it does not explicitly differentiate from sibling tools like stats or inspect_action.

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 an explicit use case: 'Use this to find the effects whose outcome is unknown, which are the ones resolve can move on and the ones blocking a retry.' This tells the agent when to use it, but does not mention when not to use it or point to alternatives, so it stops short of a 5.

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

inspect_actionA

Read-only. One action's whole history: what was proposed, what the policy decided, which approval was involved, what happened to the effect, and the receipt. Use this when you know which action you care about; use receipts to browse, stats for totals.

ParametersJSON Schema
NameRequiredDescriptionDefault
action_idYesThe action to inspect. Action ids appear on receipts and on the pending requests list_pending_approvals returns.

TDQS

A4.4/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 behavioral disclosure burden. It leads with 'Read-only,' a clear safety signal, and details what the call returns in terms of history components. It does not mention failure modes or permissions, but for a simple read operation the key behavioral trait is disclosed.

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?

Two well-formed sentences with no wasted words. The most critical behavioral fact ('Read-only') is front-loaded, followed by the resource detail and then usage guidance. Every sentence earns its place.

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 single-parameter, read-only inspection tool with no output schema, the description provides enough detail about what will be returned and when to use it. It lacks error-handling notes, but the tool is simple enough that the description is complete for correct selection and invocation.

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 coverage is 100% and the schema already explains where action_ids appear (receipts and list_pending_approvals). The description adds only the context of when to use it, not additional parameter semantics, so baseline 3 is appropriate.

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?

Description clearly states a specific verb ('inspect') and resource ('one action's whole history') and enumerates exactly what is covered: proposal, policy decision, approval, effect outcome, and receipt. It also distinguishes itself from sibling tools by explicitly naming receipts and stats as alternatives for browsing and totals.

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 when-to-use rule: 'Use this when you know which action you care about' and contrasts it with 'use receipts to browse, stats for totals.' This is concrete and directly actionable, leaving little ambiguity about which tool to select.

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

list_pending_approvalsA

Read-only. The approval requests waiting for a human, oldest first, with the action, its arguments and when the request expires. Start here: the request ids approve and deny take come from this list.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many pending requests to return, 1 to 200, default 50. This bounds the response and not the scan, so a store holding many answered requests still walks them to find the pending ones.

TDQS

A4.5/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 of behavioral disclosure. It clearly states 'Read-only', describes what the list contains, and specifies ordering. It could add more about pagination or empty results, but for a simple read-only list it is transparent enough.

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 description is two sentences with no filler. It front-loads the read-only nature, states the core behavior, and closes with the workflow-relevant instruction. Every sentence earns its place.

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 list tool with one optional parameter and no output schema, the description is complete enough: it names what is returned, the ordering, the read-only guarantee, and how the result relates to sibling tools. An agent has everything needed to call it correctly.

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 100%, and the limit parameter already has a detailed description in the schema. The tool description adds no parameter-specific meaning beyond that, so the baseline 3 is appropriate.

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?

Description names a specific verb and resource: listing pending approval requests. It clarifies the scope ('waiting for a human'), ordering (oldest first), and the included contents, making it easy to distinguish from sibling tools like approve and deny.

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?

The instruction 'Start here' explicitly tells the agent to use this tool before approve and deny, and explains that the request IDs for those tools come from this list. This is direct workflow guidance that prevents misuse.

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

receiptsA

Read-only. The individual receipts this store holds, as portable JSON, oldest last, one for every attempt including the refusals. Use this to read what happened; use stats for totals, and inspect_action for one action end to end.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many receipts to return, 1 to 200, default 20, oldest last.
controlNoReturn only receipts citing this control id, exactly as `ctrlrun receipts --control` filters. Omit it to return every receipt.

TDQS

A4.6/5.0
Behavior4/5

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

The description discloses that the tool is read-only, returns receipts oldest last, includes refusals, and returns portable JSON. Since no annotations are provided, the description carries the full burden, and it does so well. It doesn't mention pagination or rate limits, but the limit parameter is documented in the schema, so the core behavioral traits are covered.

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?

Two sentences, front-loaded with the most important fact (read-only), and every clause earns its place. The sibling routing is compact and clear. No wasted words.

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 read-only list tool with two optional parameters and no output schema, the description covers the essential context: what it returns, ordering, inclusion of refusals, and how to route to siblings. It doesn't describe the exact JSON shape, but the description says 'portable JSON' and the schema covers parameters, so the missing return format is a minor gap.

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 description coverage is 100%, so the schema already documents both parameters. The description adds context by explaining the 'control' parameter's filtering behavior ('exactly as `ctrlrun receipts --control` filters') and the ordering ('oldest last'), which enriches the schema's bare descriptions. This exceeds the baseline 3.

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 states a specific verb ('read'), a resource ('individual receipts'), and key characteristics ('portable JSON, oldest last, one for every attempt including refusals'). It also distinguishes itself from siblings by naming stats and inspect_action as alternatives. This is a clear, specific purpose that an agent can act on.

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?

The description explicitly says when to use this tool ('Use this to read what happened') and when to use alternatives ('use stats for totals, and inspect_action for one action end to end'). This is direct routing guidance that leaves no ambiguity about selection.

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

resolveA

WRITES. States what actually happened to an effect whose outcome is unknown. Requires an authenticated human and a reason; the answer is recorded under that person's name, and a 'failed' resolution permits a retry that is currently blocked.

ParametersJSON Schema
NameRequiredDescriptionDefault
reasonYesHow you know, in plain words. Must be non-empty. A resolution answered through an assistant has a conversation behind it and no record of it, so this reason is that record.
outcomeYesWhat you established actually happened at the remote. 'committed' means the effect took hold and must never run again; 'failed' means it did not, and is what releases the blocked retry. Check the remote before answering: this is a claim about the world, not a guess.
effect_keyYesThe effect to settle, as it appears in the effects list, for example refund:txn_1. Only an effect whose outcome is unknown can be resolved.

TDQS

A4.1/5.0
Behavior5/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 does so well. It opens with 'WRITES,' discloses the authentication requirement, states that the answer is recorded under the person's name, and explains the retry-unblocking consequence of a failed resolution.

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 description is compact and front-loaded, beginning with the mutation warning and then providing the essential conditions and consequences in two sentences. Every clause earns its place.

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?

The description covers prerequisites, side effects, and the core purpose well, and the schema fills in parameter details. It does not describe the expected return value or error behavior, and since there is no output schema, a fully complete description would mention what the caller gets back.

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?

The input schema already provides detailed descriptions for all three parameters, so schema coverage is 100%. The description adds behavioral context but no parameter-level meaning beyond what the schema already offers.

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?

The description clearly states a specific action and resource: it 'States what actually happened to an effect whose outcome is unknown.' It is easy to distinguish from inspection or listing tools, though it does not explicitly differentiate itself from sibling tools like approve or deny.

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 usage context: resolve only an effect whose outcome is unknown, and do so with an authenticated human and a reason. It stops short of saying when not to use the tool or which sibling tool should be used instead.

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

statsA

Read-only. One summary of what this store's receipts say over an optional window: how many actions were allowed, refused or sent to a human, and how their effects ended. Use this for totals; use receipts when you need the records themselves.

ParametersJSON Schema
NameRequiredDescriptionDefault
sinceNoHow far back to count: an ISO-8601 timestamp carrying an offset, or a relative window written as <n>m, <n>h or <n>d, such as 30m, 24h or 7d. Omit it to count every receipt in the store.

TDQS

A4.4/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 of behavioral disclosure. It declares the operation is read-only, explains that it returns aggregated counts rather than records, and names the categories included (allowed, refused, sent to human, effect outcomes). It doesn't detail output structure, but for an aggregation tool this is adequate.

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?

Two sentences convey the tool's core behavior, scope, and routing guidance without waste. The read-only and summary aspects are front-loaded, making the tool's identity immediately clear.

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 tool with one optional parameter and no output schema, the description covers what the tool does, what categories it counts, and when to use it versus the receipts alternative. It lacks a description of the exact output fields, but the summary nature and listed categories give an agent sufficient expectation of the return value.

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 coverage for the single optional parameter 'since' is 100%, with a detailed description of formats and omission behavior. The tool description only references an 'optional window,' adding no new information beyond what the schema already provides, so the baseline of 3 is appropriate.

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 clearly states the tool's purpose: it produces a read-only summary of receipts over an optional window, counting allowed, refused, and human-sent actions and their outcomes. It distinguishes itself from the receipts tool by explicitly saying this tool provides totals rather than individual records.

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?

The description gives explicit direction: 'Use this for totals; use receipts when you need the records themselves.' This tells an agent exactly when to choose this tool over its siblings, with no inference required.

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. 8 tool updates
    • Changedapprove1 field changed
      • addedInput schema / properties / request_id / description
        Added value: +"The pending request to grant, as returned by list_pending_approvals. The grant is bound to the hash of the action that request already names, so there is no argument here by which a different action could be approved."
    • Changeddeny1 field changed
      • addedInput schema / properties / request_id / description
        Added value: +"The pending request to refuse, as returned by list_pending_approvals."
    • Changedeffects1 field changed
      • addedInput schema / properties / state / description
        Added value: +"Return only effects in this state, or omit it for every effect. 'reserved' and 'executing' are in flight, 'committed' and 'failed' are settled, and 'ambiguous' is the one that needs a human: the effect may or may not have happened, and a retry stays refused until resolve says which."
    • Changedinspect_action1 field changed
      • addedInput schema / properties / action_id / description
        Added value: +"The action to inspect. Action ids appear on receipts and on the pending requests list_pending_approvals returns."
    • Changedlist_pending_approvals1 field changed
      • addedInput schema / properties / limit / description
        Added value: +"How many pending requests to return, 1 to 200, default 50. This bounds the response and not the scan, so a store holding many answered requests still walks them to find the pending ones."
    • Changedreceipts2 fields changed
      • addedInput schema / properties / control / description
        Added value: +"Return only receipts citing this control id, exactly as `ctrlrun receipts --control` filters. Omit it to return every receipt."
      • addedInput schema / properties / limit / description
        Added value: +"How many receipts to return, 1 to 200, default 20, oldest last."
    • Changedresolve3 fields changed
      • addedInput schema / properties / effect_key / description
        Added value: +"The effect to settle, as it appears in the effects list, for example refund:txn_1. Only an effect whose outcome is unknown can be resolved."
      • addedInput schema / properties / outcome / description
        Added value: +"What you established actually happened at the remote. 'committed' means the effect took hold and must never run again; 'failed' means it did not, and is what releases the blocked retry. Check the remote before answering: this is a claim about the world, not a guess."
      • addedInput schema / properties / reason / description
        Added value: +"How you know, in plain words. Must be non-empty. A resolution answered through an assistant has a conversation behind it and no record of it, so this reason is that record."
    • Changedstats1 field changed
      • addedInput schema / properties / since / description
        Added value: +"How far back to count: an ISO-8601 timestamp carrying an offset, or a relative window written as <n>m, <n>h or <n>d, such as 30m, 24h or 7d. Omit it to count every receipt in the store."
  2. 8 tool updatesv0.12.1
    • First observedapprove
    • First observeddeny
    • First observedeffects
    • First observedinspect_action
    • First observedlist_pending_approvals
    • First observedreceipts
    • First observedresolve
    • First observedstats

TDQS

A4.2/5.0

Scored across 8 tools

Disambiguation5/5

Each tool maps to a distinct resource or workflow step: pending list, approval/denial mutations, action history, raw receipts, effect states, aggregate stats, and effect resolution. No two tools are interchangeable, and the descriptions explicitly direct agents to the right one.

Naming Consistency3/5

Names split between bare verbs (approve, deny, resolve), verb-noun read commands (list_pending_approvals, inspect_action), and bare plural nouns for data views (receipts, effects, stats). They are readable and meaningful, but the naming convention is not uniformly predictable.

Tool Count5/5

Eight tools fit the approval-console domain well: write operations are covered by approve, deny, and resolve, while read-side views cover pending items, per-action history, raw receipts, effects, and statistics. No tool feels redundant or missing as filler.

Completeness5/5

The tool set covers the full workflow: identify pending requests, decide them, inspect full action history, inspect effect outcomes, and record resolutions that unblock retries. Auditing is supported through receipts and stats, so there are no obvious dead ends.

Maintenance

ActivityNo data
ResponsivenessWithin a week

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides a human approval gate for AI agents, enabling interactive inline cards for approving, editing, or rejecting actions before they are executed.
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Provides a human-approval tool for MCP-compatible agents, pausing execution until a human approves or edits the action, with every decision logged.
    2
    2 npm
    2
    MIT