rote

docs/02 · architecture

The context window as a managed resource

The context window is RAM. Observations are pages, dropping them is eviction, diffing is delta encoding, the prompt cache is L2, compaction is GC, a playbook is a cached compiled program, and site memory is the persistent store. Every one of those has a manager in an OS — Rote gives them one in a browser agent.

the memory spine

Three tiers, one gate

Each tier caches what the one below keeps forgetting. None of it re-enters the window unverified.

TIER 0built · measured

Working memory

within one run

the action ledger; evicts stale observations, diffs the live page

TIER 1replay built · distiller P2

Episodic memory

across runs of a task

verified playbooks, replayed deterministically at zero LLM tokens

TIER 2designed · P2

Semantic memory

across tasks on a site

how the portal behaves — briefs, quirks, transition models

trust gate — assertion on the way back in

Memory that might be wrong is worse than no memory. Success is page state, never the absence of an exception.

where the code lives

The four planes

Perception

Capture pages over CDP, distill them to compact interactive trees, assign stable element IDs, and send diffs instead of full page dumps.

@rote/browser · @rote/perception

Decision

Own the context layout, tag every model call by source, and skip model calls entirely when memory or replay can safely act.

@rote/agent · @rote/llm

Action

Typed browser actions with settledness detection, self-healing element resolution, per-step assertions, and a scoped repair ladder.

@rote/action · @rote/executor

Learning

Record every run losslessly, promote verified runs into replay candidates, and measure everything behind launch gates.

@rote/recorder · @rote/bench

the control loop

Replay when confident, plan when not, verify always

The loop below is the actual execution order — a hard environment gate first, memory next, the model last. Deterministic replay steps cost zero LLM tokens; every exit path ends at the verify gate. Compression shrinks a step; Rote declines to run it.

solid dot — built · hollow dot — designed (P2)

  1. fingerprinthard gate — never cross environments
  2. match playbooksemantic match + bind (P2)
  3. replay if confidentdeterministic steps cost zero LLM tokens
  4. settlezero pending requests + a quiet DOM window
  5. observedistill → stable IDs → budget
  6. diff-encodeA4 — median diff 24 chars on real pages
  7. decideplanner call over the assembled context
  8. dispatch & recordtyped action, append-only trajectory
  9. expect checkoptional per-step assertion on page state
  10. repair ladderretry → scoped repair → clean fallback
  11. verify gatefinal checks decide success — never exceptions

the perception pipeline

From 6,784 elements to a 24-character diff

Every number below is measured on the real WordPress admin fixture — 120 seeded posts, 100 rows per page — with zero variance across 15 repetitions.

capture

CDP snapshot of the live page

6,784 elements

wp-admin, 120 posts

distill

interactive tree, noise dropped

797 nodes

787 actionable

stable IDs

role + name + ancestry hash

survives re-renders

zero variance ×15 runs

render

budgeted compact markup

~22,279 tok

full page, once

diff

vs last grounded bootstrap

median 24 chars

−99.6% vs re-send

When a first or replacement observation blows past the ordinary budget, the renderer records one grounded bootstrap under a 100,000-character emergency ceiling, then returns to diffs — and fails before planning above the ceiling rather than emitting a selector-free summary.

the context assembler

One module owns the layout

The ContextAssembler is the only module allowed to order messages. Tests fail if any volatile token — a timestamp, a run id — lands above the stable line, and the runtime re-asserts prefix immutability on every call. That discipline is what makes the prefix cache-eligible: the 50 lines of cache plumbing are trivial; the discipline is not.

With eviction on, a step adds ~37 tokens instead of ~172 — the quadratic term belongs to observations, and observations don't accumulate.

Immutable prefix

~268 tok

System prompt, task, tool schemas. Nothing above this line may ever mutate — not a timestamp, not a run id. A SHA-256 of the prefix routes through prompt_cache_key.

Site brief

session-stable

