Petri 12e8a17 merges origin/main into PR #30's branch and pins
sandbox-driver at 07600aa, the same revision this workspace moved to in
the last merge, so the lock links one copy of sandbox-driver again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fabro's own DOT parser was left with one job after create-time compile
moved to Petri: walking a workflow's file references for the bundler and
the workflow-version store, and reading a name, a goal and two counts.
Petri's frontend parses the same language, so the parser goes and a small
crate reads the graph through Petri's.
`fabro-dot` is that crate: `WorkflowGraph::parse` over
`petri_frontend_attractor::dot` and its semantic model (defaults applied,
subgraphs flattened, chains expanded), `references(position)` as the one
walker over the static-reference vocabulary (each reference with its node
and position, file references checked to be template-free), and
`normalize_for_graphviz`, the re-emit of Fabro DOT with dotted attribute
keys quoted, which the SVG render needs. It sits beside `fabro-petri`
rather than inside it because `fabro-petri` depends on `fabro-workflow`,
which depends on `fabro-workflow-version`: the version store cannot reach
`fabro-petri` without a cycle, and the bundler should not pull the engine
in to read a graph.
Deleted: `fabro-graphviz`'s lexer, grammar, AST, semantic pass and
`parse_ast` (1,829 lines, plus the `nom` dependency); the DOT model in
`fabro-types::graph` (`Graph`, `Node`, `Edge`, `AttrValue`,
`shape_to_handler_type`), with only `ReferenceKind` kept, moved to
`fabro_types::reference`; `fabro-template`'s `visit_graph_references` and
the `GraphReference`/`GraphPosition` types, with the template-syntax rule
(`validate_static_reference`) kept there; the pull-request body's DOT
fallback summary, which was unreachable because the DOT source only
travels with the run spec whose display graph the summary already reads.
`fabro-graphviz` is now the render alone, over `fabro-dot`.
Parity: the old and new walkers were run over every `.fabro` and `.dot`
file in the repository (118) before the deletion. Every reference set is
identical. Five files differ in what Petri reads more correctly: a
backslash before a newline inside a quoted string is a line continuation
(four files, inline prompt text only), and a node named only by an edge
counts as a node (`test/edge_only_node.fabro`, 3 nodes rather than 2, so
the `fabro validate` snapshot moves). The checked-in bundles' shapes and
references are pinned by a snapshot in `fabro-dot`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each edge was checked with rg over the crate's sources outside its
test paths. fabro-cli keeps git2, regex, ulid, and shlex as
dev-dependencies for its integration tests. fabro-automation keeps
chrono, tokio, and tracing: its migrations compile into the crate
through #[path]. petri_testkit was already optional behind fabro-petri's
test-support feature and dual-listed as a dev-dependency. The workspace
loses the agent-client-protocol and AWS entries no crate references;
jsonschema stays for the fabro-api and fabro-tool tests. Cargo.lock
was refreshed by a plain build and only loses entries.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Move the eight sandbox-driver pins from 64c14b8 to 07600aa, which is
lithoscomputer/sandbox-driver#23 (merged as 996e8a0). It absorbs one
commit: an idle Host sentinel now also reaps its own process group
when `kill -0` on its owning provider pid fails, alongside the fence
check. A Fabro worker that is killed rather than stopped no longer
leaves an idle sentinel behind for every exec it ran. The in-command
watcher still watches only the fence, so a running workload survives
its owner and a restarted provider can still fence it.
The change is internal to sandbox-driver-host; no fabro code moves.
Only the eight sandbox-driver sources change in the lockfile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Petri accepts Fabro's platform-only environment keys silently, so a
catalog environment no longer warns on every admit. Only the Petri
source lines move in the lockfile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The merged Petri main carries the Fabro frontend's layered environments,
the launch's environment selection and the MCP catalog variable this
branch relies on, over the Pebble and lithos-llm pins Fabro's main holds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Petri 1fef017 pins the same lithos-llm and Pebble revisions Fabro's main
moved to in #883, so the workspace links one copy of each again. The
lockfile drops the second lithos-llm and pebble-agent/pebble-coding-agent
entries the merge carried while the two pins disagreed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Move the lithos-llm pin from 55add459 to 43a42ac28e9d9bcf40a91abc02be4f12ca274ebb,
and the three Pebble pins from a39f43e to 67c9f48, Pebble `main`, which pins
that same lithos-llm revision so Cargo holds one lithos-llm crate. lithos-llm
`main` (ca19fac) is one commit further; that commit touches only its nightly
workflow, so this pin stays on the revision Pebble unifies with.
The `openai`, `anthropic`, `gemini`, and `openai-compatible` features are
gone upstream; each expanded to `runtime`, which `bedrock` implies, so the
four names leave the fabro-llm feature list. Every other manifest already
names `runtime`.
The catalog schema now names one adapter and many codecs per provider.
`adapter` defaults to `http`, `codecs = [...]` replaces `codec` and defaults
to `["openai-chat"]`, and the loader rejects the old `codec` key and the four
protocol-named adapter ids. Every inline catalog in tests and docs moves to
the new shape: the `openai-compatible` + `openai-chat` pair is dropped as the
default, `adapter = "openai"` + `codec = "openai-responses"` becomes
`codecs = ["openai-responses"]`, and the one test that swaps in a custom
adapter id now adds the line instead of replacing one. The settings
reference, the API schema's `Provider.adapter` description, and the SDK page
describe the new fields; the three `docs/superpowers/plans/` files that show
the old shape are dated, unchecked historical plans and are left as they are.
The implied agent profile for an operator provider that declares none used
to read the removed protocol adapter ids; it now reads the provider's first
codec (Anthropic Messages and Gemini map to their harnesses, the `bedrock`
adapter to Anthropic, everything else to OpenAI), with a test for the codec
path.
Absorbing the rest of the range: OpenRouter and Fireworks now ship enabled,
so the two fabro-llm tests that used OpenRouter as the disabled fixture use
`bedrock-openai`, and the docs and comments that said the two ship disabled
are corrected. The built-in catalog grew past 100 enabled model rows
(Vercel, TypeSafe, and the enabled OpenRouter and Fireworks rosters), so the
pagination shape test walks `page[offset]` to the last page instead of
assuming one page fits.
`cargo update -p` on the four crates also re-resolved a few already-locked
edges to match the lithos-llm lockfile: `windows-sys` 0.61.2/0.60.2 ->
0.59.0 under dirs-sys, errno, nu-ansi-term, quinn-udp, rustix,
rustls-platform-verifier, tempfile, terminal_size, and winapi-util;
`windows-core` 0.61.2 -> 0.62.2 under iana-time-zone; `errno` 0.2.8 ->
0.3.14 under signal-hook-registry; and `indexmap` 2.13.0 as a new public
dependency of lithos-llm. No package version was added or removed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Petri main now carries the fork entry point, the interrupt control, the
incremental replay and the records the views asked for. Only the Petri
source lines move in the lockfile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Petri c216cf2 adds `events::RunReplay`, a run's replay kept between reads
that folds only the records past the ones it consumed, and
`RunLogs::read_from`, a log read from a seq with a default over `read`.
Nothing Fabro builds changes at this pin; the projector's cache lands next.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
No store sits behind `[server.slatedb]` any more: the section leaves the
settings layer, the resolved server settings, the defaults, the API schema,
the TypeScript client, the install wizard and the docs, and `fabro install`
probes the bucket for the `artifacts/` prefix alone. A settings file that
still carries the section is rewritten once at startup by a temporary
migration that removes it with a backup beside the file.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Petri a5906f6 records where each scope's sandbox ran (`scope.acquired`,
`scope.failed`) and how its lease was released (`scope.released`). The
projection folds the root invocation's records into `Run.sandbox`:
`initializing` from `run.started`, `ready` with the `RunSandboxInstance`
(the provider, Petri's `host` as Fabro's `local`, the provider's id, the
image and snapshot, the working directory) from `scope.acquired`, `failed`
from `scope.failed`; the retention outcome is kept in the fold state, since
the view has no field for it. Ask Fabro reconnect and `sandbox cp`,
`preview` and `ssh` reach the run's sandbox again.
A local reconnect designates the recorded working directory again when the
host provider does not know the id: the provider mints a registry-only id
for a workspace path too long for a path-derived one, and that registry
belongs to the run's worker. The stream listing redacts its items the way
the attached stream does, so a client that pages after a stream sees the
same items.
Pins move to Petri a5906f6 (run format 6, engine log v11, event contract
4). The attach stream snapshot is re-recorded with the new record and a
filter for the host provider's minted ids; `sandbox cp` reads an upload
back through the run's workspace, which is no longer the target folder.
Server scenario tests prove the projected instance on the host and Docker
providers.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Petri's `scope_acquired` hook point and `SandboxOptions::lost_sandbox`,
which the sandbox checkpoints and their recovery build on.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Petri's `fabro-integration-p1` branch at 83345a8 adds
`Runtime::check_source`, the in-memory check entry point over a
`frontend::FileSource`, and makes `[workflow] engine` a known
`workflow.toml` key in its Fabro frontend, refusing any other unknown
`[workflow]` key with `unsupported.workflow_toml.key`.
Every `petri_*` entry moves from a0d2ceb to 83345a8. The lockfile changes
only the source line of the fifteen Petri packages; no other crate moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fabro runs its workflows on Petri. The six Petri packages and the testkit
are pinned by revision in the workspace manifest under `petri_*` keys, and
`fabro-petri` is the one crate that depends on them. The crate's tests run
the `hello` bundle in memory on the stub registry and a command-only
workflow on the host sandbox; both skip without the sandbox-driver host
plugin, and the sandbox-plugins CI job requires it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble main a39f43e26effdf99635eaf343f095c17157c9c93 (pebble #22) carries
an assistant turn's usage as Usage in the session record and moves the
record format to version 5. CodingRuntime::from_record refuses a record
in another format with UnsupportedRecord { version, supported } before
it reads the route. Fabro persists those records in SQLite for Ask Fabro
resume, and old runs get no migration, so a record written by an older
build is read back as stored and refused on the next turn.
Two tests pin that down. The store reads pebble's own version 4 fixture
back through get without a parse error and reports it unsupported. A
resumed Ask Fabro session whose stored record declares the previous
format fails its next turn with the agent_error code and the message
"session record format version 4 is not supported (this build requires
5)", runs no turn, and leaves the stored record in place.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Re-pin lithos-llm to 55add4596b861a0623d00c3a54aa5c147c8d504b and
pebble to c91810fe51aece80359b9cd8efea971af0c46925, where token usage
and cost travel together as Usage { tokens: TokenCounts, cost:
Option<Cost> }. Fabro now carries that one type everywhere it used to
carry BilledTokenCounts, BilledModelUsage, UsdMicros, or a token count
beside a cost_usd_micros.
fabro-types: billing.rs is usage.rs with ModelRef, ModelUsage { model,
usage }, sum_usage, and usage_is_empty; billing_rollup.rs is
usage_rollup.rs with ProjectionUsageStage, ProjectionUsageByModel,
ProjectionUsageRollup, and usage_rollup_from_projection. Every usage
field is named usage: StageProjection.usage and usage_by_model,
Outcome<Option<ModelUsage>>, stage.completed and stage.failed usage and
usage_by_model, prompt.completed usage, run.completed and run.failed
usage (total_usd_micros is gone), Conclusion.usage, StageSummary.usage,
Run.usage. RunSize buckets by Cost.
fabro-workflow: model_usage_from_llm prices tokens from the catalog with
a Catalog cost source, with_reported_cost keeps a provider cost, and the
pebble handler's stage_usage groups pebble's accounts by model and sums
rows with Usage::saturating_add, so a total has a cost only when every
priced part was priced. The store fold's live usage is the agent's
usage plus its descendants'.
API: the OpenAPI spec deletes BilledTokenCounts, BilledModelUsage,
CompletionUsage, CompletionCost, TokenUsage, and RunBillingSummary,
adds TokenCounts, Cost, Usage, and ModelUsage, and renames every
billing schema, property, tag, path, and operation to usage. fabro-api
reuses lithos-llm's and fabro-types' types through with_replacement,
with a round-trip test per replacement.
Old stored runs get no migration: their pebble events in the old shape
read back with zero usage, and their rebuilt projections lose agent
usage.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moves the eight sandbox-driver crates from ddb32e1 to 64c14b8, which
brings sandbox-driver PR #22 (Daytona output resync): the Daytona
plugin's encoded-exec decoder no longer fails a command on a torn frame.
It discards through the next newline, counts the loss in the new
`sandbox_driver::OutputLoss { dropped_frames, dropped_bytes }`, and
carries it as `ExecStreamingResult::output_loss` (serde default) across
the plugin wire. A loss also sets `truncated` on both captures, so
`into_complete()` refuses while `run_streaming` completes. PR #21
(supervisor generations) was already on main under the previous pin.
Nothing in fabro needed a source change for the bump. The lockfile
changes only the eight driver source lines; the unrelated windows-sys,
windows-core, and errno flips `cargo update` proposed are not taken, and
`cargo metadata --locked` accepts the result.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble #18 adds tool-result and transcript rendering options to
pebble-cli-core (RenderOptions, Renderer::options, and
session::run_prompt_with). Pebble #19 keeps a paired human's hold across
a route failover; no embedder change is needed for it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The pin moves from the head of the section-4-driver-items branch (a92c0db6,
since merged as #20) to main, which adds #21: the protocol crate's
PluginSupervisor gains numbered generations, a health probe before a
generation serves, and a refusal of a replacement that reports another
resource namespace. Petri pins the same revision, so the two runners share
one sandbox-driver.
cargo build --workspace and cargo nextest run -p fabro-sandbox -p
fabro-workflow pass (1502 tests, 48 skipped).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvCtm3CHs5TFbQqUWtkoMX
Pebble now owns the trait an application hands its MCP support to reach a
port inside the environment. SandboxPortRoutes implements PortRoutes over
the run sandbox's preview-URL facet: a missing facet is Unsupported, a
driver failure is Failed with the driver error as its source. The mcp
feature no longer pins sandbox-driver, so fabro's sandbox-driver pin moves
on its own from here.
The new pebble rev also puts the summary call's usage and cost on
CompactionCompleted, which the CLI progress test literal names.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble's RouteFailover event now describes the route that failed and how
the new route carried the prompt on. Record the continuation on fabro's
agent.failover event as an optional string (replay_prompt or
continue_turn); events written before it existed, and one-shot prompt
stages that walk the plan themselves, read as absent. The failed route's
usage, cost, and timing are not mirrored: the stage's totals already
include them through the prompt report, and no fabro run event carries
per-route usage yet.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Same tree as the development pin e494e8e; only the three pebble source
lines in the lockfile move.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble's mcp-embedder-events branch gained a commit that waits for an
HTTP-placed MCP server to start answering, up to its startup timeout,
before the handshake. No fabro types changed. No fabro test runs a
pebble agent against an HTTP MCP server nothing listens on, and none
pins the old handshake text, so no fixture needs a shorter timeout.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble's McpServerReady and McpServerFailed events now carry startup_ms.
The workflow event sink destructured both variants by name, so it stops
listing every field. The pin is temporary: it moves to pebble main once
lithoscomputer/pebble merges the branch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble main `222d17f` merges the sandbox-driver re-pin fabro was
pointing at by branch commit. Same tree as `9ec23d0`, so this is a
lockfile-only change.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Move supplied-content packaging into fabro-manifest beside the checkout
collector, and narrow the injected seam to a packager that returns the
dependency-ordered closure so ClientBackend registers versions with the
client it already owns.
Validate the tool input once through a ValidatedWorkflowVersionCreate
newtype, matching the other tools, instead of re-validating at three
layers. Reuse the fabro-types unique-map deserializer and the shared
"not available" error helper, derive budget messages from the limit
constants, and render the tool result through the shared summary+JSON
path used by sibling tools.
Share one extension dispatch between WorkflowLocation::resolve and
from_exact_path, compute the bundler's normalized reference once, key
path-collision checks by a Cow so the canonical exact check no longer
allocates, and log the full packaging error chain before returning the
curated tool message. Replace the hand-rolled axum test server with
httpmock and declare the new unicode dependencies at the workspace.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Main merged the sandbox-driver adoption (#849) in a later form than this
branch was stacked on: the driver's own exec types replace fabro-sandbox's,
shell quoting moved to fabro-util, the sandbox lifecycle collapsed, and the
driver's events are stored as run events. This branch had deleted
`fabro-agent` and put the coding agent, the environment adapter, and the
steering hub on pebble.
The resolution takes main's sandbox API and re-applies pebble on top: the
`RunSandbox` `Environment` adapter moves to `pebble_environment.rs` (main's
`environment.rs` is the sandbox spec) and runs commands through `ExecSpec`
and `ExecControls`, feeding pebble's output sink from the driver's; the
driver-era `sandbox.*` names leave the known-event list, as on main, so a
stored event with that name and no driver shape is `Unknown` rather than an
error; `program_exit_code` matches pebble's non-exhaustive termination; the
Docker and Daytona smokes use main's constructor and credentials; the
remaining `fabro_agent` paths point at fabro-sandbox.
Pebble's `mcp` feature pins sandbox-driver, and the preview-url trait
objects only cross when both sides name one revision, so pebble moved to
main's `a92c0db6` (lithoscomputer/pebble#10) and fabro pins that pebble
revision until it lands on pebble main.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble's command line is a library now. `fabro exec` builds its agent as
before, with fabro's client, sandbox, MCP servers, skills, search, and
redaction, and hands it to pebble's session: the events rendered as they
happen, the answer on standard output, the summary after it, the agent shut
down for the reason the prompt ended with, and the terminal approval prompt
for tools the permission level does not allow. Fabro's own progress printer,
approval prompt, summary, and MCP report are gone. The event stream of
`--output-format json` stays on standard output. Standard output now carries
the final answer alone rather than every assistant message; `--verbose` no
longer prints tool results, since the session's renderer shows tool failures
only. The lockfile moves tempfile to the version pebble pins, and the SQLite
backup migration uses the replacement for the constructor that version
deprecates.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble's `SteeringBus` now owns the map of live sessions, the buffer for
steers that arrive between sessions, fan-out of steers and interrupts, the
close-the-door detach, and the hold that keeps a paired session open. The
hub keeps what only fabro knows: pair records, principals, stage ids, and
the run events that put bus activity on the run's stream in the order its
consumers expect. `PebbleControlHandle` is gone, since the coding agent's
control handle is a bus session natively; the ACP session joins the bus
through a small adapter and carries pebble's steering message end to end,
so a human steer keeps its author on the ACP `agent.steering.injected`
event. A pair message that evicted an older steer is now accepted and the
eviction recorded, where before it was queued and reported as refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble now folds a session tree's events into a serializable projection
with a per-prompt delta. Fabro's stage projection stays as it is: it is the
wire contract the API serves and is applied to incrementally, so pebble's
value cannot stand in for it without changing that contract. The new
parity tests replay one retained session across two stages through both
folds and pin a stage's live account to the prompt delta pebble reports,
its subagent rows to pebble's, and a stored projection to a replayed one.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Steps 4, 6, and 7 of .ai/plans/pebble-absorbs-embedder-concerns.md,
pinning pebble fc907a1 with its `search-providers` feature.
The sandbox's `Environment` adapter uses pebble's `environment::support`
for the glob grammar check, the tree-order sort of a listing, and the
capture accounting, in place of its own copies; the adapter itself stays.
The stage reads the compactions a prompt performed from the report, as a
breakdown of the usage it already billed. `web_search.rs` keeps the
vault-backed credentials and the Brave-over-Venice preference, and hands
pebble's `Brave` or `Venice` provider a fabro HTTP client; the providers
and their tests are pebble's now.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Steps 5 and 8 of .ai/plans/pebble-absorbs-embedder-concerns.md, pinning
pebble 49da137.
Agent stages and `fabro exec` ask pebble for the profile's instruction
files from the repository root down to the working directory
(`MemoryDiscovery::from_git_root`), which fabro lacked: it read the
working directory alone. Skill directories are pebble's to resolve too:
the user's skills directory, then `.fabro/skills` and `skills` under the
repository root. Prompt stages keep reading the working directory alone,
through the same discovery and loader, so `agent_memory.rs` keeps only
that call; the filename table is pebble's now.
A retained thread's export comes from `export_for_reuse`, which closes
the session and hands back an export whose cursor is already past the
close, in place of export, shutdown, and a cursor advance by hand. Ask
Fabro resumes a stored record with `resume_after`, the rule it applied
under the older name.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Step 3 of .ai/plans/pebble-absorbs-embedder-concerns.md, pinning pebble
6cdb30a with its `mcp` feature.
Pebble starts the stage's MCP servers while the agent is built, registers
their tools under `mcp__{server}__{tool}` with `ToolSource::Mcp`, and
closes them with the agent, for all three placements: a child of the run
worker over stdio, a server over HTTP (streamable or SSE), and a server
launched in the run sandbox and reached through the sandbox's preview
URL. `fabro_mcp::pebble::pebble_server` maps `McpServerSettings` onto
pebble's `McpServer`, keeping fabro's `/sse` path for sandbox-hosted SSE
servers; `RunSandbox::port_routes` hands pebble the driver's `PreviewUrls`
facet as the route to a sandbox port. The stage's event sink mirrors
`McpServerReady` and `McpServerFailed` onto the run's `agent.mcp.ready`
and `agent.mcp.failed` events, as it mirrors `RouteFailover` onto
`agent.failover`, and stores no second copy of a mirrored fact.
Deleted: `sandbox_mcp.rs`, the MCP branches of `pebble.rs` and `fabro
exec`, and fabro-mcp's client, connection manager, HTTP helpers, and SSE
transport, whose tests moved to pebble. fabro-mcp keeps the settings
re-export, the mapping, and a stdio client behind `test-support` for the
tests of fabro's own MCP server. `fabro exec` reports each server's
outcome from the agent's snapshot. The Daytona Playwright live test now
drives the sandbox-hosted server through an agent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Steps 1 and 2 of .ai/plans/pebble-absorbs-embedder-concerns.md, pinning
pebble 1a5abe4.
Files touched come from `PromptReport`: pebble computes them from every
successful write, edit, and patch across the prompt, subagents included,
so the event-fed `FileTracking` and the tracking half of
`WorkflowEventSink` go. The stage unions the reports of its prompts.
Route failover is pebble's. The stage resolves its plan from the catalog
as before and hands pebble the remaining routes through
`fallback_routes`, each with its controls and the stage's output limit.
Pebble keeps the conversation, moves it to the next route, requeues
pending steering, and continues the prompt; the stage's plan follows the
route the report says the prompt ended on, re-activates the session
there, and mirrors pebble's `RouteFailover` as the run's `agent.failover`
event with the same payload as before. `prompt_with_failover`,
`resume_agent_on_route`, and the route bookkeeping in `LiveAgent` go.
One-shot prompt stages still walk the plan themselves.
`agent.route.failover` and `agent.tool.rounds.exhausted` join the derived
event names; both variants were falling back to `agent.event`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>