> ## Documentation Index
> Fetch the complete documentation index at: https://ctrlrun.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Frequently asked questions

> Fourteen questions engineers ask: is it production-ready, SQLite or Postgres, idempotency keys, workflow engines, adapters, timeouts and receipts.

The fourteen questions that come up first, answered in under eighty words each.

<AccordionGroup>
  <Accordion title="Is it production-ready?">
    It runs in production on a single file or on Postgres across hosts, and every guarantee is
    graded by one suite against both stores. What it has not had: an external security audit, a
    third-party review of the kernel, and a soak of the length the roadmap asks for. Those are
    listed on [Run it in production](/production/index) rather than left for you to find.
  </Accordion>

  <Accordion title="Is SQLite really enough?">
    On one host, yes. `BEGIN IMMEDIATE` is a write lock on the file, so one effect executes once
    across threads and across OS processes on that machine, and the conformance suite grades
    SQLite case for case beside Postgres. Move when a second host must write to the store, not
    before. [The choice, in one table](/production/postgres).
  </Accordion>

  <Accordion title="Isn't this just idempotency keys?">
    No. An idempotency key deduplicates at one API that chose to support it, inside its
    retention window. An effect key deduplicates at the agent, before the call, across every
    remote it touches, is bound to the approval and the receipt, and refuses a retry when the
    outcome is unknown rather than resending and hoping. Where a remote does support
    idempotency keys, pass the effect key as one and get both.
    [More](/compare/idempotency-keys).
  </Accordion>

  <Accordion title="Why not a durable workflow engine?">
    Different guarantee. An engine makes a workflow finish, retrying activities until they
    succeed; CTRLRun decides whether a consequential effect is authorized and refuses to retry
    an unknown outcome. Their advice, make activities idempotent, is the work CTRLRun does for
    you and enforces in the store. Run the workflow in the engine and decorate the activity
    that acts. [More](/compare/durable-workflows).
  </Accordion>

  <Accordion title="Do I need an adapter for my framework?">
    Probably not. `@protect` covers anything in your Python process, and the gateway covers
    anything behind an MCP server in any language. An adapter buys exactly one thing: routing an
    approval through the framework's own interrupt so a human answers where they already answer.
    A framework with no such primitive needs none. [More](/get-started/three-ways-in).
  </Accordion>

  <Accordion title="Is it exactly-once?">
    No, and nothing can be against systems it does not control. CTRLRun guarantees it will not
    knowingly execute the same logical effect twice, and never treats an unknown outcome as a
    failure. The remote is the only thing that knows what the remote did; when nobody knows,
    CTRLRun says so and stops. [More](/concepts/outcomes-and-ambiguous).
  </Accordion>

  <Accordion title="What happens on a timeout?">
    The effect becomes `AMBIGUOUS`, never `FAILED`, and a retry against it is refused. Only
    `NotExecuted`, raised by your executor when it knows the remote did nothing, means failed.
    A human runs `ctrlrun resolve --committed` or `--failed` after asking the remote, or a
    `reconcile` hook asks for them. [More](/concepts/outcomes-and-ambiguous).
  </Accordion>

  <Accordion title="Can the agent bypass it?">
    It can call the undecorated function, and the threat model says so: process-level
    enforcement is out of scope. Two things narrow it. The gateway sits between the agent and
    its tools, where the agent has no choice; and CTRLRun is never a tool the agent decides to
    call, because a check the agent opts into is not a check.
    [Threat model](/THREAT_MODEL).
  </Accordion>

  <Accordion title="Does it phone home?">
    No. There is no telemetry, no licence check and no network call of any kind in the kernel.
    `ctrlrun stats` counts the local SQLite file, and a test runs the demo in a subprocess whose
    sockets are all refused. The extras reach the network only where you point them: your JWKS,
    your webhook, your OTLP endpoint, your Postgres.
  </Accordion>

  <Accordion title="What if the human takes an hour?">
    The approval request expires at its TTL, fifteen minutes by default, and a waiting call
    raises `ApprovalTimeout` with nothing executed. Raise the TTL for slow approvals, or do not
    wait: `ApprovalRequired` carries the request id, so an agent can surface it and come back
    later with `with_approval(request_id)`. [More](/concepts/approval-binding).
  </Accordion>

  <Accordion title="One host or many?">
    Either. On one host the store is a SQLite file and reservation is atomic across processes
    through `BEGIN IMMEDIATE`. Across hosts, `pip install "ctrlrun[postgres]"` and change the
    store: a unique index on the effect key and compare-and-set updates give the same guarantee,
    graded by the suite written for SQLite. [More](/guides/run-on-postgres).
  </Accordion>

  <Accordion title="What is in a receipt?">
    One executed action: who proposed it, the action and its canonical arguments, the decision
    and why, the approval and approver, the effect key, the outcome, the timestamps, the hash
    and declared version of the policy that decided it, and the hash of the receipt before it.
    Portable JSON, one object per line. [More](/reference/receipt-and-event-schemas).
  </Accordion>

  <Accordion title="Is the receipt chain a signature?">
    No. Each receipt carries the hash of the one before, so an edit, a deletion from the middle
    or a reordering is detected and named by `seq`. It does not prove who wrote a receipt,
    receipts are not signed, and it does not survive an administrator who can rewrite every row
    including the chain head. [More](/security/receipt-chain).
  </Accordion>

  <Accordion title="What is not covered?">
    A compromised host or process. A malicious administrator with write access to the store. A
    lying remote. A compromised approver. An executor that raises `NotExecuted` after the remote
    acted, which `ctrlrun verify` cannot see. Data exfiltration through reads. Authority across
    an agent-to-agent hop. [Threat model](/THREAT_MODEL).
  </Accordion>