Tier-2 knowledge of the portal, held stable across the session so it stays cache-eligible. Planned — P2.

Action ledger

~37 tok/step

One compact JSON action per prior step. This is what survives eviction: keep what you did, not what you saw.

Live tail

~135 tok (or a diff)

The current page only — full on bootstrap, then diffs against the last grounded observation. Prior observations are evicted.

▲ cache-stable · volatile ▼ — nothing above the line may mutate

the action plane

An action is a contract, not a hope

Before anything dispatches, the page must be settled and the target must resolve safely; after it lands, page state has to prove it.

Settledness

An action dispatches only when the page is quiet: zero pending network requests plus an unchanged DOM for a 250ms window, polled every 50ms, capped at 5s.

waitForSettled

Resolution ladder

Element targets self-heal in order — stable ID, then exact role + name, then text proximity. If no safe target resolves, the step fails closed; it never guesses.

resolveElementTarget

Closed expect DSL

Assertions come from a closed set of ten primitives over page state. Judgment steps classify into a closed enum — an out-of-enum answer is a hard error, not a shrug.

evaluateExpect

failure is a first-class path

The repair ladder

Retry

≈ free

Transient failures — a settle-and-retry with no model involvement.

Scoped repair

≈ one step

One model call over a narrow context; emits a versioned patch, never an in-place edit.

Fallback

one full run

The plain agent takes over, the run is recorded, the failure is re-learned. The fallback logs why it fired.

Errors are typed, carry the failing step and run id, and are never swallowed into a boolean. A scoped repair costs about one step; a blind restart costs the whole task — the ladder exists so it rarely gets that far.

the learning plane

A run's lifecycle: recorded, verified, promoted, replayed

The recorder is a stdio proxy that tees the tool channel unmodified — observationally invisible to both sides — while writing an append-only trajectory. Nothing is edited in place, ever; a verified run is promoted, and an exact fingerprint match replays it without a model in the loop.

.rote/runs/<runId>/
  trajectory.jsonl · manifest.json · blobs/

  1. 01 dispatchevery tools/call passes through the recorder proxy — observationally invisible to both sides
  2. 02 appendone TrajectoryEvent per call, fsync before seq advances — crash recovery can always find the last complete event
  3. 03 spillresults over 64KB go to content-addressed blobs; the trajectory stays lean
  4. 04 manifestrun id, environment fingerprint, verify outcome — the run's identity card
  5. 05 verifyfinal page-state checks decide success; a failed check can never report success
  6. 06 promotea verified run becomes a replay candidate, keyed by exact fingerprint
  7. 07 replaynext time the fingerprint matches: deterministic steps, zero LLM tokens, every expect re-checked

the monorepo

Every arrow points down

Ten packages in strict layers: everything may depend on core; core depends on nothing; CI forbids cycles and deep imports.

surface
@rote/clithe rote command — run, inspect, promote
learning
@rote/recorderlossless crash-safe proxy
@rote/benchmatrices, gates, bootstrap CIs
decision
@rote/agentobserve → plan → act loop
action
@rote/executorverified playbook replay
@rote/actionsettledness + resolution
perception
@rote/browserCDP + fixture backends
@rote/perceptiondistill, stable IDs, diffs
boundary
@rote/llmsource-tagged provider client
spine
@rote/corezod schemas, pure logic, zero I/O

the type spine

Zod first, types derived

Schemas are the single source of truth — every type is z.infer'd, never hand-duplicated. Five of them carry the design.

StableNodeIdrole + accessible name + ancestry content hash — the same control keeps the same id across re-renders
Observationfull | diff | summary — a page is sent whole exactly once, then as deltas
Actionnavigate | click | fill | select | extract | done | fail — a small closed verb set, not 50 overlapping tools
StepClassreplay | speculated | grounded-routine | frontier | recovery — every step knows why it ran
Expectten closed assertion primitives over page state — the trust gate's vocabulary