Skip to content

ctrlrun 0.5.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 07:02
· 476 commits to main since this release
86e64fe

v0.4 asked does it hold in my setup? v0.5 asks a narrower and harder question: can somebody
else implement this?
The adapter contract is one of the six things v1.0 freezes, so it is
written to be lived with rather than revised once somebody tries it.

The milestone's own answer is yes, with caveats. A session that could read the five
specifications and nothing else — no kernel source, no reference adapter, no test — wrote a third
adapter against the contract and returned fourteen questions it could not answer. Every one
became an edit, and one of them was a defect in a shipping adapter that no test caught. That
exercise, not the two adapters, is what v0.5 is for.

Added

  • Two reference adapters, on their own version line and in their own distributions:
    ctrlrun-langgraph (adapters-langgraph-1.0) reuses interrupt(), Command(resume=...) and
    the checkpointer; ctrlrun-openai-agents (adapters-openai-agents-1.0) reuses the SDK's
    tool-approval interruption. Neither is in the ctrlrun wheel or sdist (T136). LangGraph
    passes the conformance kit 6/6; the Agents SDK 4/4 with two suites not_applicable, each with
    its reason on the report and in the README.

    Prevention or attribution, in that word, is the sentence each README leads with.
    LangGraph's resumption carries the arguments a human answered against and core re-checks them;
    the Agents SDK records that a call was approved and not what its arguments were.

  • ctrlrun.adapter — the surface: FrameworkInterrupt, PendingApproval,
    ApprovalAnswer, InterruptApprovalProvider, needs_approval, banner, and
    Control.resolve_principal promoted from private. Core, stdlib, in the action path. An
    adapter returns an answer and one core provider writes the grant, through the same calls
    ctrlrun approve makes — which is what makes "never a second approval path" structural.

  • ctrlrun.conformance — this repository's own v0.1 §7 and v0.3 §10 acceptance tests,
    runnable against any adapter, in core. It is not a certification and passing it is not a
    claim about quality: it answers one question, does an action driven through this adapter get
    the same refusals as one driven through @protect?
    Fifteen deliberately broken fixtures were
    written first, and each fails the suite named for it and no other.

  • docs/adapters.md, and a README section that opens by saying when you do not need an
    adapter (T139) — @protect covers anything in this process and the gateway anything over MCP.

  • The framework probe was run against LangGraph 1.2.11 and openai-agents 0.22.0, five
    repetitions each, and the results are published. Read the approval-mutation column carefully:
    executed_once there does not mean the scenario went well.

Changed

  • ctrlrun demo --help said four scenarios and ran five, stale since v0.3 added the
    authority escalation.

Security

Three independent reviews and item 6 found five authorization defects in
ctrlrun-openai-agents before it shipped. All are fixed, mutation-tested, and recorded here
because the pattern matters more than any one of them: the SDK's approval record is keyed to a
tool call, and a CTRLRun grant binds to an action hash
, so every defect was the same shape —
reading a coarser answer as though it answered a finer question.

  • interrupt() returned granted=True unconditionally.
  • It then accepted a sticky per-tool decision, so always_approve=True answered for later
    calls no human saw.
  • It then answered for every action raised under one tool call: a human approving a $5 refund
    authorized a $1,000,000 wire raised beside it, with a receipt naming the channel as approver.
  • unwrap() returned the first CTRLRunError anywhere in the chain, so a nested NotExecuted
    masked an AMBIGUOUS refund — safe to retry reported for an effect that may have landed.
  • Observe mode interrupted and blocked the action. Found by item 6 without reading the
    adapter. §3.6's rule followed for one framework shape and had to be required of the other;
    a deployment evaluating CTRLRun in the mode built for evaluating it would have had its agent
    halted.

Added

  • docs/SPEC-v0.5.md — the v0.5 contract, a delta over v0.1, v0.2, v0.3 and v0.4. It fixes
    the adapter surface (§2), the approval round trip (§3), the SPEC-v0.3.md §4.3.1 rows an
    adapter adds (§4), the conformance kit (§5), packaging and versioning (§6), what an adapter
    must document (§7), the acceptance tests T126–T139 (§8) and the public names v1.0 will freeze
    (§9). No implementation lands with it.

    Five decisions are written into it with their arguments, because a decision whose
    reasoning lives only in a build note is one the next session re-litigates:
    ApprovalRequired + with_approval rather than Suspended + Control.resume; a Protocol
    rather than a base class; the conformance kit in this repository rather than a third distribution;
    one repository with separate distributions; and an adapter that sees the principal and
    never supplies one.

    A sixth was open and is settled in §3.6: an adapter never interrupts in observe mode,
    never prints — it is inside somebody else's loop and may have nowhere to print — and
    logs the SPEC-v0.3.md §6.5 banner once per Control, which ctrlrun.adapter.banner
    does so no two adapters word it differently. The conformance kit refuses an observing
    Control — a refused report with no suites, which is not a success — rather than producing
    an all-not_applicable report with a zero denominator, because 0/0 reported as a pass is
    the false green SPEC-v0.4.md §3.8 refuses by name.

    An independent review in a session that did not write the document found five defects that
    would each have produced an insecure or unimplementable adapter, and §9.1 records them.
    The
    worst was --principal-from-client-info's third costume: §3.5 told an adapter to answer its
    framework's pre-invocation predicate with Control.evaluate(action), and Action.principal
    has no default — so the only way to obey was to build a principal from the framework's
    session. ctrlrun.adapter.needs_approval is core's because of that, and
    Control.resolve_principal is promoted from private for it: the identity seam an adapter may
    read and may not supply.

  • ctrlrun.adapter — the adapter surface: the FrameworkInterrupt Protocol,
    PendingApproval, ApprovalAnswer, InterruptApprovalProvider, needs_approval and
    banner. Core and stdlib, re-exported from ctrlrun, and Control.resolve_principal is
    promoted from private for it — the identity seam an adapter may read and may not supply.

  • ctrlrun.conformance — the adapter conformance kit: the SPEC-v0.1.md §7 and
    SPEC-v0.3.md §10 acceptance suites runnable against any adapter through that surface, and
    eleven adapters broken in one named way each, written before the reference adapters because
    "two adapters pass the suites" means nothing until the suite can fail.

    It is core and stdlib-only, not the extra SPEC-v0.5.md §5.1 originally specified. The
    premise there was that a kit needs pytest; building it showed otherwise, and an extra with
    no dependency behind it is an install line that installs nothing. §12.1 records the change.
    dependencies is unchanged: pyyaml and click.

Changed

  • docs/ROADMAP.md's v0.5 bullet was wrong and is corrected here, not silently. It said
    the reference adapters map their frameworks' interrupts onto Suspended / Control.resume,
    "which v0.2 already ships for exactly this shape". It does not: Suspended exists for the
    remote asking a question mid-execution, where the reservation is already taken and must
    stay taken, and an approval gate has none to hold — v0.1 consumes the approval in the same
    transaction as the reservation, so a human deliberating for an hour pins nothing.
    SPEC-v0.5.md §3.1 argues it in full. This is the treatment SPEC-v0.4.md §9.4 gave the
    threat model's sentence about a check verify could not deliver.

  • Version is 0.5.0.dev0.