</AccordionGroup>

<script type="application/ld+json">
  {JSON.stringify({
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [
      {"@type": "Question", "name": "Is CTRLRun production-ready?", "acceptedAnswer": {"@type": "Answer", "text": "It runs in production on a single file or on Postgres across hosts, and every guarantee is graded by one suite against both stores. It has not had an external security audit, a third-party review of the kernel, or a soak of the length the roadmap asks for."}},
      {"@type": "Question", "name": "Is SQLite really enough for production?", "acceptedAnswer": {"@type": "Answer", "text": "On one host, yes. BEGIN IMMEDIATE is a write lock on the file, so one effect executes once across threads and across OS processes on that machine. Move to Postgres when a second host must write to the store, not before."}},
      {"@type": "Question", "name": "Isn't this just idempotency keys?", "acceptedAnswer": {"@type": "Answer", "text": "No. An idempotency key deduplicates at one API that chose to support it, inside its retention window. An effect key deduplicates at the agent, before the call, across every remote it touches, is bound to the approval and the receipt, and refuses a retry when the outcome is unknown."}},
      {"@type": "Question", "name": "Why not a durable workflow engine?", "acceptedAnswer": {"@type": "Answer", "text": "A workflow engine makes a workflow finish, retrying activities until they succeed. CTRLRun decides whether a consequential effect is authorized and refuses to retry an unknown outcome. Run the workflow in the engine and decorate the activity that acts."}},
      {"@type": "Question", "name": "Do I need an adapter for my framework?", "acceptedAnswer": {"@type": "Answer", "text": "Probably not. The @protect decorator covers anything in your Python process and the gateway covers anything behind an MCP server in any language. An adapter only routes an approval through the framework's own interrupt."}},
      {"@type": "Question", "name": "Is CTRLRun exactly-once?", "acceptedAnswer": {"@type": "Answer", "text": "No, and nothing can be against systems it does not control. CTRLRun guarantees it will not knowingly execute the same logical effect twice, and never treats an unknown outcome as a failure."}},
      {"@type": "Question", "name": "What happens on a timeout?", "acceptedAnswer": {"@type": "Answer", "text": "The effect becomes AMBIGUOUS, never FAILED, and a retry against it is refused. Only NotExecuted, raised by your executor when it knows the remote did nothing, means failed. A human or a reconcile hook resolves it."}},
      {"@type": "Question", "name": "Can the agent bypass CTRLRun?", "acceptedAnswer": {"@type": "Answer", "text": "It can call the undecorated function; process-level enforcement is out of scope. The gateway narrows this by sitting between the agent and its tools, where the agent has no choice."}},
      {"@type": "Question", "name": "Does CTRLRun phone home?", "acceptedAnswer": {"@type": "Answer", "text": "No. There is no telemetry, licence check or network call in the kernel. ctrlrun stats counts the local SQLite file, and a test runs the demo in a subprocess whose sockets are all refused."}},
      {"@type": "Question", "name": "What if the human takes an hour to approve?", "acceptedAnswer": {"@type": "Answer", "text": "The approval request expires at its TTL, fifteen minutes by default, and a waiting call raises ApprovalTimeout with nothing executed. An agent can also surface the request id and come back later."}},
      {"@type": "Question", "name": "Does CTRLRun work across many hosts?", "acceptedAnswer": {"@type": "Answer", "text": "Yes. On one host the store is a SQLite file. Across hosts, install the postgres extra and change the store: a unique index on the effect key and compare-and-set updates give the same guarantee."}},
      {"@type": "Question", "name": "What is in a CTRLRun receipt?", "acceptedAnswer": {"@type": "Answer", "text": "Who proposed the action, its canonical arguments, the decision and why, the approval and approver, the effect key, the outcome, the timestamps, the policy hash and version, and the hash of the receipt before it."}},
      {"@type": "Question", "name": "Is the receipt chain a signature?", "acceptedAnswer": {"@type": "Answer", "text": "No. The chain detects alteration and names it by seq. It does not prove who wrote a receipt, receipts are not signed, and it does not survive an administrator who can rewrite every row including the chain head."}},
      {"@type": "Question", "name": "What does CTRLRun not cover?", "acceptedAnswer": {"@type": "Answer", "text": "A compromised host, a malicious administrator with write access to the store, a lying remote, a compromised approver, an executor that raises NotExecuted after the remote acted, data exfiltration through reads, and authority across an agent-to-agent hop."}}
    ]
    })}
</script>

## Next

* [Why](/why) · [Get started](/get-started/quickstart).
* [Compare](/compare/idempotency-keys): the five comparisons in full.


## Related topics

- [Reconcile against Stripe or Kubernetes automatically](/cookbook/reconcile-against-the-remote.md)
- [CTRLRun and guardrail libraries](/compare/guardrail-libraries.md)
- [The specifications](/architecture/specifications.md)
- [CTRLRun and agent oversight toolkits](/compare/governance-toolkits.md)
- [Run it in production](/production/index.md)
