The session_projection_parity module pinned fabro's stage fold to pebble's
SessionProjection while both existed; the stage view reads the fold now,
and the one usage rule has its own tests. StageProjection.agent_control
and AgentControlState go too: pebble's fold carries the interrupted and
steered facts as agent.activity, and the stage's state says whether the
stage still runs, which is what the reset on fabro's own stage events was
for. The run-detail banner reads activity plus state.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble's stream is the agent event contract. The run's own agent.mcp.ready,
agent.mcp.failed, and agent.mcp.disconnected events, which mirrored pebble's
McpServer* events, are gone with their props, the sink arms that emitted
them, and their conversion and naming entries; pebble's stored
agent.mcp.server.* events are the only record and feed the stage's fold.
The sink no longer mirrors RouteFailover onto agent.failover either: an
agent stage's moves are pebble's agent.route.failover. The event is now
prompt.failover, emitted only by a one-shot prompt stage that walks its
fallback plan itself, and its props are trimmed to the two routes, the
attempt, and the error; nothing read the rest.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
StageProjection loses todos, subagents, skills, mcp_servers, and
context_window, the types behind them, their fold arms and helpers, and
their OpenAPI schemas: every one of those facts is pebble's fold in
StageProjection.agent now. The context-window endpoint reads the fold's
snapshot, whose event_seq is the agent's own sequence. The parity module
keeps its assertions on the surviving own fields, usage and model, and
checks that what the stage view reads from agent is the whole-session
fold's for the stage's events. The TypeScript client is regenerated and
its stale models removed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An agent stage that failed billed nothing: the backend returned a bare
error and the outcome built from it carried no usage. A terminal failure
now becomes the stage's failed outcome from the same fold that bills a
completed stage, with the tree's usage, the rows by model, the files it
wrote, and its active time; stage.failed carries billing and
billing_by_model and the store keeps both. Cancellation and retryable
failures still go up as the error.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One usage rule: a stage's usage is its session tree's, the root and every
subagent, live and at completion. The worker's event sink folds pebble's
SessionProjection over the events it records and the stage's billing and
files come from that fold at stage end, so the completed values are what
the run showed live. The store's live usage is the fold's tree usage, and
completion brings the catalog's price for the same tokens instead of
resetting them to the root's.
Fabro keeps catalog pricing: the root at its route, each descendant at its
own route where the catalog knows it and at the root's otherwise, a
provider-reported cost standing in where pebble has one. The rows travel
as billing_by_model on stage.completed and the stage projection, and the
billing rollup splits by_model by them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
StageProjection.agent is pebble's fold of the stage's agent events, fed
every stored agent event before the fabro-only arms run. Every existing
field and arm stays for now. The parity tests prove each old field is
derivable from the embedded fold: the tree's usage, the route as the model,
the context window without fabro's stamped seq, the root's todo list, the
subagent rows, the skills, and the MCP servers under the disconnected,
error, ready rule.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pebble's SessionProjection reads ProcessingEnd to complete a prompt and mark
the session idle, so a projection rebuilt from the run's log needs it: one
small event per prompt. The four pebble events the sink mirrored onto
fabro's own agent.failover and agent.mcp.* are now stored verbatim as well,
so the fold sees the route moves and the MCP outcomes; the mirrors stay
until every reader is on the projection.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
Resolve conflicts between the metadata-branch removal and the
sandbox-driver adoption on main:
- fabro-sandbox docker.rs, sandbox.rs, daytona/mod.rs: take main's driver
rewrite. The Sandbox trait is gone, so the PR's push_token_source
removal now applies to RunSandbox instead; drop that accessor and the
RepoCredentials::source helper that only served it.
- run_metadata.rs: keep deleted. Main's edits there were adaptations to
the driver API and the run git identity field.
- lifecycle/git.rs, finalize.rs: keep the PR's removal of metadata
snapshots and write_finalize_commit; carry main's RunSandbox,
GitRetryPolicy, git_identity, local_sandbox, and test catalog changes.
- sandbox_git.rs: take main's version and drop the shadow_sha parameter
and Fabro-Checkpoint trailer.
- git_integration.rs: remove meta_branch from the new git identity test.
- Cargo.toml: main's dependency set with fabro-dump kept as a
dev-dependency.
- checkpoints.mdx: keep both the git identity paragraph and the durable
execution state section.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The existing failover test asserts the backup continued the turn after
the primary committed a tool result. A new test exhausts a two-route
chain and checks that one agent.failover and one
agent.route.failover.stopped are stored on the work stage, the stop
after the error it reports, with the exhausted reason and the failing
route. The events catalog documents both.
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>
Pebble reports an MCP server whose connection closed mid-session once,
as McpServerDisconnected, and carries startup_ms on McpServerReady and
McpServerFailed. The workflow event sink mirrors the disconnect onto a
new agent.mcp.disconnected run event shaped like agent.mcp.failed, and
passes startup_ms through on agent.mcp.ready and agent.mcp.failed. The
raw pebble event is not stored for these, so the timing would otherwise
be dropped at the boundary.
The stage projection's McpServerStatus gains a `disconnected` kind next
to `ready` and `failed`. The fold keeps the server's tool count and
sticky invoked flag and only moves the status. The OpenAPI
McpServerStatus oneOf gains McpServerStatusDisconnected, and the
fabro-api round-trip test covers its JSON shape. A new
session_projection_parity test folds the same MCP events through
pebble's SessionProjection and fabro's stage projection and compares
them, including pebble's `disconnected`.
ToolErrorKind::Timeout needs no fabro change: the kind is stored as
pebble serializes it and never matched.
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>
Remote acquisition walked the entire depth-1 checkout and failed on any
symlink that dangled or resolved outside the root, even when the link
was nowhere near the selected workflow. Submodule-style dangling links
and links into the host are common in workflow repositories and made
--workflow-git fail where the same commit collected fine locally. The
bundler already root-checks every file it opens; the only unchecked
reads were the selected TOML (or a graph selector's sibling TOML) during
location resolution. Check those in collect_workflow_versions and drop
the O(repo) walk. walkdir stays a dev-dependency for the dump tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A run now resolves a single author and committer identity once, after its
GitHub credentials are selected and before anything can commit, and uses it
for every commit it creates. Resolution order: a complete explicit
`run.git.author`; the run's GitHub App bot account
(`<slug>[bot] <id+slug[bot]@users.noreply.github.com>`); the authenticated
user of the run's PAT; the generic `Fabro <noreply@fabro.sh>`. A partial
explicit author overlays the fields it supplies. Only the selected
credential is consulted; a failed lookup is a setup error. A standalone
installation token falls back to the generic identity with a warning.
The resolved identity is carried on `RunOptions` and `EngineServices`,
recorded as a `git.identity.resolved` event and `RunProjection.git_identity`
so resume reuses it, and exposed through the run state API. Engine
checkpoints and metadata commits read it through `RunOptions::git_author`.
Every workflow execution path receives it as `GIT_AUTHOR_NAME`,
`GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`, and `GIT_COMMITTER_EMAIL`, applied
last so it wins over inherited host variables and `[run.environment]`
entries: prepare steps, command stages, native agent shell tools, and ACP
launches. The identity is injected even without a Git origin, and the old
local `git config user.*` write is removed.
fabro-github gains `GET /user` and `/users/{slug}[bot]` lookups with mocked
tests for success, unauthorized, malformed, and transient cases. Real-Git
integration tests commit in the primary checkout, a clone, and a fresh
repository under conflicting local config, `[run.environment]`, and host
variables, and prove concurrent runs do not leak identities. CLI workflow
tests cover host script stages and ACP launch env through `fabro run`.
Docs and generated option metadata now describe the credential-derived
defaults instead of the stale `fabro`/`fabro@local` values.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A child created without a target copied the parent's full Git target,
including the sha admitted for the parent. Clone-based providers never
fall back to branch HEAD, so a child created after the parent pushed new
commits was checked out at the parent's starting commit and never saw
the work it was meant to review or continue.
Inherit the repository and branch only, so the child resolves the
branch's current remote HEAD at admission; the parent's pinned commit and
tag stay on the parent. Callers that want a pinned child pass an explicit
target. Folder and none targets are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The replaced manifest builder resolved the run's repository identity from
the workflow's run.scm settings before falling back to the checkout's
origin. The new standalone derivation always used the checkout's origin,
so a fork checkout of a workflow that names its upstream repository
silently targeted the fork and pushed there.
Read the run.scm layer from the resolved workflow.toml and project.toml
(or from the inline workflow.toml bytes) and pass it through both the CLI
and the standalone run-tool adapter. When the configured repository is
not the checkout's origin, nothing can be proven about it, so derivation
now fails with a message naming that mismatch instead of the generic
"push the commit" hint.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Standalone fabro_run_create ignored the environment's provider and always
produced a Git target or failed, so a Local environment with no explicit
target was rejected by admission and a directory without Git metadata
hard-failed, while fabro run derived a folder target and a none target
for the same inputs.
Move the CLI's provider-aware derivation into fabro-manifest as a shared
helper with a typed error, and have the standalone adapter look up the
selected environment and call it. The helper also distinguishes a failed
remote query from an unpublished commit, so an offline ls-remote no
longer reports "push the commit and try again" when the branch is
already on the origin.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The MCP tool schema for the object-form create spec was a hand-written
literal that had to be kept in step with the deny_unknown_fields struct
by hand, and only the target field had a parity test. A field added to
the struct deserialized fine but stayed invisible to clients because the
advertised schema forbade it.
Derive JsonSchema for CreateRunSpec so the field list and
additionalProperties come from the struct, keep hand-written schemas only
for the two custom-deserialized types (the workflow source union and the
run target union), and extend the parity test to validate a fully
populated spec against the advertised schema.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two inline file paths that differ only by case, or a file that is also
an ancestor directory of another, used to surface as platform-dependent
low-level I/O errors naming a private temporary directory, and the
case-only case succeeded on Linux while failing on macOS. Validate both
shapes in fabro_run_create input validation so callers get a clear
message before any staging or registration happens.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Inline workflow sources were routed through the checkout-selector
collector, which rewrites any extensionless relative path to a
.fabro/workflows/<name>/workflow.toml lookup. A supplied entrypoint such
as "review" therefore failed with "workflow was not found" even though
its bytes were in the file map.
Add a dedicated inline collector in fabro-manifest that treats the
entrypoint as an exact key, checks the file paths for filesystem
collisions before staging anything, and stages the bytes in a private
temporary root only for the duration of collection. The server adapter
now delegates to it instead of staging files itself.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Deduplicate shared-filesystem capability checks and simplify workflow-source dispatch and types. Move Git observation and local package collection onto spawn_blocking, and flush inline workflow files before collection.
Simplify validated source and input types, derive inline size-limit messages from shared constants, add target schema-parity coverage, and remove dead producer pass-through parameters.
The packager logged the full packaging error chain at WARN. That chain
embeds caller-supplied workflow and prompt source: the graph parser's
diagnostic includes the unparsed remainder and the TOML parser prints
the offending line. The logging strategy prohibits user file contents
in tracing events at every level, and this adapter runs inside
`fabro mcp` and run workers at the default filter.
Log the collector error's own path-only message at DEBUG, since a
malformed request is an expected input error, together with the
entrypoint and file count. Wrap the blocking-task join error with
`context` instead of interpolating it. A test installs a TRACE-level
subscriber around the blocking path and checks that the fixture's
source marker, which the full chain does contain, never reaches the
log.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Supplied files are staged on the host filesystem and WorkflowLocation
probes the fixed sibling name `workflow.toml` there. A request that
supplied `Workflow.toml` beside its graph therefore attached the config
on a case-insensitive host (and then failed the not-supplied check),
while the identical request on ext4 registered a version with no
config. The outcome of a content-addressed registration depended on
the server's filesystem.
After collection, every version is checked against the supplied map:
when no exact sibling `workflow.toml` was supplied, no supplied key may
alias that name under the same case and normalization rules the tool
already applies to supplied keys. A supplied sibling config still
attaches only to the graph it selects, matching checkouts, since
several graphs may share one directory.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every packaging failure collapsed into one generic message, so an LLM
caller that omitted a child workflow, referenced a prompt with the
wrong case, or exceeded the canonical size limit could not tell what to
fix. The collector's error type already separates variants whose
messages carry only paths and counts from the ones whose sources quote
supplied content.
collect_supplied_workflow_versions now returns the typed collector
error, with new variants for a referenced file missing from the
package root, a collected file the caller did not supply, and staging
I/O failures. The bundler reports missing files with their
package-relative path so the collector can recognize them. The packager
renders the full cause chain for path-only variants and stops at the
last path-only level, plus a hint, for graph, TOML, and template
failures whose diagnostics quote source.
The tool-side raw byte total remains a cheap lower bound; the canonical
limit now surfaces with its own message instead of the generic one.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ServerWorkflowVersionPackager was a pure adapter over
fabro_manifest::collect_supplied_workflow_versions that touched no
server state, yet it lived in fabro-server and was imported from there
by the standalone MCP server and the CLI run worker. fabro-manifest can
depend on fabro-tool without a cycle, so the adapter now lives beside
the collector as SuppliedWorkflowVersionPackager and fabro-server no
longer exports a non-server module for it.
The adapter also cloned every version's file map out of a closure it
already owned. CollectedWorkflowClosure::into_versions hands the
versions over by value inside the blocking task instead.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
WorkflowLocation dispatches any `.toml` path to the config loader, so a
supplied entrypoint such as `sub/run.toml` was accepted, its graph
became the version entrypoint, and the config file was registered under
its own name. Runtime only reads WorkflowVersion::config_path(), the
fixed sibling `workflow.toml`, so the version's goal, environment, and
Dockerfile settings were silently dropped on every run.
In workflow-version projection, reject a config whose collected path is
not the graph's sibling `workflow.toml`. This applies to every caller
that packages versions, including `fabro run <dir>/other.toml`, which
previously registered the config and then ignored it; failing at
packaging replaces a silent drop. Manifest bundling for the legacy run
path does not project versions and is unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
FabroWorkflowVersionCreateParams deserialized `files` through a
duplicate-rejecting map, but both production routes (rmcp Parameters
and the native LLM tool dispatch) deserialize from an already-parsed
serde_json::Value in which duplicate keys have collapsed last-wins. The
only test that exercised the guard used serde_json::from_str, the one
entry point production never uses, so the safeguard was misleading.
Remove the attribute and its byte-level test, and drop the
deserialize_unique_map export that existed only for it. The canonical
WorkflowVersion wire type keeps its own duplicate-key rejection, which
does run on the byte-level HTTP route.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
validate_workflow_source_paths ran inside workflow_files for every
collected version, including the pre-existing checkout callers behind
automation materialization and `fabro run`. A repository on a
case-sensitive filesystem whose graph legitimately references two paths
that differ only by case or Unicode normalization packaged before this
branch and would have started failing.
The check is also redundant for the supplied-content path that
motivated it: the tool request validates the full key set before
staging, and the supplied collector confines collected keys to that
set. Remove it from the collector so existing callers are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The pre-resolution containment check in the version bundler was a
no-op: ManifestPath::from_absolute happily returns a `..`-prefixed path
for locations outside the package root, so an escaping
stack.child_workflow reference reached WorkflowLocation resolution,
which probes and parses config files on the host before the real
containment check in read_package_file ran. The request still failed,
but the TOML parser's diagnostic quoted the host file.
Check that the normalized reference stays under the package root before
resolving it, and extend the supplied-workflow test to plant malformed
host files that any parser would quote.
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 `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>