Repository navigation
v0.16.0
The compile now decides a pipeline's structure before any agent runs, and
checks it against the skill, its tools and a sample of its input.
- The structure pass is the default compile skeleton. One schema-locked
Haiku call reads the whole skill and returns every node. rote checks each
node against the skill's text, its tools' schemas and the sample input.
--structure rulesopts out. - Clef or Jev checks the structure pass by default (
--decider auto).
Clef runs whenROTE_CLEF_ACCOUNT_IDandROTE_CLEF_API_TOKENare set,
else Jev whenTYPESAFE_API_KEYis set, else nothing checks it and the
compile says so. Against a confident verdict, the pass must change the
node, or keep it and quote 4+ words of the step's text. rote runis read-only by default on both sides. An agent loop gets
only the MCP tools that cannot write.--allow-writeslifts it.- Fidelity scoring is drift-aware.
rote bench --fidelitymatches a
read on the request it sent, and reports a live answer that changed as
drift, not as a lost read. - MCP calls are grounded in data. Tool arguments are checked against each
tool's own input schema. Result shapes come from recordings and declared
output schemas before any guess. A bound tool must exist, and every
argument must have a source. - A failed compile reports what it spent. The summary
cost_usdis the
whole compile, including failed calls and System-1's decision calls.
Upgrading from 0.15
This release has breaking changes. Each one has a full entry below.
- The default compile skeleton is new. The structure pass makes one
model call (about $0.015 and 70 s) on the compile driver's own client,
never a fallback key.--structure rules(orstructure: rules,
ROTE_STRUCTURE) keeps the rule-based skeleton. System-1, new in this
release, is the default compile path, and--no-system1runs the
full-rubric agent. rote run <pipeline>is read-only unless you pass--allow-writes.
An MCP tool that may write is refused before it is called. A tool passes
when its server marks itreadOnlyHint, or when you registered the whole
server withrote mcp add <name> <url> --read-only. A deployed run is
not affected.- Decision-model credentials are
ROTE_CLEF_ACCOUNT_ID/
ROTE_CLEF_API_TOKEN(Clef) andTYPESAFE_API_KEY(Jev).CLOUDFLARE_*
is no longer read for them, so your deploy credentials never opt a compile
into inference. mcp.args: {}calls the tool with no arguments. Only an omitted
argspasses the step's payload through.- IR: a
loop_bodysub-node must be a pure_function, external_call or
llm_judge, and must not bemandatory. - Re-emit TypeScript apps to pick up
@anthropic-ai/sdk^0.136.0. - With
XDG_DATA_HOMEorXDG_CACHE_HOMEset, rote's data and caches
move under them. That covers the eval corpus, the fixture store and the
price and spec caches. An existing~/.local/share/rote/eval-corpus.jsonl
is no longer read: move it to$XDG_DATA_HOME/rote/, or point
ROTE_EVAL_CORPUS_PATHat it.
Added
--tiebreak haiku(opt-in): Claude Haiku decides only the steps
System-1 is unsure of. One schema-lockedclaude-haiku-5-5call a
compile, billed like the compile (subscription under the claude driver,
the api driver's own client under--agent api), about $0.001 and 6 s.
On the held-out skills it lifts kind accuracy from 0.49 to 0.68 with the
rules, 0.66 to 0.74 with Clef and 0.67 to 0.72 with Jev. It is off by
default: in a cached-API fidelity bench (3 compiles per cell) it scored
0.909 against the rules' 0.915 on expense-audit and 0.855 against 0.858 on
ops-report, within the spread but not at least the rules' level.
--tiebreak haiku(ortiebreak: haiku,ROTE_TIEBREAK) turns it on. With no lane
the compile fails and says so.rote bench'scost$now includes
System-1's decision calls.- Effort knob for Anthropic judges and agent loops.
ROTE_EFFORT_<NODE_ID>
sets one node's effort, andROTE_JUDGE_EFFORTsets it for every node. An
empty value omits it. The SDK lanes sendoutput_config.effort; the
subscription lane passesclaude -p --effort, and an unknown level fails
there too instead of being ignored. Unset, judges still send no effort (the
API runshigh): measured on 8 example judges,lowsaved 15 to 33% of
output tokens but changed decisions. Works on every runtime. --decider jev: Typesafe's Jev decision model as a System-1 decider,
beside Clef. Each has its own route and frozen thresholds. Jev's thresholds were fitted on the five dev
skills and then measured once on the held-out set: kind 0.67 against the
rules' 0.49, 3 of 3 gates against 2 of 3, one false gate, about $0.001 a
compile. Clef there: kind 0.66, 3 of 3 gates, no false gate, about $0.005.
Jev runs on Typesafe's own API withTYPESAFE_API_KEY.- The decider is a layered setting.
--decider,ROTE_DECIDER, ordecider:inrote.yamlor the user
config, with strict validation.rote initasks for it androte config
shows it. An explicit model decider without its credentials
(ROTE_CLEF_ACCOUNT_IDandROTE_CLEF_API_TOKEN, orTYPESAFE_API_KEY)
fails the compile. A--deciderflag on a compile
that does not run System-1 (--no-system1,--update) is a usage error.
A configured one is skipped for that compile, with a one-line note. rote bench --fidelity: does the compiled pipeline behave like the
skill? Each compiled pipeline runs on fixed cases (input, gate answers,
fixture MCP responses served by a local fastmcp server) and is scored
against the skill itself run as an agent on the same cases, several
times, counting only what every agent run agrees on: outputs matched by
value, outcome, reads, ordered side effects, and gate payload fit.
References are recorded once with--record-reference, and
--fidelity-of DIRscores any compiled dir. Cases and references ship
for expense-audit and ops-report. Seedocs/compile-bench.md.- System-1 compile: rules decide, the agent only
writes code. Before the agent runs, rote decides the whole pipeline
structure from the skill's text with no inference: steps and node kinds,
data flow, fan_out, per-item loops, mandatory flags, gates with their
signals, payloads and timeouts, input fields named in the skill's own
words, constants, and the skill's markdown rule tables as tested Python.
Every decision carries a confidence and the rule behind it. The agent
then gets a short rubric and writes only module implementations, tests
and judge prompts. rote restores every decision after each of its stops,
lists any change it proposes without applying it, and writes
compile-report.mditself from data. It is the default path (see
Changed). See
docs/design/system1-compile.md. rote bench, a scored compile benchmark (dev tool, spends tokens).
It compiles the example skills, scores eachpipeline.yamlagainst the
hand-writtenexpected/pipeline.yaml(node and kind match, edges, wave
order, fan_out, mandatory, gates, inputs), re-verifies the deliverable,
checks the dbos and cloudflare emits, and writes a diffable
results.json(--compare). Hidden fromrote --help. A pre-#144
baseline is inexamples/bench/baseline.json. See
docs/compile-bench.md.rote servealways exposes two built-in tools. With no registry
file the server used to answertools/listwith nothing, which left
MCP directories (Glama scores a server by its tool definitions) with
no tool to score and a fresh install with nothing to call.
rote_list_pipelineslists every registered pipeline (name,
description, runtime, trigger summary, trigger/status/signal tool
names,pipeline.yamlpath, registration time) plus the registry path
and whether the file exists, with an optionalruntimefilter. An
empty registry returns an empty list.rote_describe_pipelinereturns
one entry's input schema and trigger configuration with URL passwords
replaced by***, and names the registered pipelines when asked for
an unknown one. Both are read-only, return structured content with an
output schema, and re-read the registry per call. Therote_prefix
is reserved:rote registerrefuses it, and an entry in a hand-edited
registry whose trigger or companion name collides with a built-in is
skipped with one stderr warning instead of shadowing it.rote run <pipeline>is read-only, with--allow-writesto lift it.
The run setsROTE_MCP_READ_ONLYfor the emitted app (aswrangler dev --varon Cloudflare). The Python, Node and Workers MCP helpers check each
call against thetools/listthey already fetch and refuse a tool that may
write before calling it, as a configuration error that is not retried. One
predicate decides it on both sides ofrote run: the server marks the tool
readOnlyHint, or the server was registered with the newrote mcp add <name> <url> --read-only. An agent loop in a read-only run is handed only
the tools that pass, on every lane (--allowedTools/--disallowedTools
forclaude -p, a per-servermcp_toolseton the SDK lane, filtered
bindings in TS). A loop with no passing tool does not start, and the error
names each withheld tool.rote run <skill>takes gate answers through--signal NAME=JSONor
NAME=@FILE, in the order given.--inputtakes@FILEtoo.rote run <dir> --live-check(--backend api): one real, read-only
call per entry vendor read, with your own credentials, compared with what
rote believed (reads_missing,schema_drift,spec_only). Only GET and
HEAD requests that resolve to a spec operation run. Anything else is
refused first, and again at run time. The report holds field paths and
types, never values. Drift or a missing read exits 1.- MCP tool arguments are grounded in each tool's input schema. Before
the agent runs, rote lists the tools of the registered servers the skill
names (--tool-discovery alllists every server,nonenone) and hands
their schemas to the agent. Verification checks everymcp:binding
against them. A skill may also shipmcp-tools.jsonbeside its SKILL.md
with each server'stools/list. A live listing wins per server, and a
stale snapshot is reported. - The pipeline's input contract is grounded in a sample input. The
sample comes from--sample-input, elsesample-input.jsonor
input.jsonin the skill dir, else a baseline's input. It is reduced to a
schema and handed to the agent, and verification runs the pipeline's pure
functions (and recorded MCP calls) on it before the compile is done. The
examples now shipskill/sample-input.json. - MCP hard checks in verification:
tool_unknown(a bound tool the
livetools/listdoes not list),tool_arguments_unsourced(an
mcp.argsentry naming a payload key nothing fills), and
declared_tool_unbound(a tool the skill names and a server declares, but
no node calls). A server that could not be listed proves nothing and never
produces the first. --effort {low,medium,high}on compile and analyze, with
ROTE_EFFORTand aneffortconfig key. Theclaudedriver now defaults
tolow: on the bench, expense-audit went from $2.25 / 986 s to $1.20-1.31 /
423-513 s with quality held.--backend apicompiles research and check vendor calls. The agent is
told it targets the api backend and must cite the docs it read. The api
and codex drivers get web research (the openai-api driver refuses
--backend api). Verification checks every vendor request against the
vendor's own machine-readable spec (Google Discovery, OpenAPI 3, GraphQL
schemas), or against a spec the agent writes from the docs it fetched,
labeled lower trust. Generated tests run with the vendor libraries the
code imports. A compile records its backend inbackend.json, so a later
rote emitkeeps it.- A local fixture store at
$XDG_DATA_HOME/rote/fixtures
(ROTE_FIXTURE_DIR), keyed by vendor/api/version, androte bench --fidelityfor--backend apipipelines against a fake vendor API built
from the spec. - Clef's AI Gateway calls carry
CLOUDFLARE_AI_GATEWAY_METADATAwhen
CLOUDFLARE_AI_GATEWAY_IDroutes them. Metadata the gateway would
silently change (more than five entries, acf.*key, a non-scalar value)
is refused before the first call. - Three held-out bench skills in
examples/holdout/, and bench nodes
matched by structure, not by name.
Changed
-
With the structure pass on,
--decider clef|jevdouble-checks it.
The decision model answers its two questions (kind, and whether a person
acts) for every SKILL.md heading, one step at a time. A confident answer
the skeleton contradicts goes back to the pass as one more repair round,
never as an override; one the pass keeps is an Open question with both
answers, and a kept one must quote at least four words of the step's own
text. The built-in decider is nowauto: Clef when
ROTE_CLEF_ACCOUNT_IDandROTE_CLEF_API_TOKENare set, else Jev on
Typesafe's own API whenTYPESAFE_API_KEYis set, else no check, with one
line saying which and why. The decision models no longer read
CLOUDFLARE_*, so deploy credentials never opt a compile into inference;
setROTE_CLEF_*instead.rote benchreports the checker per job and
takes--deciderto pin it.--decider rulesmakes no check. The warning
that--deciderdoes nothing with the pass on is gone. -
The structure pass decides the pipeline's skeleton by default. One
schema-lockedclaude-haiku-5-5call reads the whole skill and returns
every node, and rote checks each one against the skill's text, its tools'
schemas and the sample input, asking again up to twice. About $0.015 and
70 s a compile, billed like the compile. On the held-out skills it scores
kind accuracy 0.88 against the rules' 0.49 and edge F1 0.78 against
0.58.--structure rules(orstructure: rules,ROTE_STRUCTURE) keeps
the rules, with no call. In cloud mode the flag is rejected, like
--tiebreak. With no lane the
compile fails and names that flag.rote analyzenow passes
--structurethrough; it used to ignore it. -
System-1's model calls run on the compile driver's own client only.
The structure pass and the tie-break useclaude -punder the claude
driver, the api driver's client under--agent api, and now the
openai-api driver's client and model under--agent openai-api, and
codex exec --output-schemaunder--agent codex. They used to fall
back to anANTHROPIC_*key or aclaudeon PATH, which billed your
Claude account for a compile sent to another provider and failed in
containers that have neither. A transient failure (a timeout, a rate
limit, a 5xx) is retried on the same lane up to three calls in all. -
System-1 writes its text as parallel calls. With the
claudeandapi
drivers, each node's interface entry, module and tests, and each judge's
prompt is its own schema-locked call, all at once (--max-parallel,
default 8), checked by code as it arrives and repaired alone. On the bench
(3 compiles per skill, subscription lane), expense-audit went from 321 s
and $0.69 to 63 s and $0.43 at fidelity 0.92 to 0.98, and ops-report from
209 s and $0.57 to 62 s and $0.33 at fidelity 0.60 to 0.93. The prefix run on the sample now
checks each result against its node'soutput_schemaand runs past gates
the sample answers. Seedocs/design/parallel-generation.md. -
System-1 is the default
rote compilepath. rote decides the pipeline
structure with rules before the agent runs, and the agent writes only text.
On the bench, a default compile of expense-audit or ops-report costs about
$0.47 and takes about 3 minutes, against about $1.00 and 6 to 7 minutes on
the full-rubric path.
--no-system1runs the full-rubric agent.--updatealways runs it,
because System-1 does not support incremental re-compilation yet, and the
compile log says so. The--system1flag is gone: there is nothing to opt
into.rote analyzetakes the same flags. In cloud mode--no-system1
and--deciderare local-only flags.Compiler
defaults to the same rule (system1=None), so library callers such as
rote-cloud get System-1 too. -
rote emitships every top-level.pyincompiled/extracted/, not only
the modules a node'simpl:names, for the dbos and python runtimes. A shared
helper the agent wrote (extracted/_common.py) used to stay behind, and the
emitted app crashed on its first import. A scratch.pyleft in that folder
now ships too, and from there to a deploy. Subdirectories,__pycache__and
non-.pyfiles do not. Verification's dry emit also flags anyextracted
import the emitted runtime cannot satisfy. -
The compile summary's
cost_usdis the whole compile's spend, failed
calls and System-1's decider, tie-break and structure pass included, with
system1_cost_usdbeside it. A failed api compile'sDriverErrorcarries
its usage and cost, sorote bench --budgetcounts it. -
Parallel repair reaches every finding a node owns, interface entries
included. A finding on rote's own files, or a test run where pytest itself
died, is reported and sends nothing to a model. -
MCP results have one shape: what the step receives. The emitted
helper's unwrap rule is the one every reader uses (probe, the prefix run,
System-1's interface). A tool's declaredoutputSchemaand a decoded
recording decide the shape before the skill's words do. A reader of a
result nobody observed or declared gets an untyped parameter, so a guessed
shape cannot fail a judge's input model. -
System-1 asks for a node's return value as a field list and code
writes the JSON Schema from it, so a model can no longer hand back a
schema that does not parse. -
Contract repairs and IR-validation repairs have their own budgets, so
an IR that a contract fix broke still goes back for repair. -
rote renders
compile-report.mditself from the deliverable, and the
claudedriver's child writes a 5-minute prompt cache instead of a
1-hour one. -
The api driver caches its prompt (four breakpoints) and reports a
priced run incompile-metrics.json. -
Per-user caches and data follow the XDG dirs: price caches and the
spec cache under$XDG_CACHE_HOME/rote, the fixture store and the eval
corpus under$XDG_DATA_HOME/rote. TheROTE_*overrides still win, an
empty XDG variable counts as unset, and nothing moves when the variables
are unset. The emitted TS MCP helper now treats an emptyXDG_*or
ROTE_MCP_CONFIGas unset too. -
Emitted TypeScript apps pin
@anthropic-ai/sdk^0.136.0(was
^0.115.0). -
Fidelity scoring: a read matches on the data it got or the request it
sent, and a changed live answer isresponse_drift, not a lost read. A
write is identified by the arguments every reference run agrees on, and a
value the pipeline sends in a write counts as an output it produced. Each
case reports its reference coverage and needs two reference runs. Fixtures
refuse arguments a real server would.
Fixed
-
--decider clef|jevrefuses a step it would read only part of.
Workers AI truncates a text state past the model's window without an
error, so a long step got a verdict on text the model never saw. Clef's
window is its documented 65,536 tokens; Jev's is unpublished, so the
smaller of the two listed figures (32,768) is assumed. A step estimated
over 90% of it fails the compile before any call, naming
--decider rules, and a call whose input filled the window fails it after. -
Emitted Python apps list what their code imports. The dbos app now
emits arequirements.txt(it had none, and its README installeddbos
alone), and both dbos and python add the vendor SDKs thatextracted/
imports, from the same import map verification installs from. An import
rote cannot name is a comment naming the file, never a guessed package.
The python app's file also listsfastmcpwhen the MCP helper ships;
it used to omit it. -
An unknown effort level fails before a subscription call is paid for.
claude -pignores an unknown--effortwith only a warning, so rote now
asks the CLI with a freeclaude --effort <level> --versionfirst, and
self-checks once per process that the CLI still warns in a way rote
recognizes. -
ROTE_TEMPERATURE_<NODE_ID>reaches judges on DBOS-TS and Inngest.
Those runtimes pass each judge a narrow env object, which left the
temperature knob out, so an empty value could not drop the parameter. -
Emitted judges work on claude-sonnet-5-5. That model rejects a forced
tool_choicewith HTTP 400, and every Anthropic judge forced one, so
pointingROTE_MODEL_<ID>at it failed every judge call. Judges on every
Anthropic lane (Python and TypeScript) now use structured outputs
(output_config.format), on every model. The request schema is rewritten
into the subset structured outputs accepts (objects closed,oneOfas
anyOf, bounds moved into descriptions). The judge still validates the
answer against the original. Arefusalormax_tokensstop is an
error, never data. An anthropic judge whose output schema has an object that
admits undeclared keys (no properties, oradditionalProperties: true
or a schema) or an untyped value now fails atrote emitand in the
compile's dry emit, naming the path. The dbos README and a new temporal
requirements.txtpinanthropic>=1.15, the SDK floor with
output_config. An
agent loop's one extra turn after it ends withoutsubmit_resultuses
the same mechanism. -
System-1 binds MCP arguments only from schema names that pass grounding's
filter. It read raw property names from a server's tool schema, so a name
that is not identifier-shaped became anmcp.argskey and the skeleton failed
IR validation: a hostile or sloppy server could break the compile. Such names
are now withheld and listed under Open questions, the same as in the agent's
brief. -
mcp.args: {}now calls the tool with no arguments, on every runtime. It
used to behave like an omittedargsand send the step's whole payload as the
tool's arguments. Only an omitted (null)argspasses the payload through. A
hand-written pipeline that relied onargs: {}as a pass-through must drop the
key; none of the examples does. -
System-1: an MCP step's arguments come from the step, never the whole input.
The skeleton binds the parameters the step states (Search query: ...,
File ID: ...,Tab: ...) as constants inmcp.args, named by the tool's input
schema when grounding has one, and calls a tool with no arguments rather than
forwarding the pipeline input when the step states none. -
System-1: a write to no named system is the pipeline's output. "Save it to
the shared folder" no longer becomes an external_call stub that raises
"target system not specified"; a named destination (HubSpot, #channel, a URL,
a tool, a UI action) still does. -
A Cloudflare app whose judge returns free-form objects (
{type: object}
with no properties, alone, in an array or nested) or an untyped field
now compiles. The judge's Zod schema typed themRecord<string, unknown>andunknown, whichstep.do'sRpc.Serializable
constraint rejects (TS2769), sotscfailed on emit (found byrote benchon deal-monitor). They are now a five-level JSON value type,
declared in a signature module only when used. DBOS-TS and Inngest
compiled before and still do. The typed-impls e2e covers the shape on
all three. -
A
tools/listor tool call that landed while the registry file was
corrupt (hand-edit typo, torn write) raised inside the provider and
FastMCP answered with no tools. Only the background watcher tolerated
a corrupt file. Every read path now logs the parse error and keeps
serving the previous entries, and the next read retries, so fixing
the file recovers the server without a restart. -
An
eval.yamlthat fails its schema goes back to the agent as a
verification error, instead of a traceback after the paid run.rote eval
works on every committed run underexamples/. -
The sample prefix fans out an MCP node one call per element, as the
emitted code does, instead of failing a correct pipeline on a list
argument. -
IR validation rejects a
loop_bodysub-node that is ahitl_gateor
markedmandatory. The loop's agent decides whether to call it. -
rote runandrote compileagree on stubs. An mcp-bound function on
the default backend is not a stub that blocks a run, and a missing MCP
endpoint ends the emitted app with oneerror:line. -
Citations treat an em dash, an en dash and a run of hyphens as one mark,
and ignore a backslash before a quote. -
The openai-api driver sends
--effortasreasoning_effortand
echoes Gemini'sthought_signature.