contenox
Browse docs/

The oracle

Unattended subagents stall. You start a plan, walk away, and come back to a run still waiting on an ask its own intent already answers — “which directory holds the docs?” on a subagent whose intent names the docs — or on a gated tool call nobody was there to approve. The oracle keeps the shift moving: it reviews each ask a subagent raises, rules on the routine ones so the run continues, and leaves everything else exactly where it was — a durable ask waiting for a human.

wait is always the safe verdict, and the durable record shows exactly who decided what.

What it may rule on

Ask kindRaised whenVerdicts
questionthe subagent calls mission_ask_attentionanswer, wait
gated tool callthe subagent makes a call the envelope put on the approve tierapprove, deny (with guidance), wait

The second is off by default and takes two separate grants to turn on — see Turning it on. Letting a model release a gated call is a bigger grant than letting it answer a question, so it is never implied by the first.

Turning it on

The oracle is a configured default, not a flag you remember to pass:

# Which oracle. Setting this is what turns it on; unset means no oracle at all.
contenox config set default-oracle-chain chain-oracle-default.json

# Optional: the envelope the oracle chain itself runs under.
# Unset already uses hitl-policy-oracle.json, transpiled from [envelopes.oracle].
contenox config set default-oracle-policy hitl-policy-oracle.json

# Optional: let it rule on gated TOOL CALLS, not just questions.
contenox config set oracle-approves-tool-calls true

Every value is overridable per run, the way every contenox default is:

# In your terminal (beam): live visibility, auto-approved routine calls, Ctrl+C kill switch
contenox beam --new --oracle default --oracle-approves-tool-calls

# Over ACP (editors):
contenox acp --oracle default --oracle-approves-tool-calls
contenox acp --oracle off          # this run only, no oracle

The oracle mounts on the host (whether contenox beam or contenox acp), which is where sessions and subagents come from. The short name default resolves directly to chain-oracle-default.json.

The two grants

For background subagents, turning the oracle on is not enough to let it release a gated call. The subagent’s own envelope decides that, and it is a separate envelope from the oracle’s:

# agents.toml
[envelopes.mine]
missions.answer = "approve"   # answers AND rulings on gated calls

[envelopes.mine.attention]
max_agent_answers = 3
max_agent_approvals = 10

which transpiles to:

{
  "attention": {
    "allowAgentAnswers": true,   "maxAgentAnswers": 3,
    "allowAgentApprovals": true, "maxAgentApprovals": 10
  }
}

Both have to agree — the config key says this host has an oracle that may rule on calls, the envelope says this subagent’s calls may be ruled on. Either one off means the call waits for you.

The counts are durable and actor-aware: a restart does not refill them, your own verdicts do not consume them, and the counting and the write happen in one statement, so a firing session’s agent and the oracle answering the same mission concurrently still cannot overrun the budget.

What happens on an ask, step by step

  1. The subagent raises the ask. It becomes a durable row immediately (contenox approvals list shows it), and the unit waits on that row — or, if its dispatcher detached its asks, checkpoints and hands the process back.
  2. The ask is offered to the oracle in-process, before a human sees it.
  3. The oracle runs its chain with the ask as input: the ask kind, the subagent’s intent, and — for a gated call — the tool, its arguments, and the rule that gated it. The model holds exactly one tool, oracle.submit_verdict, and one job: judge it against that intent and submit one verdict. The loop is budgeted and self-correcting — a malformed call or a chat-text reply gets a machine-register correction and a bounded retry.
  4. A verdict goes through the service layer, under the subagent envelope’s bounds, recorded as answeredBy/decidedBy: "oracle". It lands on the same durable row the unit is waiting on, so the unit stops waiting and carries on in place — or, if that unit’s dispatcher had detached its asks, the durable resume path picks the verdict up and continues it from its checkpoint. Either way it is the route a human’s answer takes, whether it lands in a second or a day later.

Everything that is not a verdict leaves the ask alone

wait, a chain error, a spent budget, a malformed call after its retries, an envelope that forbids it — every one of these changes nothing. The ask stays pending and takes the untouched normal path: your terminal, your editor’s card, or the on_timeout verdict.

The oracle can only ever be faster than you, never more permissive than the envelope. It cannot widen a rule, cannot reach a tool the policy denies, and cannot exceed the counts.

Why a denial carries guidance

A refused tool call reaches the model as a bare rejection — the protocol has no free-text channel on a permission response. A subagent that only ever sees “rejected” circles: it retries the same call, or gives up.

So a deny verdict takes an optional guidance — one sentence naming what to do instead:

{"verdict": "deny", "guidance": "write under ./out, not /tmp", "askId": "..."}

That redirect is recorded on the ask, and the runtime’s next prompt turn to the subagent carries every redirect it collected. The subagent learns why it was blocked and what to do about it, on the turn after it happened.

What it costs

One model call per ask reviewed. The oracle chain is small — one tool, a hard round budget — but it is not free, and it runs on every ask the subagent raises, including the ones it will decline to decide.

If a subagent raises enough asks for that to matter, the oracle is treating a symptom. A subagent asking constantly is usually one whose intent is too vague or whose envelope gates the wrong things. Fix those first; the oracle is for the residue, not the flood.

The oracle’s own envelope

[envelopes.oracle] in agents.toml, transpiled to hitl-policy-oracle.json, is the only pure allowlist in the shipped set:

[envelopes.oracle]
description = "The adjudication chain: submit a verdict, reach nothing else."
default_action = "deny"
missions.answer = "deny"

[envelopes.oracle.tools]
"oracle.*" = "allow"

default_action: deny with the in-process oracle toolset allowed and nothing else — its whole surface is the verdict tool and the deterministic state gate behind it. No file axis, no shell axis, no network axis, and therefore no command_prefix_allowlist to subvert through PATH. Denied rather than approved because nobody watches this chain to answer an ask, and an ask it raised would be offered back to itself.

missions.answer = "deny" is human-only on both halves: the oracle never adjudicates its own asks. An ask the oracle chain raises waits for a person, which is also what stops it from being offered its own question in a loop.

Interactive sessions with the oracle (contenox beam)

When working directly in the terminal with contenox beam, you often want the agent to execute routine steps without interrupting you every few seconds to confirm every file edit or test run — while still retaining live visibility and the Ctrl+C interrupt kill-switch.

Starting beam with the oracle enabled:

contenox beam --new --oracle default --oracle-approves-tool-calls

binds each prompt turn to an active session mission. As the agent works:

  • Routine in-scope actions (editing named files, running tests specified in your prompt) are reviewed against your prompt’s intent and approved automatically.
  • Out-of-scope or destructive actions are denied with steering guidance or returned as wait.
  • Any call returned as wait renders the standard approval card in your beam terminal for you to decide.
  • You see every action streamed to your terminal in real time, with the ability to pause or interrupt the turn (Ctrl+C) at any moment.

Reading what it did

contenox approvals list            # what is still pending
contenox mission asks              # narrowed to open missions
contenox mission reports <id>      # what the subagent actually did

Every resolved row records who decided it. An oracle verdict is never anonymous and never indistinguishable from yours.

Next

Esc to close