diff --git a/AGENTS.md b/AGENTS.md
index a62c665b2..61b329f67 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -109,7 +109,7 @@ Fabro is an AI-powered workflow orchestration platform. Workflows are defined as
### Rust crates (`lib/crates/`)
- **fabro-cli** — CLI entry point. Commands: `run`, `exec`, `serve`, `validate`, `parse`, `cp`, `model`, `doctor`, `install`, `ps`, `system prune`
-- **fabro-workflow** — Core workflow engine. Parses Graphviz graphs, runs stages, manages checkpoints/resume, hooks, retros, and human-in-the-loop interactions
+- **fabro-workflow** — Core workflow engine. Parses Graphviz graphs, runs stages, manages checkpoints/resume, hooks, and human-in-the-loop interactions
- **fabro-agent** — AI coding agent with tool use (Bash, Read, Write, Edit, Glob, Grep, WebFetch). `Sandbox` trait abstracts execution environments
- **fabro-sandbox** — Local, Docker, and Daytona sandbox providers. Docker is the default runtime provider and creates clone-based `/workspace` containers through the operator's Docker daemon; Daytona uses the same GitHub-only clone-source contract. Docker daemon access is host-root-equivalent and assumes trusted callers/payloads.
- **fabro-server** — Axum HTTP server. Routes for runs, sessions, models, completions, usage. SSE event streaming. Demo mode via header
@@ -180,7 +180,6 @@ Never run `cargo insta accept` without first checking what's pending — it acce
## Testing workflows
- `fabro run ` — run a workflow by name (resolves `.fabro/workflows//workflow.toml`), e.g. `fabro run repl`
-- Use `--no-retro` to skip the retro step and finish faster
- `#[e2e_test(twin, live("VAR"))]` — dual-mode test that runs against twin-openai or real API. `#[e2e_test(twin)]` for twin-only tests (e.g., scripted failures). `#[e2e_test(live("VAR"))]` for live-only tests requiring secrets. `#[e2e_test()]` for sandbox tests with no API deps. Behavior is controlled by `FABRO_TEST_MODE` (`live`, `strict`; default is `twin`), and `cargo nextest run --profile e2e ...` implies `strict`. Use `fabro_test::e2e_openai!()` in twin/dual-mode tests to get `(base_url, api_key)`.
- Local test HTTP clients must use `.no_proxy()`. Prefer shared helpers like `fabro_test::test_http_client()` or crate-local equivalents instead of `reqwest::Client::new()`, bare `Client::builder().build()`, or `reqwest::get(...)`.
- This is not cosmetic: macOS proxy discovery adds hidden startup overhead to repeated localhost reqwest clients and can surface as misleading nextest timeouts.
diff --git a/Cargo.lock b/Cargo.lock
index f9c7ee93c..f17264c7a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1675,7 +1675,6 @@ dependencies = [
"fabro-oauth",
"fabro-proc",
"fabro-redact",
- "fabro-retro",
"fabro-sandbox",
"fabro-server",
"fabro-static",
@@ -2078,25 +2077,6 @@ dependencies = [
"url",
]
-[[package]]
-name = "fabro-retro"
-version = "0.228.0-nightly.0"
-dependencies = [
- "anyhow",
- "chrono",
- "fabro-agent",
- "fabro-dump",
- "fabro-llm",
- "fabro-store",
- "fabro-types",
- "fabro-util",
- "serde",
- "serde_json",
- "tempfile",
- "tokio",
- "tracing",
-]
-
[[package]]
name = "fabro-sandbox"
version = "0.228.0-nightly.0"
@@ -2166,7 +2146,6 @@ dependencies = [
"fabro-model",
"fabro-proc",
"fabro-redact",
- "fabro-retro",
"fabro-sandbox",
"fabro-slack",
"fabro-spa",
@@ -2443,7 +2422,6 @@ dependencies = [
"fabro-mcp",
"fabro-model",
"fabro-redact",
- "fabro-retro",
"fabro-sandbox",
"fabro-static",
"fabro-store",
diff --git a/README.md b/README.md
index 0a3caeec9..7cc53e30a 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ Then run `fabro server start` to finish setup in your browser. The server opens
- **Run agents 24/7** — Fabro's API server queues and executes runs continuously. Close your laptop — workflows keep running and results are waiting when you return.
- **Scale infinitely** — Move execution off your laptop and into cloud sandboxes. Run as many concurrent workflows as your infrastructure allows.
- **Guarantee code quality** — Layer deterministic verifications — test suites, linters, type checkers, LLM-as-judge — into your workflow graph. Failures trigger fix loops automatically.
-- **Achieve compounding engineering** — Automatic retrospectives after every run feed a continuous improvement loop. Your workflows get better over time, not just your code.
+- **Inspect every run** — Query durable event streams, checkpoints, conclusions, and stage outputs to understand what happened and improve the workflow.
- **Specify in natural language** — Define requirements as natural-language specs and let Fabro generate — and regenerate — implementations that conform to them.
---
@@ -56,7 +56,7 @@ Then run `fabro server start` to finish setup in your browser. The server opens
| ☁️ | Cloud sandboxes | Run agents in isolated Daytona cloud VMs with snapshot-based setup, network controls, and automatic cleanup |
| 🔌 | SSH access and preview links | Shell into running sandboxes with `fabro sandbox ssh` and expose ports with `fabro sandbox preview` for live debugging |
| 🌲 | Git checkpointing | Every stage commits code changes and execution metadata to Git branches. Resume, revert, or trace any change |
-| 📊 | Automatic retros | Each run generates a retrospective with cost, duration, files touched, and an LLM-written narrative |
+| 📊 | Run observability | Durable events, checkpoints, conclusions, and stage outputs make every run inspectable and exportable |
| ⚡ | Comprehensive API | REST API with SSE event streaming and a React web UI. Run workflows programmatically or as a service |
| 🦀 | Single binary, no runtime | One compiled Rust executable with zero dependencies. No Python, no Node, no Docker required |
| ⚖️ | Open source (MIT) | Full source code, no vendor lock-in. Self-host, fork, or extend to fit your workflow |
@@ -104,7 +104,7 @@ Fabro ships with [comprehensive documentation](https://docs.fabro.sh) covering e
- [**Getting Started**](https://docs.fabro.sh/getting-started/introduction) -- Installation, first workflow, and why Fabro exists
- [**Defining Workflows**](https://docs.fabro.sh/workflows/stages-and-nodes) -- Node types, transitions, variables, stylesheets, and human gates
-- [**Executing Workflows**](https://docs.fabro.sh/execution/run-configuration) -- Run configuration, sandboxes, checkpoints, retros, and failure handling
+- [**Executing Workflows**](https://docs.fabro.sh/execution/run-configuration) -- Run configuration, sandboxes, checkpoints, observability, and failure handling
- [**Tutorials**](https://docs.fabro.sh/tutorials/hello-world) -- Step-by-step guides from hello world to parallel multi-model ensembles
- [**API Reference**](https://docs.fabro.sh/api-reference/overview) -- Full OpenAPI spec with authentication, SSE events, and client SDKs
diff --git a/apps/fabro-web/app/routes/start.tsx b/apps/fabro-web/app/routes/start.tsx
index cb08ba57f..aba31a025 100644
--- a/apps/fabro-web/app/routes/start.tsx
+++ b/apps/fabro-web/app/routes/start.tsx
@@ -48,7 +48,6 @@ export default function Start() {
const systemInfo = useSystemInfo();
const features = systemInfo.data?.features ?? {
session_sandboxes: false,
- retros: false,
};
const [prompt, setPrompt] = useState("");
const [project, setProject] = useState(projects[0]);
diff --git a/apps/fabro-web/app/routes/workflow-detail.tsx b/apps/fabro-web/app/routes/workflow-detail.tsx
index 4f36d5d19..df5959421 100644
--- a/apps/fabro-web/app/routes/workflow-detail.tsx
+++ b/apps/fabro-web/app/routes/workflow-detail.tsx
@@ -61,7 +61,7 @@ function sampleSettings({
model: { provider: null, name: "claude-sonnet", fallbacks: [] },
git: { author: null },
prepare: { commands: prepareCommands, timeout_ms: 120_000 },
- execution: { mode: "normal", approval: "prompt", retros: true },
+ execution: { mode: "normal", approval: "prompt" },
checkpoint: { exclude_globs: [] },
sandbox: {
provider: "daytona",
diff --git a/apps/marketing/font-comparison.html b/apps/marketing/font-comparison.html
index 3774d805a..9c770cebc 100644
--- a/apps/marketing/font-comparison.html
+++ b/apps/marketing/font-comparison.html
@@ -453,7 +453,7 @@
Verification is a first-class concept
Every workflow can include build gates, test assertions, and human approval stages. When a stage fails, Fabro routes back to the appropriate fix loop — no manual re-runs, no git stash gymnastics.
-
The retrospective engine runs after every workflow, surfacing what went wrong and why. Over time, your workflows get smarter.
+
The event stream captures every workflow, surfacing what happened and why. Over time, your workflows get easier to inspect.
@@ -498,7 +498,7 @@
Verification is a first-class concept
Every workflow can include build gates, test assertions, and human approval stages. When a stage fails, Fabro routes back to the appropriate fix loop — no manual re-runs, no git stash gymnastics.
-
The retrospective engine runs after every workflow, surfacing what went wrong and why. Over time, your workflows get smarter.
+
The event stream captures every workflow, surfacing what happened and why. Over time, your workflows get easier to inspect.
@@ -543,7 +543,7 @@
Verification is a first-class concept
Every workflow can include build gates, test assertions, and human approval stages. When a stage fails, Fabro routes back to the appropriate fix loop — no manual re-runs, no git stash gymnastics.
-
The retrospective engine runs after every workflow, surfacing what went wrong and why. Over time, your workflows get smarter.
+
The event stream captures every workflow, surfacing what happened and why. Over time, your workflows get easier to inspect.
@@ -588,7 +588,7 @@
Verification is a first-class concept
Every workflow can include build gates, test assertions, and human approval stages. When a stage fails, Fabro routes back to the appropriate fix loop — no manual re-runs, no git stash gymnastics.
-
The retrospective engine runs after every workflow, surfacing what went wrong and why. Over time, your workflows get smarter.
+
The event stream captures every workflow, surfacing what happened and why. Over time, your workflows get easier to inspect.
diff --git a/apps/marketing/public/llms.txt b/apps/marketing/public/llms.txt
index d775edc0d..79f4f8cf5 100644
--- a/apps/marketing/public/llms.txt
+++ b/apps/marketing/public/llms.txt
@@ -57,7 +57,6 @@ Fabro replaces the prompt-wait-review loop with version-controlled workflow grap
- [Environments](https://docs.fabro.sh/execution/environments): Sandbox providers for workflow execution
- [Run Configuration](https://docs.fabro.sh/execution/run-configuration): Configure runs with TOML files
- [Checkpoints](https://docs.fabro.sh/execution/checkpoints): Git-based checkpoint and resume
-- [Retros](https://docs.fabro.sh/execution/retros): Automatic retrospectives for every run
- [Observability](https://docs.fabro.sh/execution/observability): Monitor, inspect, and analyze runs
## Integrations
diff --git a/apps/marketing/src/content/blog/introducing-fabro.md b/apps/marketing/src/content/blog/introducing-fabro.md
index a95e0e32d..44e6517b5 100644
--- a/apps/marketing/src/content/blog/introducing-fabro.md
+++ b/apps/marketing/src/content/blog/introducing-fabro.md
@@ -90,4 +90,4 @@ curl -fsSL https://fabro.sh/install.sh | bash
fabro run implement
```
-Check the [roadmap](/roadmap) to see what we're building — including automatic retrospectives, a REST API server, and analytics — and join us on [Discord](/discord) to shape what comes next.
+Check the [roadmap](/roadmap) to see what we're building — including the REST API server, analytics, and web workflows — and join us on [Discord](/discord) to shape what comes next.
diff --git a/apps/marketing/src/content/roadmap/automatic-retrospectives.yaml b/apps/marketing/src/content/roadmap/automatic-retrospectives.yaml
deleted file mode 100644
index b58f9963d..000000000
--- a/apps/marketing/src/content/roadmap/automatic-retrospectives.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-title: Compounding with auto-retrospectives
-description: Structured post-run analysis with cost, duration, smoothness ratings, friction points, and LLM-generated narratives.
-status: building
-date: 2026-04-03
diff --git a/apps/marketing/src/content/roadmap/web-app.yaml b/apps/marketing/src/content/roadmap/web-app.yaml
index e3e7f7814..738ca09fb 100644
--- a/apps/marketing/src/content/roadmap/web-app.yaml
+++ b/apps/marketing/src/content/roadmap/web-app.yaml
@@ -1,4 +1,4 @@
title: Web app
-description: React dashboard for managing workflows, viewing runs, approving human gates, and browsing retrospectives.
+description: React dashboard for managing workflows, viewing runs, approving human gates, and inspecting execution traces.
status: next
date: 2026-06-03
diff --git a/apps/marketing/src/pages/index.astro b/apps/marketing/src/pages/index.astro
index ba997edba..1dd0de89c 100644
--- a/apps/marketing/src/pages/index.astro
+++ b/apps/marketing/src/pages/index.astro
@@ -283,7 +283,7 @@ const cssExample = `/* All nodes default to fast + ch
-
+
@@ -293,9 +293,9 @@ const cssExample = `/* All nodes default to fast + ch
Analytics
-
Automatic retrospectives
+
Run observability
- Each run generates a retro with cost, duration, files touched, and an LLM-written narrative. Your workflows improve over time.
+ Durable event streams, checkpoints, conclusions, and stage outputs make every run inspectable and exportable.
@@ -524,7 +524,7 @@ const cssExample = `/* All nodes default to fast + ch
Full traceability across runs — every model call, every tool invocation, every decision point.
- Query run data with SQL. Automatic retrospectives rate each run and surface friction points.
+ Query run data with SQL and inspect the full execution trail.
@@ -568,7 +568,7 @@ const cssExample = `/* All nodes default to fast + ch
-
retro
+
verify
3.3s
diff --git a/docs/internal/events-strategy.md b/docs/internal/events-strategy.md
index b2e931bfb..e2931a060 100644
--- a/docs/internal/events-strategy.md
+++ b/docs/internal/events-strategy.md
@@ -1,6 +1,6 @@
# Fabro Events Strategy
-Fabro emits structured **workflow run events** during execution for observability. Events are the durable audit trail for a run: they drive the run store, SSE streaming, CLI progress rendering, retro analysis, and optional JSONL sinks.
+Fabro emits structured **workflow run events** during execution for observability. Events are the durable audit trail for a run: they drive the run store, SSE streaming, CLI progress rendering, and optional JSONL sinks.
Events are distinct from tracing logs. Tracing is developer diagnostics; events are product-facing state transitions and activity records that other systems consume.
@@ -159,7 +159,6 @@ Check:
- CLI progress parsing
- `fabro events`
-- retro duration extraction
- store validation
- tests or fixtures that inspect event names or fields
diff --git a/docs/internal/events.md b/docs/internal/events.md
index 580de2349..ce4c37d3f 100644
--- a/docs/internal/events.md
+++ b/docs/internal/events.md
@@ -2263,65 +2263,3 @@ Emitted when the stall watchdog detects no progress.
| Property | Type | Description |
|----------|------|-------------|
| `idle_seconds` | number | Seconds since last activity |
-
----
-
-## Retro events
-
-### `retro.started`
-
-```json
-{
- "id": "...", "ts": "...", "run_id": "...",
- "event": "retro.started",
- "properties": {
- "prompt": "Analyze the workflow run data at `/tmp/retro_data/` ...",
- "provider": "anthropic",
- "model": "claude-sonnet-4-20250514"
- }
-}
-```
-
-| Property | Type | Description |
-|----------|------|-------------|
-| `prompt` | string? | Prompt sent to the retro agent |
-| `provider` | string? | LLM provider for the retro agent |
-| `model` | string? | Model used for the retro agent |
-
-### `retro.completed`
-
-```json
-{
- "id": "...", "ts": "...", "run_id": "...",
- "event": "retro.completed",
- "properties": {
- "duration_ms": 5000,
- "response": "The run was mostly smooth...",
- "retro": {"smoothness": "smooth"}
- }
-}
-```
-
-| Property | Type | Description |
-|----------|------|-------------|
-| `duration_ms` | number | Retro duration |
-| `response` | string? | Raw assistant response from the retro agent |
-| `retro` | object? | Parsed `Retro` payload |
-
-### `retro.failed`
-
-```json
-{
- "id": "...", "ts": "...", "run_id": "...",
- "event": "retro.failed",
- "properties": {
- "error": "LLM request failed",
- "duration_ms": 3000
- }
-}
-```
-
-| Property | Type | Description |
-|----------|------|-------------|
-| `error` | string | Error message |
-| `duration_ms` | number | Duration before failure |
diff --git a/docs/internal/product/business-problem.md b/docs/internal/product/business-problem.md
index f5286f4dd..094ba1ccf 100644
--- a/docs/internal/product/business-problem.md
+++ b/docs/internal/product/business-problem.md
@@ -18,7 +18,7 @@ Fabro treats the process itself as code:
- Workflow graphs define stages, branching, loops, parallelism, and human gates.
- Model stylesheets route different stages to different models and providers.
- Sandboxes and Git checkpoints make runs isolated, resumable, and inspectable.
-- Event logs, verifications, and retros create a feedback loop after execution.
+- Event logs, verifications, and run state create a feedback loop after execution.
## Product direction
diff --git a/docs/internal/product/current-state.md b/docs/internal/product/current-state.md
index b878e4178..355cb272c 100644
--- a/docs/internal/product/current-state.md
+++ b/docs/internal/product/current-state.md
@@ -19,7 +19,7 @@ Fabro currently presents as:
- Git checkpointing for resume, rewind, fork, and auditability
- structured run artifacts and event streams for observability
- verifications and insights in the broader product surface
-- retrospectives, documented as experimental
+- observability surfaces for event streams, run state, and verification data
## Current positioning
diff --git a/docs/internal/product/product-description.md b/docs/internal/product/product-description.md
index 5eb363b65..c381283d2 100644
--- a/docs/internal/product/product-description.md
+++ b/docs/internal/product/product-description.md
@@ -18,7 +18,7 @@ A workflow can combine:
- model stylesheets choose models and providers per stage
- sandboxes isolate execution from the host
- Git checkpoints make runs resumable and auditable
-- event logs, verifications, and optional retros help teams inspect outcomes
+- event logs, verifications, and run state help teams inspect outcomes
## Interfaces
diff --git a/docs/internal/product/success-metrics.md b/docs/internal/product/success-metrics.md
index 7fcba1c7f..8f702390f 100644
--- a/docs/internal/product/success-metrics.md
+++ b/docs/internal/product/success-metrics.md
@@ -7,7 +7,6 @@ For product work, measure trust and useful outcomes, not just command execution.
- CLI telemetry for command success and failure
- per-run event streams in `progress.jsonl`
- run artifacts such as `checkpoint.json`, `conclusion.json`, and verification data
-- `retro.json` when retros are enabled
- API and web data for runs, workflows, usage, verifications, and insights
## Metrics that matter most
diff --git a/docs/internal/product/technical-requirements.md b/docs/internal/product/technical-requirements.md
index abbc1cd70..b6d62b9b4 100644
--- a/docs/internal/product/technical-requirements.md
+++ b/docs/internal/product/technical-requirements.md
@@ -10,7 +10,7 @@ This note captures stable constraints that product changes should respect.
- Model routing is per-stage and provider-agnostic through stylesheets and config.
- Execution happens through sandbox providers rather than assuming direct host access.
- Git checkpointing is central to resume, rewind, fork, and auditability.
-- Runs produce structured artifacts such as `progress.jsonl`, `live.json`, `checkpoint.json`, and optional `retro.json`.
+- Runs produce structured artifacts such as `progress.jsonl`, `live.json`, `checkpoint.json`, and `conclusion.json`.
- The HTTP API is OpenAPI-based, and the web app depends on that contract.
## Operational constraints
diff --git a/docs/internal/run-directory-keys.md b/docs/internal/run-directory-keys.md
index 74eda074c..91ceeb2a8 100644
--- a/docs/internal/run-directory-keys.md
+++ b/docs/internal/run-directory-keys.md
@@ -32,9 +32,8 @@ These paths are local runtime state, not canonical event projections.
These names are still real, but they are no longer live scratch files by default:
-- Metadata branch files such as `run.json`, `start.json`, `checkpoint.json`, and `retro.json`
-- `fabro dump` exports such as `run.json`, `start.json`, `status.json`, `checkpoint.json`, `conclusion.json`, `retro.json`, `events.jsonl`, and per-node prompt/response/status/stdout/stderr files
-- Retro-agent temp uploads named `events.jsonl`, `run.json`, `graph.fabro`, `checkpoints/{seq:04}.json`, `run.log` when available, and per-stage files under `stages/{rank:03}-{node_id}@{visit}/...` inside the retro sandbox
+- Metadata branch files such as `run.json`, `start.json`, and `checkpoint.json`
+- `fabro dump` exports such as `run.json`, `start.json`, `status.json`, `checkpoint.json`, `conclusion.json`, `events.jsonl`, and per-node prompt/response/status/stdout/stderr files
## Notes
diff --git a/docs/internal/updating-web-screenshots.md b/docs/internal/updating-web-screenshots.md
index 42d5d44d5..0b311c16a 100644
--- a/docs/internal/updating-web-screenshots.md
+++ b/docs/internal/updating-web-screenshots.md
@@ -57,9 +57,7 @@ Screenshots live in `docs/public/images/web/`. Each screenshot maps to a specifi
| `run-overview.png` | `/runs/run-1` |
| `run-stages.png` | `/runs/run-1/stages/detect-drift` |
| `run-files-changed.png` | `/runs/run-1/compare` |
-| `run-retro.png` | `/runs/run-1/retro` |
| `run-usage.png` | `/runs/run-1/usage` |
-| `retros-list.png` | `/retros` |
### Verification checklist
@@ -96,8 +94,6 @@ Current placements:
| `workflow-runs.png` | `core-concepts/workflows.mdx` |
| `run-stages.png` | `execution/observability.mdx` |
| `run-usage.png` | `execution/observability.mdx` |
-| `retros-list.png` | `execution/retros.mdx` |
-| `run-retro.png` | `execution/retros.mdx` |
| `run-files-changed.png` | `human-tools/steering.mdx` |
## Cleanup
diff --git a/docs/public/agents/outputs.mdx b/docs/public/agents/outputs.mdx
index c6da1fe22..2fda1a5a8 100644
--- a/docs/public/agents/outputs.mdx
+++ b/docs/public/agents/outputs.mdx
@@ -121,7 +121,6 @@ The tracked paths are stored as `files_touched` on the stage outcome:
|---|---|
| `StageCompleted` event | Emitted with `files_touched` in the event stream and surfaced by `fabro events` / exported event streams |
| Preambles | Listed under each completed stage so downstream agents know what changed |
-| Retros | Included per-stage and aggregated across the full run |
| `status.json` | Written to the stage's logs directory after each node completes |
### How tracking works
@@ -240,6 +239,5 @@ Outputs and artifacts appear in several observability surfaces:
| `StageCompleted` event | `files_touched` list for the stage |
| `WorkflowRunCompleted` event | `artifact_count` -- total number of offloaded artifacts across the run |
| Web UI | Run stage output, stage artifacts, and downloadable artifact files |
-| [Retros](/execution/retros) | Per-stage `files_touched` and aggregate `files_touched` across all stages |
| [Preambles](/execution/context#preamble-construction) | File list and artifact pointer references for completed stages |
| Stage logs | `status.json` in each stage's run directory contains the full outcome including `files_touched` |
diff --git a/docs/public/api-reference/demo-mode.mdx b/docs/public/api-reference/demo-mode.mdx
index 380f0414b..5d3670c0a 100644
--- a/docs/public/api-reference/demo-mode.mdx
+++ b/docs/public/api-reference/demo-mode.mdx
@@ -47,7 +47,6 @@ Demo mode implements every API endpoint. Read endpoints return static data repre
| Run Internals | Stages, turns, checkpoints, context, and configuration all return static data |
| Verifications | Categories and controls with pass/fail examples |
| Insights | Saved queries and history; execute returns a canned result |
-| Retros | Retrospective data for completed runs |
| Projects | Sample projects and branches |
| Settings | Platform configuration |
diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml
index 5695ae2fa..f5dd4f27e 100644
--- a/docs/public/api-reference/fabro-api.yaml
+++ b/docs/public/api-reference/fabro-api.yaml
@@ -4781,8 +4781,6 @@ components:
type: boolean
auto_approve:
type: boolean
- no_retro:
- type: boolean
preserve_sandbox:
type: boolean
worktree_mode:
@@ -6102,13 +6100,6 @@ components:
conclusion:
type: ["object", "null"]
additionalProperties: true
- retro:
- type: ["object", "null"]
- additionalProperties: true
- retro_prompt:
- type: ["string", "null"]
- retro_response:
- type: ["string", "null"]
sandbox:
type: ["object", "null"]
additionalProperties: true
@@ -8087,14 +8078,12 @@ components:
RunExecutionSettings:
type: object
- required: [mode, approval, retros]
+ required: [mode, approval]
properties:
mode:
$ref: "#/components/schemas/RunMode"
approval:
$ref: "#/components/schemas/ApprovalMode"
- retros:
- type: boolean
RunMode:
type: string
@@ -8549,9 +8538,6 @@ components:
session_sandboxes:
type: boolean
description: Whether session sandboxes are enabled.
- retros:
- type: boolean
- description: Whether workflow retros are enabled.
SystemRunCounts:
description: Counts of known runs in the active server process.
diff --git a/docs/public/core-concepts/how-fabro-works.mdx b/docs/public/core-concepts/how-fabro-works.mdx
index de33adec5..9a7c34def 100644
--- a/docs/public/core-concepts/how-fabro-works.mdx
+++ b/docs/public/core-concepts/how-fabro-works.mdx
@@ -82,7 +82,7 @@ The sandbox is configured per-run via CLI flags (`--sandbox docker`) or the run
Every significant action — stage starts, LLM calls, tool invocations, edge selections, stage completions — is emitted as a structured event. These events power:
- The **web UI** for real-time run monitoring
-- **Retrospectives** generated automatically after each run
+- **Run summaries** built from durable events, checkpoints, conclusions, and stage outputs
- **DuckDB queries** via `fabro insights` for SQL-based analysis across runs
See [Observability](/execution/observability) for more on querying run data.
diff --git a/docs/public/core-concepts/workflows.mdx b/docs/public/core-concepts/workflows.mdx
index d1f6baa27..558334219 100644
--- a/docs/public/core-concepts/workflows.mdx
+++ b/docs/public/core-concepts/workflows.mdx
@@ -28,7 +28,7 @@ digraph MyWorkflow {
}
```
-The `goal` attribute describes what the workflow accomplishes. Fabro uses it to guide agent behavior and generate retrospectives.
+The `goal` attribute describes what the workflow accomplishes. Fabro uses it to guide agent behavior.
## Key node types
diff --git a/docs/public/docs.json b/docs/public/docs.json
index 721cf099c..3055c1b8e 100644
--- a/docs/public/docs.json
+++ b/docs/public/docs.json
@@ -55,7 +55,6 @@
"execution/checkpoints",
"execution/outcomes",
"execution/failures",
- "execution/retros",
"execution/observability",
"execution/devcontainers"
]
diff --git a/docs/public/execution/checkpoints.mdx b/docs/public/execution/checkpoints.mdx
index 321f2c74b..71ff54329 100644
--- a/docs/public/execution/checkpoints.mdx
+++ b/docs/public/execution/checkpoints.mdx
@@ -45,14 +45,13 @@ Fabro disables Git commit and tag signing for checkpoint commits created inside
The metadata branch (`fabro/meta/{run_id}`) is an orphan branch that stores structured run data using Git's object storage directly. Fabro writes it from inside the sandbox with Git plumbing commands, without checking out a metadata worktree. It is initialized at run start with:
-- **`run.json`** — Current projection snapshot: run spec, start/status records, current checkpoint, conclusion, sandbox, retro state, and other run-level metadata
+- **`run.json`** — Current projection snapshot: run spec, start/status records, current checkpoint, conclusion, sandbox, and other run-level metadata
- **`graph.fabro`** — Workflow source for the run
After each node, the metadata branch is updated with:
- **`run.json`** — Refreshed projection snapshot with the new current checkpoint
- **`stages/{rank:03}-{node_id}@{visit}/...`** — Execution-order-prefixed per-stage trace files (prompts, responses, status, diffs, command output, and tool metadata)
-- **`stages/retro/*.md`** — Retro prompt/response text when present
## What's in a checkpoint
diff --git a/docs/public/execution/observability.mdx b/docs/public/execution/observability.mdx
index 17ba46e20..875a6206d 100644
--- a/docs/public/execution/observability.mdx
+++ b/docs/public/execution/observability.mdx
@@ -3,7 +3,7 @@ title: "Observability"
description: "How to monitor, inspect, and analyze workflow runs"
---
-Fabro captures a structured event for every significant action during a workflow run. These events cover stage execution, agent tool calls, retries, routing, sandbox lifecycle, git checkpoints, retro generation, and more.
+Fabro captures a structured event for every significant action during a workflow run. These events cover stage execution, agent tool calls, retries, routing, sandbox lifecycle, git checkpoints, and more.
## Event stream
@@ -11,7 +11,7 @@ Every workflow run emits a sequence of canonical **run event envelopes** that ar
- Stored durably in the run store
- Broadcast over SSE to connected API clients
-- Stored for later analysis and retro generation
+- Stored for later analysis
- Rendered by CLI progress and log tooling
- Optionally materialized into JSONL by export/debug paths
@@ -95,7 +95,6 @@ Common categories include:
| Routing | `edge.selected`, `loop.restart`, `parallel.started` |
| Git and checkpoints | `checkpoint.completed`, `git.commit`, `git.push` |
| Setup and sandbox | `sandbox.initializing`, `sandbox.ready`, `setup.started` |
-| Retro | `retro.started`, `retro.completed`, `retro.failed` |
## Sub-agent visibility
@@ -135,4 +134,4 @@ Post-run analysis surfaces include:
| `fabro inspect
` | Current durable run state, including run/start/checkpoint/conclusion records |
| `fabro dump --output ` | Exported `events.jsonl` plus reconstructed JSON and node files |
-See [retros](/execution/retros), [stages](/api-reference/run-internals/list-run-stages), and [turns](/api-reference/run-internals/list-stage-turns) for higher-level analysis views built on top of this event stream.
+See [stages](/api-reference/run-internals/list-run-stages) and [turns](/api-reference/run-internals/list-stage-turns) for higher-level analysis views built on top of this event stream.
diff --git a/docs/public/execution/retros.mdx b/docs/public/execution/retros.mdx
deleted file mode 100644
index 9c8c65019..000000000
--- a/docs/public/execution/retros.mdx
+++ /dev/null
@@ -1,146 +0,0 @@
----
-title: "Retros"
-description: "Automatic retrospectives that analyze every workflow run"
----
-
-
-**Experimental feature.** Retros are disabled by default. Enable them by setting `retros = true` under `[run.execution]` in your project or workflow config.
-
-
-After every workflow run, Fabro can generate a **retro** — a structured retrospective that captures what happened, what went well, and what didn't. Retros combine deterministic metrics extracted from the run's checkpoint with a qualitative narrative produced by an LLM agent that analyzes the full event stream.
-
-The goal is continuous improvement. Retros give you a searchable history of how your workflows perform over time, surface friction patterns that would otherwise go unnoticed, and identify follow-up work before it falls through the cracks.
-
-
-
-
-
-## What's in a retro
-
-A retro has two layers: **quantitative stats** derived cheaply from checkpoint data, and an **agent-generated narrative** that interprets the run holistically.
-
-### Quantitative layer
-
-The quantitative layer is extracted directly from the [checkpoint](/execution/checkpoints) and event stream — no LLM calls required:
-
-| Field | Description |
-|---|---|
-| **Per-stage breakdown** | Duration, retry count, cost, files touched, status, and failure reason for each stage |
-| **Aggregate stats** | Total duration, total cost, total retries, all files touched, stages completed vs. failed |
-
-### Narrative layer
-
-An LLM agent reads the run's full event stream and produces a structured analysis:
-
-| Field | Description |
-|---|---|
-| **Smoothness** | Overall rating on a 5-point scale (see below) |
-| **Intent** | What the run was trying to accomplish |
-| **Outcome** | What actually happened |
-| **Learnings** | What was discovered about the repo, code, workflow, or tools |
-| **Friction points** | Where things got stuck and why |
-| **Open items** | Follow-up work, tech debt, test gaps, or investigations identified |
-
-The agent has tool access to grep and read the event stream, so it can inspect actual tool call patterns, error messages, and approach pivots — not just pass/fail signals.
-
-## Smoothness ratings
-
-Every retro includes a smoothness rating that grades the overall quality of the run's execution:
-
-| Rating | Meaning |
-|---|---|
-| **Effortless** | Goal achieved on the first try. No retries, no wrong approaches. Agent moved efficiently from start to finish. |
-| **Smooth** | Goal achieved with minor hiccups — 1–2 retries or a brief wrong approach quickly corrected. No human intervention needed. |
-| **Bumpy** | Goal achieved but with notable friction: multiple retries, at least one significant wrong approach, or substantial time on dead ends. |
-| **Struggled** | Goal achieved only with difficulty: many retries, major approach changes, human intervention, or partial failures requiring recovery. |
-| **Failed** | Run did not achieve its stated goal. Some stages may have completed, but the overall intent was not fulfilled. |
-
-The rating considers the full context visible in agent events — tool call patterns, error recovery sequences, approach pivots — not just stage pass/fail counts.
-
-## Learnings, friction points, and open items
-
-### Learnings
-
-Learnings capture what was discovered during the run, categorized by type:
-
-| Category | Examples |
-|---|---|
-| `repo` | Repository structure, build system quirks, CI configuration |
-| `code` | Bug root causes, module boundaries, API contracts |
-| `workflow` | Node ordering issues, missing stages, prompt improvements |
-| `tool` | Tool limitations, MCP server behavior, command output parsing |
-
-### Friction points
-
-Friction points identify where the run got stuck and what caused the slowdown:
-
-| Kind | Description |
-|---|---|
-| `retry` | A stage needed multiple attempts |
-| `timeout` | A stage or tool call hit a time limit |
-| `wrong_approach` | The agent pursued a dead end before pivoting |
-| `tool_failure` | A tool or command failed unexpectedly |
-| `ambiguity` | Unclear requirements or conflicting signals caused confusion |
-
-Each friction point can optionally reference the `stage_id` where it occurred.
-
-### Open items
-
-Open items capture follow-up work identified during the run:
-
-| Kind | Description |
-|---|---|
-| `tech_debt` | Code quality issues worth addressing later |
-| `follow_up` | Work that's related but out of scope for this run |
-| `investigation` | Unknowns that need further research |
-| `test_gap` | Missing test coverage discovered during the run |
-
-## How retros are generated
-
-Retro generation happens in two phases after a run completes:
-
-1. **Derive** — Fabro extracts stage durations from durable run events and builds a retro from the checkpoint data. This is deterministic, fast, and produces the quantitative layer.
-
-2. **Narrate** — An LLM agent session analyzes the run data. The agent receives `events.jsonl`, `run.json`, `graph.fabro`, checkpoint snapshots under `checkpoints/{seq:04}.json`, `run.log` when available, and per-stage files under `stages/{rank:03}-{node_id}@{visit}/...` inside its sandbox so it can grep and read the event stream, run snapshot, workflow source, checkpoints, logs, and full stage payloads. The narrative fields are merged back into durable retro state.
-
-Both phases run automatically at the end of every CLI run. The API server derives the quantitative layer but does not currently run the narrative agent.
-
-
-
-
-
-## Accessing retros
-
-### CLI
-
-To enable retros for your project, set `retros = true` under `[run.execution]` in your `.fabro/project.toml`:
-
-```toml title=".fabro/project.toml"
-_version = 1
-
-[run.execution]
-retros = true
-```
-
-To skip retro generation for a single run when retros are enabled, pass `--no-retro`:
-
-```bash
-fabro run workflow.fabro --no-retro
-```
-
-Retros can also be enabled server-wide in `settings.toml`:
-
-```toml title="settings.toml"
-_version = 1
-
-[run.execution]
-retros = true
-```
-
-### API
-
-Retros are also available via the REST API. See the [list retros](/api-reference/retros/list-retros) and [retrieve retro](/api-reference/retros/retrieve-retro) API reference pages.
-
-## Storage
-
-Retros are stored in durable run state. If you need files on disk, `fabro dump` materializes retro text under `stages/retro/` alongside `run.json`, stage files, and the rest of the exported run data.
diff --git a/docs/public/execution/run-configuration.mdx b/docs/public/execution/run-configuration.mdx
index 588018448..60c2cc102 100644
--- a/docs/public/execution/run-configuration.mdx
+++ b/docs/public/execution/run-configuration.mdx
@@ -27,7 +27,7 @@ goal = "Implement the login feature"
|---|---|---|
| `_version` | No (defaults to `1`) | Schema version. Must be `1` in the first pass. |
| `[workflow].graph` | No | Path to the Graphviz workflow file, relative to the TOML file's directory. Defaults to `workflow.fabro`. |
-| `[run].goal` | No | What the workflow should accomplish. Passed to agents and used in retrospectives. Can also be provided via `--goal` CLI flag or Graphviz graph `goal` attribute. |
+| `[run].goal` | No | What the workflow should accomplish. Passed to agents and available via `--goal` CLI flag or Graphviz graph `goal` attribute. |
Goal precedence: CLI `--goal` > `[run].goal` > Graphviz graph attribute.
diff --git a/docs/public/getting-started/dark-factory.mdx b/docs/public/getting-started/dark-factory.mdx
index 53a9bcf07..b078f4d81 100644
--- a/docs/public/getting-started/dark-factory.mdx
+++ b/docs/public/getting-started/dark-factory.mdx
@@ -25,7 +25,7 @@ The dark factory isn't a single tool or practice. It's a set of capabilities tha
**Checkpointed execution over black-box runs.** Git commits after every stage create an audit trail. When something goes wrong, you can inspect, revert, or fork from any point — without having watched the run live.
-**Continuous improvement over static processes.** Automatic retrospectives after every run feed a learning loop. Workflows get better over time, not just the code they produce.
+**Observability over black-box automation.** Durable event streams, checkpoints, conclusions, and stage outputs make each run inspectable after the fact. Workflows get better when teams can see what happened and adjust the graph.
## The human role in a dark factory
@@ -36,7 +36,7 @@ The dark factory doesn't eliminate engineering judgment. It redirects it:
| Writing code | Defining workflows and prompts |
| Reviewing diffs | Defining verification criteria |
| Debugging test failures | Designing fix loops |
-| Watching agent sessions | Reviewing retrospectives |
+| Watching agent sessions | Inspecting run traces |
| Manual quality checks | Tuning goal gates and evals |
The goal is to spend your time on the parts that require human judgment — what to build, how to verify it, and when something doesn't look right — while the factory handles the rest.
@@ -53,7 +53,7 @@ The goal is to spend your time on the parts that require human judgment — what
Build verification into your workflows.
-
- Automatic retrospectives for continuous improvement.
+
+ Inspect event streams, logs, and exported run state.
diff --git a/docs/public/getting-started/introduction.mdx b/docs/public/getting-started/introduction.mdx
index d8f74e30b..6e19969dc 100644
--- a/docs/public/getting-started/introduction.mdx
+++ b/docs/public/getting-started/introduction.mdx
@@ -16,7 +16,7 @@ Fabro replaces the prompt-wait-review loop with version-controlled workflow grap
- **Improve agent security** — Run agents in cloud sandboxes with full network and filesystem isolation. Keep untrusted code off your laptop and out of your production environment.
- **Run agents 24/7 at scale** — Fabro's API server queues and executes runs continuously in cloud sandboxes. Close your laptop — workflows keep running across as many concurrent runs as your infrastructure allows.
- **Guarantee code quality** — Layer deterministic verifications — test suites, linters, type checkers, LLM-as-judge — into your workflow graph. Failures trigger fix loops automatically.
-- **Achieve compounding engineering** — Automatic retrospectives after every run feed a continuous improvement loop. Your workflows get better over time, not just your code.
+- **Inspect every run** — Query durable event streams, checkpoints, conclusions, and stage outputs to understand what happened and improve the workflow.
- **Specify in natural language** — Define requirements as natural-language specs and let Fabro generate — and regenerate — implementations that conform to them.
diff --git a/docs/public/getting-started/why-fabro.mdx b/docs/public/getting-started/why-fabro.mdx
index e77605872..162ff4b8e 100644
--- a/docs/public/getting-started/why-fabro.mdx
+++ b/docs/public/getting-started/why-fabro.mdx
@@ -35,7 +35,7 @@ Fabro gives you a deterministic harness around non-deterministic AI. You define
Combine LLM-as-judge, test suites, third-party tools, and human review. Verifications act as an eval suite tailored to your organization, building confidence over time.
- Every tool call, agent turn, and shell command is captured in a unified event stream. Query run data with SQL via DuckDB and generate automatic retrospectives.
+ Every tool call, agent turn, and shell command is captured in a unified event stream. Query run data with SQL via DuckDB and inspect the full execution trail.
Licensed under MIT. Written in Rust with minimal dependencies. Runs on a single node with no databases to set up.
diff --git a/docs/public/images/web/retros-list.png b/docs/public/images/web/retros-list.png
deleted file mode 100644
index ded9ee2e8..000000000
Binary files a/docs/public/images/web/retros-list.png and /dev/null differ
diff --git a/docs/public/images/web/run-retro.png b/docs/public/images/web/run-retro.png
deleted file mode 100644
index c9dbaed3c..000000000
Binary files a/docs/public/images/web/run-retro.png and /dev/null differ
diff --git a/docs/public/reference/cli.mdx b/docs/public/reference/cli.mdx
index 95feb16e8..2729b5902 100644
--- a/docs/public/reference/cli.mdx
+++ b/docs/public/reference/cli.mdx
@@ -306,7 +306,6 @@ fabro create [OPTIONS]
| `--in-place` | Run directly in the source checkout without git checkpoints |
| `--label ` | Attach a label to this run (repeatable, format: KEY=VALUE) |
| `--model ` | Override default LLM model |
-| `--no-retro` | Skip retro generation after the run |
| `--preserve-sandbox` | Keep the sandbox alive after the run finishes (for debugging) |
| `--provider ` | Override default LLM provider |
| `--sandbox ` | Sandbox for agent tools
Values: `local`, `docker`, `daytona` |
@@ -845,7 +844,6 @@ fabro run [OPTIONS]
| `--in-place` | Run directly in the source checkout without git checkpoints |
| `--label ` | Attach a label to this run (repeatable, format: KEY=VALUE) |
| `--model ` | Override default LLM model |
-| `--no-retro` | Skip retro generation after the run |
| `--preserve-sandbox` | Keep the sandbox alive after the run finishes (for debugging) |
| `--provider ` | Override default LLM provider |
| `--sandbox ` | Sandbox for agent tools
Values: `local`, `docker`, `daytona` |
diff --git a/docs/public/reference/dot-language.mdx b/docs/public/reference/dot-language.mdx
index cebe0bac3..12436ec70 100644
--- a/docs/public/reference/dot-language.mdx
+++ b/docs/public/reference/dot-language.mdx
@@ -73,7 +73,7 @@ rankdir=LR
| Attribute | Type | Description |
|---|---|---|
-| `goal` | String | Workflow objective — guides agent behavior and retrospectives |
+| `goal` | String | Workflow objective — guides agent behavior |
| `rankdir` | Identifier | Layout direction: `LR` (left-to-right) or `TB` (top-to-bottom) |
| `model_stylesheet` | String | CSS-like rules for model assignment (see [Model Stylesheets](/workflows/stylesheets)) |
| `default_max_retries` | Integer | Default retry count for all nodes (default: 0) |
diff --git a/docs/public/reference/run-directory.mdx b/docs/public/reference/run-directory.mdx
index 39f433d2f..80431ae91 100644
--- a/docs/public/reference/run-directory.mdx
+++ b/docs/public/reference/run-directory.mdx
@@ -30,7 +30,7 @@ These paths are local runtime state and caches, not the canonical run state.
- **`runtime/`** — Local runtime files. Today this includes `runtime/server.log` for the raw per-run worker tracing log and materialized blob payloads under `runtime/blobs/`.
- **`nodes/{manager_node}_{visit}/child/`** — Nested scratch directories for manager-loop child workflows.
-Large durable values, event streams, checkpoints, diffs, conclusions, and retros are no longer projected into live scratch by default. Use `fabro events`, `fabro inspect`, the API, or `fabro dump` for those surfaces. Use `fabro logs` for the raw per-run worker tracing log when it is available.
+Large durable values, event streams, checkpoints, diffs, and conclusions are no longer projected into live scratch by default. Use `fabro events`, `fabro inspect`, the API, or `fabro dump` for those surfaces. Use `fabro logs` for the raw per-run worker tracing log when it is available.
## Reconstructed and export-only layouts
@@ -38,7 +38,6 @@ Metadata branch snapshots and `fabro dump` exports now use the same core layout:
- `run.json` for the current projection snapshot, including the current checkpoint
- `graph.fabro` for workflow source
-- `stages/retro/*.md` for retro prompt/response text
- `stages/{rank:03}-{node_id}@{visit}/...` for execution-order-prefixed per-stage prompt, response, status, diff, and command output files
`fabro dump` adds export-only history surfaces on top of that shared layout:
diff --git a/docs/public/tutorials/hello-world.mdx b/docs/public/tutorials/hello-world.mdx
index 4e913686e..1000648e2 100644
--- a/docs/public/tutorials/hello-world.mdx
+++ b/docs/public/tutorials/hello-world.mdx
@@ -41,7 +41,7 @@ fabro run docs/internal/demo/01-hello.fabro
- `shape=tab` makes this a **prompt node** — a single LLM call with no tool access. Good for generation, summarization, and classification.
- `reasoning_effort="low"` tells the model to think less. This is a simple task that doesn't need deep reasoning.
-- `graph [goal="..."]` describes the workflow's purpose. Fabro uses it in preambles and retrospectives.
+- `graph [goal="..."]` describes the workflow's purpose. Fabro uses it in preambles and agent context.
Every workflow needs exactly one `start` node (`shape=Mdiamond`) and one `exit` node (`shape=Msquare`).
diff --git a/evals/swe-bench/evaluate_daytona.py b/evals/swe-bench/evaluate_daytona.py
index 0340cc343..17606cce0 100644
--- a/evals/swe-bench/evaluate_daytona.py
+++ b/evals/swe-bench/evaluate_daytona.py
@@ -350,7 +350,6 @@ def evaluate_instance(
cmd = [
"fabro", "run", str(toml_file),
"--auto-approve",
- "--no-retro",
"--label", f"swe-eval={instance_id}",
]
diff --git a/evals/swe-bench/run_eval.py b/evals/swe-bench/run_eval.py
index d1fc3f89d..aa34dbbb0 100644
--- a/evals/swe-bench/run_eval.py
+++ b/evals/swe-bench/run_eval.py
@@ -247,7 +247,6 @@ def run_instance(
"--model", model,
"--provider", provider,
"--goal-file", str(goal_file),
- "--no-retro",
"--label", f"swe-bench={instance_id}",
]
diff --git a/lib/crates/fabro-api/tests/run_projection_round_trip.rs b/lib/crates/fabro-api/tests/run_projection_round_trip.rs
index c43c1e8bb..412d04d4d 100644
--- a/lib/crates/fabro-api/tests/run_projection_round_trip.rs
+++ b/lib/crates/fabro-api/tests/run_projection_round_trip.rs
@@ -32,9 +32,6 @@ fn run_projection_round_trips_populated_projection() {
}
]],
"conclusion": null,
- "retro": null,
- "retro_prompt": null,
- "retro_response": null,
"sandbox": null,
"final_patch": null,
"pull_request": null,
@@ -90,9 +87,6 @@ fn run_projection_round_trips_with_pending_control_unset() {
"checkpoint": null,
"checkpoints": [],
"conclusion": null,
- "retro": null,
- "retro_prompt": null,
- "retro_response": null,
"sandbox": null,
"final_patch": null,
"pull_request": null,
diff --git a/lib/crates/fabro-cli/Cargo.toml b/lib/crates/fabro-cli/Cargo.toml
index eb12c99f0..866b46a7d 100644
--- a/lib/crates/fabro-cli/Cargo.toml
+++ b/lib/crates/fabro-cli/Cargo.toml
@@ -32,7 +32,6 @@ fabro-install = { path = "../fabro-install" }
fabro-interview = { path = "../fabro-interview" }
fabro-mcp = { path = "../fabro-mcp" }
fabro-proc = { path = "../fabro-proc" }
-fabro-retro = { path = "../fabro-retro" }
fabro-sandbox = { path = "../fabro-sandbox", features = ["daytona"] }
fabro-checkpoint = { path = "../fabro-checkpoint" }
fabro-graphviz = { path = "../fabro-graphviz" }
diff --git a/lib/crates/fabro-cli/src/args.rs b/lib/crates/fabro-cli/src/args.rs
index d3f27eb35..7f359a1f6 100644
--- a/lib/crates/fabro-cli/src/args.rs
+++ b/lib/crates/fabro-cli/src/args.rs
@@ -254,10 +254,6 @@ pub(crate) struct RunArgs {
#[arg(long = "label", value_name = "KEY=VALUE")]
pub(crate) label: Vec,
- /// Skip retro generation after the run
- #[arg(long)]
- pub(crate) no_retro: bool,
-
/// Keep the sandbox alive after the run finishes (for debugging)
#[arg(long)]
pub(crate) preserve_sandbox: bool,
diff --git a/lib/crates/fabro-cli/src/commands/run/attach.rs b/lib/crates/fabro-cli/src/commands/run/attach.rs
index a192c6a1f..e2ed6e1e1 100644
--- a/lib/crates/fabro-cli/src/commands/run/attach.rs
+++ b/lib/crates/fabro-cli/src/commands/run/attach.rs
@@ -849,9 +849,6 @@ mod tests {
"checkpoint": null,
"checkpoints": [],
"conclusion": null,
- "retro": null,
- "retro_prompt": null,
- "retro_response": null,
"sandbox": null,
"final_patch": null,
"pull_request": null,
diff --git a/lib/crates/fabro-cli/src/commands/run/events.rs b/lib/crates/fabro-cli/src/commands/run/events.rs
index 6196e6baa..e905d874a 100644
--- a/lib/crates/fabro-cli/src/commands/run/events.rs
+++ b/lib/crates/fabro-cli/src/commands/run/events.rs
@@ -775,31 +775,6 @@ fn format_event_pretty_value(envelope: &serde_json::Value, styles: &Styles) -> O
styles.red.apply_to(error),
))
}
- "retro.completed" => {
- let duration = format_duration_ms(prop_field(envelope, "duration_ms"));
- Some(format!(
- "{} {} Retro {}",
- styles.dim.apply_to(&ts),
- styles.green.apply_to("\u{2713}"),
- duration,
- ))
- }
- "retro.failed" => {
- let error = prop_str_field(envelope, "error").unwrap_or("unknown error");
- let duration = format_duration_ms(prop_field(envelope, "duration_ms"));
- Some(format!(
- "{} {} Retro {} {}",
- styles.dim.apply_to(&ts),
- styles.bold_red.apply_to("\u{2717}"),
- duration,
- styles.red.apply_to(error),
- ))
- }
- "retro.started" => Some(format!(
- "{} {} Retro",
- styles.dim.apply_to(&ts),
- styles.bold_cyan.apply_to("\u{25b6}"),
- )),
_ => None,
}
}
diff --git a/lib/crates/fabro-cli/src/commands/run/overrides.rs b/lib/crates/fabro-cli/src/commands/run/overrides.rs
index e24d88743..6e1174aae 100644
--- a/lib/crates/fabro-cli/src/commands/run/overrides.rs
+++ b/lib/crates/fabro-cli/src/commands/run/overrides.rs
@@ -57,12 +57,8 @@ fn sandbox_layer(
})
}
-fn execution_layer(
- dry_run: Option,
- auto_approve: Option,
- no_retro: Option,
-) -> Option {
- if dry_run.is_none() && auto_approve.is_none() && no_retro.is_none() {
+fn execution_layer(dry_run: Option, auto_approve: Option) -> Option {
+ if dry_run.is_none() && auto_approve.is_none() {
return None;
}
Some(RunExecutionLayer {
@@ -74,7 +70,6 @@ fn execution_layer(
ApprovalMode::Prompt
}
}),
- retros: no_retro.map(|nr| !nr),
})
}
@@ -129,11 +124,7 @@ pub(crate) fn run_args_overrides(args: &RunArgs) -> Result Option {
from_node: props.from_node.clone(),
to_node: props.to_node.clone(),
}),
- EventBody::RetroStarted(_) => Some(ProgressEvent::RetroStarted),
- EventBody::RetroCompleted(props) => Some(ProgressEvent::RetroCompleted {
- duration_ms: props.duration_ms,
- }),
- EventBody::RetroFailed(props) => Some(ProgressEvent::RetroFailed {
- duration_ms: props.duration_ms,
- }),
EventBody::MetadataSnapshotFailed(props) => Some(ProgressEvent::MetadataSnapshotFailed {
phase: props.phase.to_string(),
failure_kind: props.failure_kind.to_string(),
diff --git a/lib/crates/fabro-cli/src/commands/run/run_progress/mod.rs b/lib/crates/fabro-cli/src/commands/run/run_progress/mod.rs
index 7e7df7f2d..58398efb0 100644
--- a/lib/crates/fabro-cli/src/commands/run/run_progress/mod.rs
+++ b/lib/crates/fabro-cli/src/commands/run/run_progress/mod.rs
@@ -420,15 +420,6 @@ impl ProgressUI {
ProgressEvent::LoopRestart { from_node, to_node } => {
self.info.on_loop_restart(renderer, &from_node, &to_node);
}
- ProgressEvent::RetroStarted => {
- self.stage.on_retro_started(renderer);
- }
- ProgressEvent::RetroCompleted { duration_ms } => {
- self.stage.on_retro_completed(renderer, duration_ms);
- }
- ProgressEvent::RetroFailed { duration_ms } => {
- self.stage.on_retro_failed(renderer, duration_ms);
- }
ProgressEvent::MetadataSnapshotFailed {
phase,
failure_kind,
diff --git a/lib/crates/fabro-cli/src/commands/run/run_progress/stage_display.rs b/lib/crates/fabro-cli/src/commands/run/run_progress/stage_display.rs
index a7f8624bc..13014c2a0 100644
--- a/lib/crates/fabro-cli/src/commands/run/run_progress/stage_display.rs
+++ b/lib/crates/fabro-cli/src/commands/run/run_progress/stage_display.rs
@@ -547,30 +547,6 @@ impl StageDisplay {
);
}
- pub(super) fn on_retro_started(&mut self, renderer: &ProgressRenderer) {
- self.on_stage_started(renderer, "retro", "Retro", None);
- }
-
- pub(super) fn on_retro_completed(&mut self, renderer: &ProgressRenderer, duration_ms: u64) {
- self.finish_stage(
- renderer,
- "retro",
- "Retro",
- &styles::green_check(renderer.styles()),
- &format_duration_ms(duration_ms),
- );
- }
-
- pub(super) fn on_retro_failed(&mut self, renderer: &ProgressRenderer, duration_ms: u64) {
- self.finish_stage(
- renderer,
- "retro",
- "Retro",
- &styles::red_cross(renderer.styles()),
- &format_duration_ms(duration_ms),
- );
- }
-
fn finish_stage(
&mut self,
renderer: &ProgressRenderer,
diff --git a/lib/crates/fabro-cli/src/manifest_builder.rs b/lib/crates/fabro-cli/src/manifest_builder.rs
index 6e66543f2..05e412028 100644
--- a/lib/crates/fabro-cli/src/manifest_builder.rs
+++ b/lib/crates/fabro-cli/src/manifest_builder.rs
@@ -177,7 +177,6 @@ pub(crate) fn run_manifest_args(args: &RunArgs) -> Option {
dry_run: args.dry_run.then_some(true),
label: args.label.clone(),
model: args.model.clone(),
- no_retro: args.no_retro.then_some(true),
preserve_sandbox: args.preserve_sandbox.then_some(true),
provider: args.provider.clone(),
sandbox: args
@@ -201,7 +200,6 @@ pub(crate) fn preflight_manifest_args(args: &PreflightArgs) -> Option bool {
&& args.dry_run.is_none()
&& args.label.is_empty()
&& args.model.is_none()
- && args.no_retro.is_none()
&& args.preserve_sandbox.is_none()
&& args.provider.is_none()
&& args.sandbox.is_none()
diff --git a/lib/crates/fabro-cli/tests/it/cmd/attach.rs b/lib/crates/fabro-cli/tests/it/cmd/attach.rs
index 339d81d1e..1752cd394 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/attach.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/attach.rs
@@ -161,7 +161,6 @@ fn start_detached_human_run(
.args([
"run",
"--detach",
- "--no-retro",
"--sandbox",
"local",
"--provider",
@@ -330,7 +329,6 @@ fn attach_replays_completed_detached_run() {
"run",
"--dry-run",
"--auto-approve",
- "--no-retro",
"--detach",
"--run-id",
run_id.as_str(),
@@ -394,7 +392,6 @@ fn attach_advances_when_pending_question_is_answered_elsewhere() {
.args([
"run",
"--detach",
- "--no-retro",
"--sandbox",
"local",
"--provider",
@@ -522,7 +519,6 @@ fn attach_before_completion_streams_to_finished_state() {
"openai",
"--sandbox",
"local",
- "--no-retro",
"slow.fabro",
]);
let run_output = run_cmd.output().expect("command should execute");
@@ -643,7 +639,6 @@ fn attach_json_errors_without_prompting_for_human_input() {
.args([
"run",
"--detach",
- "--no-retro",
"--sandbox",
"local",
"--provider",
@@ -937,8 +932,7 @@ fn attach_json_errors_without_prompting_for_human_input() {
},
"execution": {
"approval": "prompt",
- "mode": "normal",
- "retros": false
+ "mode": "normal"
},
"git": {
"author": null
diff --git a/lib/crates/fabro-cli/tests/it/cmd/create.rs b/lib/crates/fabro-cli/tests/it/cmd/create.rs
index 6cace1acf..0c6c94416 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/create.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/create.rs
@@ -55,7 +55,6 @@ fn help() {
--sandbox Sandbox for agent tools [possible values: local, docker, daytona]
--in-place Run directly in the source checkout without git checkpoints
--label Attach a label to this run (repeatable, format: KEY=VALUE)
- --no-retro Skip retro generation after the run
--preserve-sandbox Keep the sandbox alive after the run finishes (for debugging)
-d, --detach Run the workflow in the background and print the run ID
-h, --help Print help
@@ -325,7 +324,6 @@ fn create_persists_requested_overrides_into_store() {
"--label",
"team=cli",
"--verbose",
- "--no-retro",
"--preserve-sandbox",
workflow.to_str().unwrap(),
]);
@@ -362,7 +360,6 @@ fn create_persists_requested_overrides_into_store() {
},
"dry_run": resolved_run.execution.mode == fabro_types::settings::run::RunMode::DryRun,
"auto_approve": resolved_run.execution.approval == fabro_types::settings::run::ApprovalMode::Auto,
- "no_retro": !resolved_run.execution.retros,
"llm": {
"model": resolved_run.model.name.as_ref().map(fabro_types::settings::InterpString::as_source),
"provider": resolved_run.model.provider.as_ref().map(fabro_types::settings::InterpString::as_source),
@@ -382,7 +379,6 @@ fn create_persists_requested_overrides_into_store() {
"goal": "Ship the release",
"dry_run": true,
"auto_approve": true,
- "no_retro": true,
"llm": {
"model": "gpt-5",
"provider": "openai"
diff --git a/lib/crates/fabro-cli/tests/it/cmd/dump.rs b/lib/crates/fabro-cli/tests/it/cmd/dump.rs
index c541b59eb..f15e7dccb 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/dump.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/dump.rs
@@ -103,13 +103,7 @@ fn dump_exports_large_command_output_backed_by_blob_refs() {
let mut run_cmd = context.run_cmd();
run_cmd.current_dir(&context.temp_dir);
run_cmd.timeout(Duration::from_secs(30));
- run_cmd.args([
- "--run-id",
- run_id.as_str(),
- "--no-retro",
- "--sandbox",
- "local",
- ]);
+ run_cmd.args(["--run-id", run_id.as_str(), "--sandbox", "local"]);
run_cmd.arg(&workflow);
let run_output = run_cmd.output().expect("command should execute");
assert!(
@@ -203,7 +197,6 @@ include = ["assets/**"]
run_cmd.args([
"--run-id",
run_id.as_str(),
- "--no-retro",
"--sandbox",
"local",
"run.toml",
diff --git a/lib/crates/fabro-cli/tests/it/cmd/ps.rs b/lib/crates/fabro-cli/tests/it/cmd/ps.rs
index effe90204..e912b13ff 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/ps.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/ps.rs
@@ -282,25 +282,13 @@ fn ps_filters_by_workflow_and_label() {
context
.run_cmd()
- .args([
- "--dry-run",
- "--auto-approve",
- "--no-retro",
- "--label",
- "suite=alpha",
- ])
+ .args(["--dry-run", "--auto-approve", "--label", "suite=alpha"])
.arg(&simple)
.assert()
.success();
context
.create_cmd()
- .args([
- "--dry-run",
- "--auto-approve",
- "--no-retro",
- "--label",
- "suite=beta",
- ])
+ .args(["--dry-run", "--auto-approve", "--label", "suite=beta"])
.arg(&branching)
.assert()
.success();
diff --git a/lib/crates/fabro-cli/tests/it/cmd/run.rs b/lib/crates/fabro-cli/tests/it/cmd/run.rs
index 0d385f35a..eeaa6b537 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/run.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/run.rs
@@ -57,9 +57,6 @@ fn remote_run_state_response() -> serde_json::Value {
"billing": null,
"total_retries": 0,
},
- "retro": null,
- "retro_prompt": null,
- "retro_response": null,
"sandbox": null,
"final_patch": null,
"pull_request": null,
@@ -154,7 +151,6 @@ fn help() {
--sandbox Sandbox for agent tools [possible values: local, docker, daytona]
--in-place Run directly in the source checkout without git checkpoints
--label Attach a label to this run (repeatable, format: KEY=VALUE)
- --no-retro Skip retro generation after the run
--preserve-sandbox Keep the sandbox alive after the run finishes (for debugging)
-d, --detach Run the workflow in the background and print the run ID
-h, --help Print help
@@ -373,7 +369,6 @@ digraph VaultWorkerLlm {
"--run-id",
run_id.as_str(),
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
"--provider",
@@ -412,7 +407,6 @@ fn detach_rejects_storage_dir_flag() {
"--detach",
"--dry-run",
"--auto-approve",
- "--no-retro",
workflow.to_str().unwrap(),
])
.output()
@@ -691,7 +685,6 @@ include = ["assets/**"]
"--run-id",
run_id.as_str(),
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
"--provider",
@@ -966,7 +959,6 @@ fn json_run_requires_manual_input_for_human_gates_without_auto_approve() {
"run",
"--sandbox",
"local",
- "--no-retro",
workflow.to_str().unwrap(),
])
.output()
diff --git a/lib/crates/fabro-cli/tests/it/cmd/runner.rs b/lib/crates/fabro-cli/tests/it/cmd/runner.rs
index 0fb3de155..761908a38 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/runner.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/runner.rs
@@ -481,7 +481,6 @@ methods = ["dev-token"]
run_id.as_str(),
"--detach",
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
workflow_path
@@ -566,7 +565,6 @@ digraph Test {
"run",
"--dry-run",
"--auto-approve",
- "--no-retro",
"--detach",
context.temp_dir.join("workflow.fabro").to_str().unwrap(),
])
@@ -667,9 +665,6 @@ fn runner_reports_missing_run_spec_without_prefetching_events() {
"checkpoint": null,
"checkpoints": [],
"conclusion": null,
- "retro": null,
- "retro_prompt": null,
- "retro_response": null,
"sandbox": null,
"final_patch": null,
"pull_request": null,
@@ -749,7 +744,6 @@ fn detached_run_answers_pending_question_without_interview_scratch_files() {
"--detach",
"--run-id",
run_id.as_str(),
- "--no-retro",
"--sandbox",
"local",
workflow_path.to_str().unwrap(),
@@ -810,77 +804,6 @@ fn detached_run_answers_pending_question_without_interview_scratch_files() {
)));
}
-#[test]
-fn worker_exits_with_retro_enabled_even_when_stdin_stays_open() {
- let context = auth_context();
- let run_id = unique_run_id();
- let workflow_path = context.temp_dir.join("retro-success.fabro");
-
- context.write_temp(
- ".fabro/project.toml",
- r"_version = 1
-
-[run.execution]
-retros = true
-",
- );
- context.write_temp(
- "retro-success.fabro",
- r#"digraph RetroSuccess {
- graph [goal="Finish successfully with retro enabled"]
- start [shape=Mdiamond, label="Start"]
- exit [shape=Msquare, label="Exit"]
- work [shape=parallelogram, label="Work", script="true"]
- start -> work -> exit
-}
-"#,
- );
-
- context
- .command()
- .args([
- "create",
- "--dry-run",
- "--auto-approve",
- "--run-id",
- run_id.as_str(),
- workflow_path.to_str().unwrap(),
- ])
- .assert()
- .success();
-
- let run_dir = context.find_run_dir(&run_id);
- let server = server_target(&context.storage_dir);
- let mut child = spawn_worker_process(&context, &server, &run_dir, &run_id, "start");
- let stdin = child.stdin.take().expect("worker stdin should be piped");
-
- wait_for_event_names(&run_dir, &["run.completed", "retro.completed"]);
- let status = wait_for_child_exit(&mut child, SHARED_DAEMON_TIMEOUT);
- drop(stdin);
- let output = child_output(child, status);
-
- assert!(
- output.status.success(),
- "worker should exit successfully after retro even with stdin open:\nstdout:\n{}\nstderr:\n{}",
- String::from_utf8_lossy(&output.stdout),
- String::from_utf8_lossy(&output.stderr)
- );
-
- let events = stored_worker_events(&run_dir);
- let run_completed_index = events
- .iter()
- .position(|event| matches!(&event.body, EventBody::RunCompleted(_)))
- .expect("run.completed should be present");
- let retro_completed_index = events
- .iter()
- .position(|event| matches!(&event.body, EventBody::RetroCompleted(_)))
- .expect("retro.completed should be present");
- assert!(
- retro_completed_index < run_completed_index,
- "retro.completed must precede run.completed"
- );
-}
-
#[cfg(unix)]
#[test]
fn worker_exits_after_sigterm_cancel_even_when_stdin_stays_open() {
@@ -894,7 +817,6 @@ fn worker_exits_after_sigterm_cancel_even_when_stdin_stays_open() {
.args([
"create",
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
"--run-id",
diff --git a/lib/crates/fabro-cli/tests/it/cmd/start.rs b/lib/crates/fabro-cli/tests/it/cmd/start.rs
index 81b5c05d8..d52014e48 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/start.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/start.rs
@@ -145,7 +145,6 @@ fn start_rejects_already_active_or_completed_run() {
"openai",
"--sandbox",
"local",
- "--no-retro",
"slow.fabro",
]);
let create_output = create_cmd.output().expect("command should execute");
@@ -207,7 +206,6 @@ fn start_runs_under_server_ownership_without_launcher_record() {
"openai",
"--sandbox",
"local",
- "--no-retro",
"owned-by-server.fabro",
])
.env("OPENAI_API_KEY", "test")
diff --git a/lib/crates/fabro-cli/tests/it/cmd/support.rs b/lib/crates/fabro-cli/tests/it/cmd/support.rs
index 1932f27cc..23c8c431b 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/support.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/support.rs
@@ -200,7 +200,6 @@ fn run_completed_dry_run(context: &TestContext, workflow: &Path) -> RunSetup {
run_id.as_str(),
"--dry-run",
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
]);
@@ -208,7 +207,7 @@ fn run_completed_dry_run(context: &TestContext, workflow: &Path) -> RunSetup {
let output = cmd.output().expect("command should execute");
if !output.status.success() {
panic!(
- "command failed: fabro run --dry-run --auto-approve --no-retro --sandbox local {}\nstdout:\n{}\nstderr:\n{}",
+ "command failed: fabro run --dry-run --auto-approve --sandbox local {}\nstdout:\n{}\nstderr:\n{}",
workflow.display(),
stdout(&output),
stderr(&output)
@@ -264,7 +263,6 @@ pub(crate) fn setup_detached_dry_run(context: &TestContext) -> RunSetup {
"--detach",
"--dry-run",
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
]);
@@ -272,7 +270,7 @@ pub(crate) fn setup_detached_dry_run(context: &TestContext) -> RunSetup {
let output = cmd.output().expect("command should execute");
if !output.status.success() {
panic!(
- "command failed: fabro run --detach --dry-run --auto-approve --no-retro --sandbox local {}\nstdout:\n{}\nstderr:\n{}",
+ "command failed: fabro run --detach --dry-run --auto-approve --sandbox local {}\nstdout:\n{}\nstderr:\n{}",
fixture("simple.fabro").display(),
stdout(&output),
stderr(&output)
@@ -372,7 +370,6 @@ fn run_local_workflow(context: &TestContext, workspace_dir: &Path, workflow: &st
"--run-id",
run_id.as_str(),
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
"--provider",
@@ -382,7 +379,7 @@ fn run_local_workflow(context: &TestContext, workspace_dir: &Path, workflow: &st
let output = cmd.output().expect("command should execute");
if !output.status.success() {
panic!(
- "command failed: fabro run --auto-approve --no-retro --sandbox local --provider openai {workflow}\nstdout:\n{}\nstderr:\n{}",
+ "command failed: fabro run --auto-approve --sandbox local --provider openai {workflow}\nstdout:\n{}\nstderr:\n{}",
stdout(&output),
stderr(&output)
);
@@ -752,7 +749,6 @@ async fn seed_dry_run(context: &TestContext, state: SeededRunState) -> RunSetup
serde_json::json!({
"dry_run": true,
"auto_approve": true,
- "no_retro": true,
"sandbox": "local",
"label": test_labels(context),
}),
@@ -780,7 +776,6 @@ async fn seed_git_backed_changed_run(context: &TestContext) -> SeededGitRunSetup
serde_json::json!({
"provider": "openai",
"sandbox": "local",
- "no_retro": true,
"label": test_labels(context),
}),
Some(serde_json::json!({
@@ -825,7 +820,6 @@ async fn seed_git_backed_noop_run(context: &TestContext) -> RunSetup {
serde_json::json!({
"provider": "openai",
"sandbox": "local",
- "no_retro": true,
"label": test_labels(context),
}),
Some(serde_json::json!({
@@ -855,7 +849,6 @@ async fn seed_artifact_run(context: &TestContext) -> RunSetup {
artifact_workflow_source(),
serde_json::json!({
"sandbox": "local",
- "no_retro": true,
"label": test_labels(context),
}),
None,
diff --git a/lib/crates/fabro-cli/tests/it/cmd/worker_auth.rs b/lib/crates/fabro-cli/tests/it/cmd/worker_auth.rs
index 2f8e5f32b..0793153d5 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/worker_auth.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/worker_auth.rs
@@ -302,7 +302,6 @@ async fn github_only_server_dispatched_worker_succeeds_without_worker_auth_store
"--detach",
"--dry-run",
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
workflow.to_str().unwrap(),
@@ -359,7 +358,6 @@ fn runner_rejects_bogus_worker_token_against_github_only_server() {
&run_id,
"--dry-run",
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
workflow.to_str().unwrap(),
diff --git a/lib/crates/fabro-cli/tests/it/scenario/lifecycle.rs b/lib/crates/fabro-cli/tests/it/scenario/lifecycle.rs
index c91a1664d..6f7db1d7f 100644
--- a/lib/crates/fabro-cli/tests/it/scenario/lifecycle.rs
+++ b/lib/crates/fabro-cli/tests/it/scenario/lifecycle.rs
@@ -29,7 +29,6 @@ fn local_run_lifecycle() {
cmd(&[
"run",
"--auto-approve",
- "--no-retro",
"--sandbox",
"local",
fixture("command_pipeline.fabro").to_str().unwrap(),
diff --git a/lib/crates/fabro-cli/tests/it/scenario/smoke.rs b/lib/crates/fabro-cli/tests/it/scenario/smoke.rs
index 6820875fe..56187a233 100644
--- a/lib/crates/fabro-cli/tests/it/scenario/smoke.rs
+++ b/lib/crates/fabro-cli/tests/it/scenario/smoke.rs
@@ -15,9 +15,6 @@ fn live_run_state_response() -> serde_json::Value {
"checkpoint": null,
"checkpoints": [],
"conclusion": null,
- "retro": null,
- "retro_prompt": null,
- "retro_response": null,
"sandbox": null,
"final_patch": null,
"pull_request": null,
diff --git a/lib/crates/fabro-cli/tests/it/workflow/agent_linear.rs b/lib/crates/fabro-cli/tests/it/workflow/agent_linear.rs
index c0eaffa53..175445f91 100644
--- a/lib/crates/fabro-cli/tests/it/workflow/agent_linear.rs
+++ b/lib/crates/fabro-cli/tests/it/workflow/agent_linear.rs
@@ -13,7 +13,6 @@ fn scenario_agent_linear(sandbox: &str) {
.run_cmd()
.args([
"--auto-approve",
- "--no-retro",
"--sandbox",
sandbox,
"--model",
diff --git a/lib/crates/fabro-cli/tests/it/workflow/command_agent_mixed.rs b/lib/crates/fabro-cli/tests/it/workflow/command_agent_mixed.rs
index 9bdd6b7ec..1718e8428 100644
--- a/lib/crates/fabro-cli/tests/it/workflow/command_agent_mixed.rs
+++ b/lib/crates/fabro-cli/tests/it/workflow/command_agent_mixed.rs
@@ -19,7 +19,6 @@ fn scenario_command_agent_mixed(sandbox: &str) {
.run_cmd()
.args([
"--auto-approve",
- "--no-retro",
"--sandbox",
sandbox,
"--model",
diff --git a/lib/crates/fabro-cli/tests/it/workflow/command_pipeline.rs b/lib/crates/fabro-cli/tests/it/workflow/command_pipeline.rs
index e691fb007..ff6e583a9 100644
--- a/lib/crates/fabro-cli/tests/it/workflow/command_pipeline.rs
+++ b/lib/crates/fabro-cli/tests/it/workflow/command_pipeline.rs
@@ -23,7 +23,7 @@ fn scenario_command_pipeline(sandbox: &str) {
context
.run_cmd()
- .args(["--auto-approve", "--no-retro", "--sandbox", sandbox])
+ .args(["--auto-approve", "--sandbox", sandbox])
.arg(fixture("command_pipeline.fabro"))
.timeout(timeout_for(sandbox))
.assert()
diff --git a/lib/crates/fabro-cli/tests/it/workflow/conditional_branching.rs b/lib/crates/fabro-cli/tests/it/workflow/conditional_branching.rs
index 71e273420..288c2ca6b 100644
--- a/lib/crates/fabro-cli/tests/it/workflow/conditional_branching.rs
+++ b/lib/crates/fabro-cli/tests/it/workflow/conditional_branching.rs
@@ -9,7 +9,7 @@ fn scenario_conditional_branching(sandbox: &str) {
context
.run_cmd()
- .args(["--auto-approve", "--no-retro", "--sandbox", sandbox])
+ .args(["--auto-approve", "--sandbox", sandbox])
.arg(fixture("conditional_branching.fabro"))
.timeout(timeout_for(sandbox))
.assert()
diff --git a/lib/crates/fabro-cli/tests/it/workflow/full_stack.rs b/lib/crates/fabro-cli/tests/it/workflow/full_stack.rs
index 921d01855..fef6e0578 100644
--- a/lib/crates/fabro-cli/tests/it/workflow/full_stack.rs
+++ b/lib/crates/fabro-cli/tests/it/workflow/full_stack.rs
@@ -19,7 +19,6 @@ fn scenario_full_stack(sandbox: &str) {
.run_cmd()
.args([
"--auto-approve",
- "--no-retro",
"--sandbox",
sandbox,
"--model",
diff --git a/lib/crates/fabro-cli/tests/it/workflow/hooks.rs b/lib/crates/fabro-cli/tests/it/workflow/hooks.rs
index df15350e9..c2eecb762 100644
--- a/lib/crates/fabro-cli/tests/it/workflow/hooks.rs
+++ b/lib/crates/fabro-cli/tests/it/workflow/hooks.rs
@@ -64,7 +64,6 @@ fn configure_hook_env(cmd: &mut assert_cmd::Command, hook_model: &str) {
cmd.env_remove("ANTHROPIC_API_KEY");
}
cmd.arg("--sandbox").arg("local");
- cmd.arg("--no-retro");
cmd.arg("--auto-approve");
cmd.arg("--provider").arg(stage_provider());
cmd.arg("--model").arg(hook_model);
diff --git a/lib/crates/fabro-cli/tests/it/workflow/human_gate.rs b/lib/crates/fabro-cli/tests/it/workflow/human_gate.rs
index 8c11f651e..48f649cbf 100644
--- a/lib/crates/fabro-cli/tests/it/workflow/human_gate.rs
+++ b/lib/crates/fabro-cli/tests/it/workflow/human_gate.rs
@@ -11,7 +11,6 @@ fn scenario_human_gate(sandbox: &str) {
.run_cmd()
.args([
"--auto-approve",
- "--no-retro",
"--sandbox",
sandbox,
"--model",
diff --git a/lib/crates/fabro-config/src/builders.rs b/lib/crates/fabro-config/src/builders.rs
index 4b09c407c..a18ea8a7c 100644
--- a/lib/crates/fabro-config/src/builders.rs
+++ b/lib/crates/fabro-config/src/builders.rs
@@ -501,7 +501,6 @@ command = ["demo-mcp"]
execution: Some(RunExecutionLayer {
mode: Some(RunMode::DryRun),
approval: Some(ApprovalMode::Auto),
- retros: Some(false),
}),
..RunLayer::default()
})
@@ -539,6 +538,5 @@ command = ["demo-mcp"]
);
assert_eq!(settings.run.execution.mode, RunMode::DryRun);
assert_eq!(settings.run.execution.approval, ApprovalMode::Auto);
- assert!(!settings.run.execution.retros);
}
}
diff --git a/lib/crates/fabro-config/src/defaults.toml b/lib/crates/fabro-config/src/defaults.toml
index 2654f0eb2..11041787e 100644
--- a/lib/crates/fabro-config/src/defaults.toml
+++ b/lib/crates/fabro-config/src/defaults.toml
@@ -11,7 +11,6 @@ graph = "workflow.fabro"
[run.execution]
mode = "normal"
approval = "prompt"
-retros = true
[run.prepare]
timeout = "5m"
diff --git a/lib/crates/fabro-config/src/layers/run.rs b/lib/crates/fabro-config/src/layers/run.rs
index 927c515cb..3a2610054 100644
--- a/lib/crates/fabro-config/src/layers/run.rs
+++ b/lib/crates/fabro-config/src/layers/run.rs
@@ -238,9 +238,6 @@ pub struct RunExecutionLayer {
pub mode: Option,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approval: Option,
- /// Positive-form: `true` runs retros, `false` skips them.
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub retros: Option,
}
/// `[run.checkpoint]` — checkpoint policy.
diff --git a/lib/crates/fabro-config/src/parse.rs b/lib/crates/fabro-config/src/parse.rs
index 66f3f9fde..7fb3becd9 100644
--- a/lib/crates/fabro-config/src/parse.rs
+++ b/lib/crates/fabro-config/src/parse.rs
@@ -126,7 +126,6 @@ fn rename_hint(key: &str) -> Option {
"upgrade_check" => "rename to `[cli.updates] check`",
"dry_run" => "rename to `[run.execution] mode = \"dry_run\"`",
"auto_approve" => "rename to `[run.execution] approval = \"auto\"`",
- "no_retro" => "rename to `[run.execution] retros = false`",
_ => return None,
};
Some(target.to_owned())
diff --git a/lib/crates/fabro-config/src/project.rs b/lib/crates/fabro-config/src/project.rs
index e34cb9346..3c5bf48a7 100644
--- a/lib/crates/fabro-config/src/project.rs
+++ b/lib/crates/fabro-config/src/project.rs
@@ -334,25 +334,6 @@ pub fn resolve_workflow(arg: &Path) -> Result {
Ok(resolution.dot_path)
}
-/// Check whether retros are enabled in the project config.
-/// Retros are now expressed as `[run.execution] retros = true` in v2.
-pub fn is_retro_enabled() -> bool {
- let start = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
- match discover_project_config(&start) {
- Ok(Some(path)) => load_project_config(&path)
- .ok()
- .and_then(|config| {
- config
- .run
- .as_ref()
- .and_then(|r| r.execution.as_ref())
- .and_then(|e| e.retros)
- })
- .unwrap_or(false),
- _ => false,
- }
-}
-
fn normalize_joined_path(base_dir: &Path, reference: &Path) -> PathBuf {
if reference.is_absolute() {
return reference.to_path_buf();
@@ -420,26 +401,6 @@ directory = "custom/"
);
}
- #[test]
- fn parse_with_run_execution_retros() {
- let config = "
-_version = 1
-
-[run.execution]
-retros = true
-"
- .parse::()
- .unwrap();
- assert_eq!(
- config
- .run
- .as_ref()
- .and_then(|r| r.execution.as_ref())
- .and_then(|e| e.retros),
- Some(true)
- );
- }
-
#[test]
fn parse_rejects_legacy_llm_section() {
let err = "_version = 1\n[llm]\nprovider = \"openai\"\n"
diff --git a/lib/crates/fabro-config/src/resolve/run.rs b/lib/crates/fabro-config/src/resolve/run.rs
index 433714e93..aa3b150dc 100644
--- a/lib/crates/fabro-config/src/resolve/run.rs
+++ b/lib/crates/fabro-config/src/resolve/run.rs
@@ -142,9 +142,6 @@ fn resolve_execution(execution: Option<&RunExecutionLayer>) -> RunExecutionSetti
approval: execution
.approval
.expect("defaults.toml should provide run.execution.approval"),
- retros: execution
- .retros
- .expect("defaults.toml should provide run.execution.retros"),
}
}
diff --git a/lib/crates/fabro-config/src/tests/resolve_run.rs b/lib/crates/fabro-config/src/tests/resolve_run.rs
index d8b3b1e5f..f63814e91 100644
--- a/lib/crates/fabro-config/src/tests/resolve_run.rs
+++ b/lib/crates/fabro-config/src/tests/resolve_run.rs
@@ -11,7 +11,6 @@ fn resolves_run_defaults_from_empty_settings() {
assert_eq!(settings.execution.mode, RunMode::Normal);
assert_eq!(settings.execution.approval, ApprovalMode::Prompt);
- assert!(settings.execution.retros);
assert_eq!(settings.prepare.timeout_ms, 300_000);
assert_eq!(settings.sandbox.provider, "docker");
assert!(settings.sandbox.stop_on_terminal);
diff --git a/lib/crates/fabro-dump/src/lib.rs b/lib/crates/fabro-dump/src/lib.rs
index b9fce2d99..560f23043 100644
--- a/lib/crates/fabro-dump/src/lib.rs
+++ b/lib/crates/fabro-dump/src/lib.rs
@@ -137,16 +137,6 @@ impl RunDump {
}
}
- if let Some(prompt) = state.retro_prompt.as_ref() {
- entries.push(RunDumpEntry::text("stages/retro/prompt.md", prompt.clone()));
- }
- if let Some(response) = state.retro_response.as_ref() {
- entries.push(RunDumpEntry::text(
- "stages/retro/response.md",
- response.clone(),
- ));
- }
-
Ok(Self {
entries,
stage_ranks,
@@ -571,8 +561,6 @@ mod tests {
clone_origin_url: None,
clone_branch: None,
});
- projection.retro_prompt = Some("retro prompt".to_string());
- projection.retro_response = Some("retro response".to_string());
let stage =
projection.stage_entry(stage_id.node_id(), stage_id.visit(), first_event_seq(2));
stage.prompt = Some("plan".to_string());
@@ -602,8 +590,6 @@ mod tests {
assert!(paths.contains(&"run.json"));
assert!(paths.contains(&"graph.fabro"));
- assert!(paths.contains(&"stages/retro/prompt.md"));
- assert!(paths.contains(&"stages/retro/response.md"));
assert!(paths.contains(&"stages/001-build@2/prompt.md"));
assert!(paths.contains(&"stages/001-build@2/response.md"));
assert!(paths.contains(&"stages/001-build@2/status.json"));
@@ -617,7 +603,6 @@ mod tests {
assert!(!paths.contains(&"status.json"));
assert!(!paths.contains(&"checkpoint.json"));
assert!(!paths.contains(&"sandbox.json"));
- assert!(!paths.contains(&"retro.json"));
assert!(!paths.contains(&"conclusion.json"));
let run_json = dump
diff --git a/lib/crates/fabro-retro/Cargo.toml b/lib/crates/fabro-retro/Cargo.toml
deleted file mode 100644
index d2674d175..000000000
--- a/lib/crates/fabro-retro/Cargo.toml
+++ /dev/null
@@ -1,31 +0,0 @@
-[package]
-name = "fabro-retro"
-edition.workspace = true
-version.workspace = true
-publish = false
-license.workspace = true
-description = "Retrospective analysis for Fabro workflow runs"
-
-[lib]
-doctest = false
-
-[lints]
-workspace = true
-
-[dependencies]
-anyhow = "1"
-chrono = { workspace = true, features = ["serde"] }
-fabro-agent = { path = "../fabro-agent" }
-fabro-dump = { path = "../fabro-dump" }
-fabro-llm = { path = "../fabro-llm" }
-fabro-store = { path = "../fabro-store" }
-fabro-types = { path = "../fabro-types" }
-fabro-util = { path = "../fabro-util" }
-serde.workspace = true
-serde_json.workspace = true
-tokio.workspace = true
-tracing.workspace = true
-
-[dev-dependencies]
-tokio = { workspace = true, features = ["test-util", "macros"] }
-tempfile = "3"
diff --git a/lib/crates/fabro-retro/src/lib.rs b/lib/crates/fabro-retro/src/lib.rs
deleted file mode 100644
index 5c630bf09..000000000
--- a/lib/crates/fabro-retro/src/lib.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-pub mod retro;
-pub mod retro_agent;
diff --git a/lib/crates/fabro-retro/src/retro.rs b/lib/crates/fabro-retro/src/retro.rs
deleted file mode 100644
index 13011a934..000000000
--- a/lib/crates/fabro-retro/src/retro.rs
+++ /dev/null
@@ -1,101 +0,0 @@
-use std::collections::HashMap;
-
-use fabro_types::RunId;
-pub use fabro_types::retro::{
- AggregateStats, FrictionKind, FrictionPoint, Learning, LearningCategory, OpenItem,
- OpenItemKind, Retro, RetroNarrative, SmoothnessRating, StageRetro,
-};
-
-#[derive(Debug, Clone)]
-pub struct CompletedStage {
- pub node_id: String,
- pub status: String,
- pub succeeded: bool,
- pub failed: bool,
- pub retries: u32,
- pub billing_usd_micros: Option,
- pub notes: Option,
- pub failure_reason: Option,
- pub files_touched: Vec,
-}
-
-pub fn derive_retro(
- run_id: RunId,
- workflow_name: &str,
- goal: &str,
- completed_stages: Vec,
- duration_ms: u64,
- stage_durations: &HashMap,
-) -> Retro {
- let mut stages = Vec::new();
- let mut all_files: Vec = Vec::new();
- let mut total_billing_usd_micros: Option = None;
- let mut total_retries: u32 = 0;
- let mut stages_completed: usize = 0;
- let mut stages_failed: usize = 0;
-
- for cs in completed_stages {
- total_retries += cs.retries;
-
- if cs.succeeded {
- stages_completed += 1;
- }
- if cs.failed {
- stages_failed += 1;
- }
-
- if let Some(cost) = cs.billing_usd_micros {
- *total_billing_usd_micros.get_or_insert(0) += cost;
- }
-
- let dur = stage_durations.get(&cs.node_id).copied().unwrap_or(0);
-
- stages.push(StageRetro {
- stage_label: cs.node_id.clone(),
- duration_ms: dur,
- retries: cs.retries,
- billing_usd_micros: cs.billing_usd_micros,
- stage_id: cs.node_id,
- status: cs.status,
- notes: cs.notes,
- failure_reason: cs.failure_reason,
- files_touched: cs.files_touched,
- });
-
- all_files.extend(
- stages
- .last()
- .expect("stage just pushed")
- .files_touched
- .iter()
- .cloned(),
- );
- }
-
- all_files.sort();
- all_files.dedup();
-
- let stats = AggregateStats {
- total_duration_ms: duration_ms,
- total_billing_usd_micros,
- total_retries,
- files_touched: all_files,
- stages_completed,
- stages_failed,
- };
-
- Retro {
- run_id,
- workflow_name: workflow_name.to_string(),
- goal: goal.to_string(),
- timestamp: chrono::Utc::now(),
- smoothness: None,
- stages,
- stats,
- intent: None,
- outcome: None,
- learnings: None,
- friction_points: None,
- open_items: None,
- }
-}
diff --git a/lib/crates/fabro-retro/src/retro_agent.rs b/lib/crates/fabro-retro/src/retro_agent.rs
deleted file mode 100644
index 4a630662f..000000000
--- a/lib/crates/fabro-retro/src/retro_agent.rs
+++ /dev/null
@@ -1,582 +0,0 @@
-use std::sync::{Arc, Mutex};
-use std::time::Duration;
-
-use anyhow::Context as _;
-use fabro_agent::tool_registry::RegisteredTool;
-use fabro_agent::{
- AgentProfile, AnthropicProfile, GeminiProfile, OpenAiProfile, Sandbox, Session, SessionEvent,
- SessionOptions, Turn,
-};
-use fabro_dump::{BlobReader, RunDump};
-use fabro_llm::client::Client;
-use fabro_llm::provider::Provider;
-use fabro_llm::types::ToolDefinition;
-use fabro_store::{EventEnvelope, RunProjection};
-use tokio::task::JoinHandle;
-
-use crate::retro::{RetroNarrative, SmoothnessRating};
-
-const RETRO_SYSTEM_PROMPT: &str = r"You are a workflow run retrospective analyst. Your job is to analyze a completed workflow run and generate a structured retrospective.
-
-You have access to the run's data files:
-- `events.jsonl` — the full event stream (stage starts/completions, agent tool calls, errors, retries)
-- `run.json` — serialized run projection with the run spec, checkpoint state, conclusion, retro data, and other metadata
-- `graph.fabro` — the workflow source for the run
-- `checkpoints/{seq:04}.json` — zero-padded checkpoint snapshots captured during the run
-- `run.log` — server/worker log output for the run when available
-- `stages/{rank:03}-{node_id}@{visit}/...` — execution-order-prefixed per-stage prompt, response, status, diff, output, and tool metadata files
-
-## Your task
-
-1. **Explore the data** using grep and read tools to understand what happened:
- - Look for failures, retries, and error messages
- - Check agent tool call patterns for wrong approaches or pivots
- - Note which stages took longest or had issues
- - Look for patterns indicating friction (repeated similar tool calls, error recovery)
- - Use `run.json` for the run-level snapshot, `graph.fabro` for workflow intent, and `stages/` for full per-stage payloads
-
-2. **Call the `submit_retro` tool** with your structured analysis.
-
-## Smoothness grading guidelines
-
-Grade the run on a 5-point scale:
-
-- **effortless** — Run achieved its goal on the first try with no retries, no wrong approaches. Agent moved efficiently from start to finish.
-- **smooth** — Goal achieved with minor hiccups (1-2 retries or a brief wrong approach quickly corrected). No human intervention needed. Overall clean execution.
-- **bumpy** — Goal achieved but with notable friction: multiple retries, at least one significant wrong approach, or substantial time spent on dead ends.
-- **struggled** — Goal achieved only with difficulty: many retries, major approach changes, human intervention, or partial failures requiring recovery.
-- **failed** — Run did not achieve its stated goal. May have completed some stages but the overall intent was not fulfilled.
-
-Consider the full context: not just stage pass/fail, but the quality of the journey visible in the agent events (tool call patterns, error recovery, approach pivots).
-
-## Guidelines for qualitative fields
-
-- **intent**: What was the workflow run trying to accomplish? Summarize the goal in a sentence.
-- **outcome**: What actually happened? Did it succeed? What was produced?
-- **learnings**: What was discovered about the repo, code, workflow, or tools?
-- **friction_points**: Where did things get stuck? What caused slowdowns?
-- **open_items**: What follow-up work, tech debt, or gaps were identified?
-
-Be specific and concise. Reference actual stage names, file paths, and error messages where relevant.";
-
-const SUBMIT_RETRO_SCHEMA: &str = r#"{
- "type": "object",
- "properties": {
- "smoothness": {
- "type": "string",
- "enum": ["effortless", "smooth", "bumpy", "struggled", "failed"],
- "description": "Overall smoothness rating for the workflow run"
- },
- "intent": {
- "type": "string",
- "description": "What was the workflow run trying to accomplish?"
- },
- "outcome": {
- "type": "string",
- "description": "What actually happened? Did it succeed?"
- },
- "learnings": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "category": { "type": "string", "enum": ["repo", "code", "workflow", "tool"] },
- "text": { "type": "string" }
- },
- "required": ["category", "text"]
- },
- "description": "What was discovered during the run?"
- },
- "friction_points": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "kind": { "type": "string", "enum": ["retry", "timeout", "wrong_approach", "tool_failure", "ambiguity"] },
- "description": { "type": "string" },
- "stage_id": { "type": "string" }
- },
- "required": ["kind", "description"]
- },
- "description": "Where did things get stuck?"
- },
- "open_items": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "kind": { "type": "string", "enum": ["tech_debt", "follow_up", "investigation", "test_gap"] },
- "description": { "type": "string" }
- },
- "required": ["kind", "description"]
- },
- "description": "Follow-up work or gaps identified"
- }
- },
- "required": ["smoothness", "intent", "outcome"]
-}"#;
-
-pub const RETRO_DATA_DIR: &str = "/tmp/retro_data";
-
-pub struct RetroAgentResult {
- pub narrative: RetroNarrative,
- pub response: String,
-}
-
-#[must_use]
-pub fn build_retro_prompt(retro_data_dir: &str) -> String {
- format!(
- "Analyze the workflow run data at `{retro_data_dir}/` and generate a retrospective. \
- The key file is `{retro_data_dir}/events.jsonl` which contains the full event stream. \
- Use `{retro_data_dir}/run.json` for the run-level snapshot, `{retro_data_dir}/graph.fabro` \
- for the workflow source, `{retro_data_dir}/checkpoints/` for checkpoint snapshots, \
- `{retro_data_dir}/run.log` for server logs when present, and `{retro_data_dir}/stages/` \
- for full per-stage payloads. \
- Use grep to search for interesting signals (failures, retries, errors, approach changes) \
- rather than reading the entire file. When done, call the `submit_retro` tool with your analysis."
- )
-}
-
-/// Run a retro agent session that analyzes workflow run data and produces
-/// a structured narrative. The agent explores `events.jsonl` and other
-/// files via tool access, then calls `submit_retro` with its analysis.
-pub async fn run_retro_agent(
- sandbox: &Arc,
- state: &RunProjection,
- events: &[EventEnvelope],
- run_log: Option>,
- blob_reader: Option,
- llm_client: &Client,
- provider: Provider,
- model: &str,
- event_callback: Option>,
-) -> anyhow::Result {
- // Upload data files into sandbox (needed for Daytona; no-op effect for local
- // since the agent can also read from the original paths via tools).
- upload_data_files(sandbox, state, events, RETRO_DATA_DIR, run_log, blob_reader).await?;
-
- // Build provider profile with the submit_retro tool
- let captured: Arc>> = Arc::new(Mutex::new(None));
- let captured_clone = Arc::clone(&captured);
-
- let mut profile = build_profile(provider, model);
-
- // Register submit_retro tool
- let submit_tool = RegisteredTool {
- definition: ToolDefinition {
- name: "submit_retro".to_string(),
- description: "Submit the structured retrospective analysis. Call this once you have analyzed the workflow run data.".to_string(),
- parameters: serde_json::from_str(SUBMIT_RETRO_SCHEMA)
- .expect("submit_retro schema should be valid JSON"),
- },
- executor: Arc::new(move |args, _ctx| {
- let captured = Arc::clone(&captured_clone);
- Box::pin(async move {
- let narrative: RetroNarrative = serde_json::from_value(args)
- .map_err(|e| format!("Invalid retro submission: {e}"))?;
- *captured.lock().unwrap_or_else(std::sync::PoisonError::into_inner) = Some(narrative);
- Ok("Retrospective submitted successfully.".to_string())
- })
- }),
- };
- profile.tool_registry_mut().register(submit_tool);
-
- let profile: Arc = Arc::from(profile);
-
- let config = SessionOptions {
- max_tool_rounds_per_input: 20,
- wall_clock_timeout: Some(Duration::from_mins(3)),
- // Disable features not needed for retro analysis
- enable_context_compaction: false,
- skill_dirs: Some(vec![]),
- user_instructions: Some(RETRO_SYSTEM_PROMPT.to_string()),
- ..SessionOptions::default()
- };
-
- let mut session = Session::new(
- llm_client.clone(),
- profile,
- Arc::clone(sandbox),
- config,
- None,
- );
-
- // Optionally forward agent events via the callback
- let event_forwarder_handle = event_callback.map(|cb| spawn_retro_event_forwarder(&session, cb));
-
- session
- .initialize()
- .await
- .context("Retro agent session initialization failed")?;
-
- let prompt = build_retro_prompt(RETRO_DATA_DIR);
-
- let process_result = session
- .process_input(&prompt)
- .await
- .context("Retro agent session failed");
-
- // Extract response from session history
- let response_text = session
- .history()
- .turns()
- .iter()
- .rev()
- .find_map(|t| match t {
- Turn::Assistant { content, .. } => Some(content.as_str()),
- _ => None,
- })
- .unwrap_or_default()
- .to_string();
-
- // Extract result / determine outcome
- let (_outcome, _failure_reason, narrative_result) = match process_result {
- Ok(()) => {
- let maybe_narrative = captured
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner)
- .take();
- match maybe_narrative {
- Some(narrative) => ("success", None, Ok(narrative)),
- None => (
- "error",
- Some("Retro agent did not call submit_retro".to_string()),
- Err(anyhow::anyhow!("Retro agent did not call submit_retro")),
- ),
- }
- }
- Err(e) => {
- let reason = e.to_string();
- ("error", Some(reason), Err(e))
- }
- };
-
- // Drop session to close the broadcast channel, then wait for event forwarder
- drop(session);
- if let Some(handle) = event_forwarder_handle {
- let _ = handle.await;
- }
-
- narrative_result.map(|narrative| RetroAgentResult {
- narrative,
- response: response_text,
- })
-}
-
-/// Return a placeholder narrative for dry-run mode. Exercises the full
-/// derive → apply_narrative → save path without making LLM calls.
-pub fn dry_run_narrative() -> RetroNarrative {
- RetroNarrative {
- smoothness: SmoothnessRating::Smooth,
- intent: "[dry-run] No LLM analysis performed".to_string(),
- outcome: "[dry-run] Run completed in simulated mode".to_string(),
- learnings: vec![],
- friction_points: vec![],
- open_items: vec![],
- }
-}
-
-/// Spawn a background task that forwards session events via the provided
-/// callback.
-fn spawn_retro_event_forwarder(
- session: &Session,
- callback: Arc,
-) -> JoinHandle<()> {
- let mut rx = session.subscribe();
- tokio::spawn(async move {
- while let Ok(event) = rx.recv().await {
- callback(event);
- }
- })
-}
-
-fn build_profile(provider: Provider, model: &str) -> Box {
- match provider {
- Provider::OpenAi => Box::new(OpenAiProfile::new(model)),
- Provider::Kimi
- | Provider::Zai
- | Provider::Minimax
- | Provider::Inception
- | Provider::OpenAiCompatible => Box::new(OpenAiProfile::new(model).with_provider(provider)),
- Provider::Gemini => Box::new(GeminiProfile::new(model)),
- Provider::Anthropic => Box::new(AnthropicProfile::new(model)),
- }
-}
-
-async fn upload_data_files(
- sandbox: &Arc,
- state: &RunProjection,
- events: &[EventEnvelope],
- target_dir: &str,
- run_log: Option>,
- blob_reader: Option,
-) -> anyhow::Result<()> {
- let mut dump = RunDump::from_store_state_and_events(state, events)?;
- if let Some(log) = run_log {
- dump.add_file_bytes("run.log", log);
- }
- if let Some(reader) = blob_reader {
- dump.hydrate_referenced_blobs_with_reader(reader).await?;
- }
- for entry in dump.entries() {
- let remote_path = format!("{target_dir}/{}", entry.path());
- let bytes = entry.to_bytes()?;
- let text = String::from_utf8(bytes)
- .with_context(|| format!("non-UTF8 retro entry {}", entry.path()))?;
- sandbox
- .write_file(&remote_path, &text)
- .await
- .with_context(|| format!("Failed to upload {}", entry.path()))?;
- }
- Ok(())
-}
-
-#[cfg(test)]
-mod tests {
- use std::sync::Arc;
-
- use chrono::{TimeZone, Utc};
- use fabro_agent::LocalSandbox;
- use fabro_store::StageId;
- use fabro_types::{StageCompletion, StageOutcome, first_event_seq};
- use tokio::fs;
-
- use super::*;
-
- #[test]
- fn submit_retro_schema_is_valid_json() {
- let schema: serde_json::Value = serde_json::from_str(SUBMIT_RETRO_SCHEMA).unwrap();
- assert_eq!(schema["type"], "object");
- assert!(schema["properties"]["smoothness"].is_object());
- assert!(schema["properties"]["intent"].is_object());
- assert!(schema["properties"]["outcome"].is_object());
- }
-
- #[test]
- fn retro_narrative_parses_from_submit_retro_args() {
- let args = serde_json::json!({
- "smoothness": "smooth",
- "intent": "Fix the login bug",
- "outcome": "Successfully fixed the authentication flow",
- "learnings": [
- { "category": "code", "text": "Token refresh was in wrong module" }
- ],
- "friction_points": [
- { "kind": "retry", "description": "First attempt had wrong import", "stage_id": "code" }
- ],
- "open_items": [
- { "kind": "test_gap", "description": "No integration test for token refresh" }
- ]
- });
-
- let narrative: RetroNarrative = serde_json::from_value(args).unwrap();
- assert_eq!(narrative.smoothness, SmoothnessRating::Smooth);
- assert_eq!(narrative.intent, "Fix the login bug");
- assert_eq!(narrative.learnings.len(), 1);
- assert_eq!(narrative.friction_points.len(), 1);
- assert_eq!(narrative.open_items.len(), 1);
- }
-
- #[test]
- fn retro_narrative_parses_minimal_args() {
- let args = serde_json::json!({
- "smoothness": "effortless",
- "intent": "Deploy feature",
- "outcome": "Deployed successfully"
- });
-
- let narrative: RetroNarrative = serde_json::from_value(args).unwrap();
- assert_eq!(narrative.smoothness, SmoothnessRating::Effortless);
- assert!(narrative.learnings.is_empty());
- assert!(narrative.friction_points.is_empty());
- assert!(narrative.open_items.is_empty());
- }
-
- #[test]
- fn retro_prompt_mentions_graph_and_stage_files() {
- let prompt = build_retro_prompt(RETRO_DATA_DIR);
-
- assert!(prompt.contains("run.json"));
- assert!(prompt.contains("graph.fabro"));
- assert!(prompt.contains("stages/"));
- }
-
- #[tokio::test]
- async fn upload_data_files_writes_projection_graph_and_stage_files() {
- let sandbox_root = tempfile::tempdir().expect("sandbox tempdir should exist");
- let sandbox: Arc =
- Arc::new(LocalSandbox::new(sandbox_root.path().to_path_buf()));
- let output_dir = tempfile::tempdir().expect("retro tempdir should exist");
- let target_dir = output_dir.path().join("retro");
- let target_dir_str = target_dir.to_string_lossy().to_string();
-
- let stage_id = StageId::new("build", 2);
- let mut state = RunProjection::default();
- state.graph_source = Some("digraph Ship {}".to_string());
- let stage = state.stage_entry(stage_id.node_id(), stage_id.visit(), first_event_seq(2));
- stage.prompt = Some("plan".to_string());
- stage.response = Some("done".to_string());
- stage.completion = Some(StageCompletion {
- outcome: StageOutcome::Succeeded,
- notes: Some("ok".to_string()),
- failure_reason: None,
- timestamp: Utc
- .with_ymd_and_hms(2026, 4, 20, 12, 1, 0)
- .single()
- .unwrap(),
- });
- stage.provider_used = Some(serde_json::json!({ "provider": "openai" }));
- stage.diff = Some("diff --git a/a b/a".to_string());
- stage.script_invocation = Some(serde_json::json!({ "command": "cargo test" }));
- stage.script_timing = Some(serde_json::json!({ "duration_ms": 10 }));
- stage.parallel_results = Some(serde_json::json!([{ "stage": "fanout@1" }]));
- stage.output = Some("output".to_string());
-
- upload_data_files(
- &sandbox,
- &state,
- &[],
- &target_dir_str,
- Some(b"server log\n".to_vec()),
- None,
- )
- .await
- .expect("retro files should upload");
-
- let run_json: serde_json::Value = serde_json::from_str(
- &fs::read_to_string(target_dir.join("run.json"))
- .await
- .expect("run.json should exist"),
- )
- .expect("run.json should parse");
- assert!(run_json.get("spec").is_some());
- assert!(run_json.get("run").is_none());
- assert!(run_json["stages"]["build@2"]["prompt"].is_null());
- assert!(run_json["stages"]["build@2"]["diff"].is_null());
- assert_eq!(
- fs::read_to_string(target_dir.join("graph.fabro"))
- .await
- .expect("graph.fabro should exist"),
- "digraph Ship {}"
- );
- assert_eq!(
- fs::read_to_string(target_dir.join("stages/001-build@2/prompt.md"))
- .await
- .expect("prompt file should exist"),
- "plan"
- );
- assert_eq!(
- fs::read_to_string(target_dir.join("stages/001-build@2/response.md"))
- .await
- .expect("response file should exist"),
- "done"
- );
- assert_eq!(
- fs::read_to_string(target_dir.join("stages/001-build@2/output.log"))
- .await
- .expect("output file should exist"),
- "output"
- );
- assert_eq!(
- fs::read_to_string(target_dir.join("events.jsonl"))
- .await
- .expect("events file should exist"),
- ""
- );
- assert_eq!(
- fs::read_to_string(target_dir.join("run.log"))
- .await
- .expect("run.log should exist"),
- "server log\n"
- );
- assert!(
- target_dir.join("stages/001-build@2/status.json").exists(),
- "status file should exist"
- );
- assert!(
- !target_dir.join("progress.jsonl").exists(),
- "legacy progress file should not be emitted"
- );
- }
-
- #[tokio::test]
- async fn upload_data_files_resolves_command_output_blob_refs() {
- let sandbox_root = tempfile::tempdir().expect("sandbox tempdir should exist");
- let sandbox: Arc =
- Arc::new(LocalSandbox::new(sandbox_root.path().to_path_buf()));
- let output_dir = tempfile::tempdir().expect("retro tempdir should exist");
- let target_dir = output_dir.path().join("retro");
- let target_dir_str = target_dir.to_string_lossy().to_string();
-
- let output_blob = serde_json::to_vec("resolved output").unwrap();
- let output_id = fabro_types::RunBlobId::new(&output_blob);
-
- let stage_id = StageId::new("build", 1);
- let mut state = RunProjection::default();
- let output_ref = fabro_types::format_blob_ref(&output_id);
- let stage = state.stage_entry(stage_id.node_id(), stage_id.visit(), first_event_seq(1));
- stage.script_invocation = Some(serde_json::json!({
- "command": "cargo test",
- "output": output_ref,
- }));
- stage.script_timing = Some(serde_json::json!({
- "exit_code": 0,
- "output": output_ref,
- }));
- stage.output = Some(output_ref);
-
- let reader: BlobReader = Box::new(move |blob_id| {
- let output_blob = output_blob.clone();
- Box::pin(async move {
- if blob_id == output_id {
- Ok(Some(output_blob.into()))
- } else {
- Ok(None)
- }
- })
- });
-
- upload_data_files(&sandbox, &state, &[], &target_dir_str, None, Some(reader))
- .await
- .expect("retro files should upload");
-
- assert_eq!(
- fs::read_to_string(target_dir.join("stages/001-build@1/output.log"))
- .await
- .expect("output file should exist"),
- "resolved output"
- );
-
- let script_timing: serde_json::Value = serde_json::from_str(
- &fs::read_to_string(target_dir.join("stages/001-build@1/script_timing.json"))
- .await
- .expect("script timing should exist"),
- )
- .expect("script timing should parse");
- assert_eq!(script_timing["output"], "resolved output");
-
- let script_invocation: serde_json::Value = serde_json::from_str(
- &fs::read_to_string(target_dir.join("stages/001-build@1/script_invocation.json"))
- .await
- .expect("script invocation should exist"),
- )
- .expect("script invocation should parse");
- assert_eq!(script_invocation["output"], "resolved output");
-
- let run_json: serde_json::Value = serde_json::from_str(
- &fs::read_to_string(target_dir.join("run.json"))
- .await
- .expect("run.json should exist"),
- )
- .expect("run.json should parse");
- assert_eq!(
- run_json["stages"]["build@1"]["script_timing"]["output"],
- "resolved output"
- );
- assert_eq!(
- run_json["stages"]["build@1"]["script_invocation"]["output"],
- "resolved output"
- );
- assert!(run_json["stages"]["build@1"]["output"].is_null());
- }
-}
diff --git a/lib/crates/fabro-server/Cargo.toml b/lib/crates/fabro-server/Cargo.toml
index 3bfa871a7..559310a5a 100644
--- a/lib/crates/fabro-server/Cargo.toml
+++ b/lib/crates/fabro-server/Cargo.toml
@@ -37,7 +37,6 @@ fabro-agent = { path = "../fabro-agent" }
fabro-llm = { path = "../fabro-llm" }
fabro-model = { path = "../fabro-model" }
fabro-proc = { path = "../fabro-proc" }
-fabro-retro = { path = "../fabro-retro" }
fabro-types = { path = "../fabro-types" }
fabro-util = { path = "../fabro-util" }
fabro-api = { path = "../fabro-api" }
@@ -109,4 +108,4 @@ tokio-util.workspace = true
fabro-macros = { path = "../fabro-macros" }
fabro-sandbox = { path = "../fabro-sandbox", features = ["test-support"] }
fabro-test = { workspace = true }
-fabro-types = { path = "../fabro-types", features = ["test-support"] }
\ No newline at end of file
+fabro-types = { path = "../fabro-types", features = ["test-support"] }
diff --git a/lib/crates/fabro-server/src/demo/mod.rs b/lib/crates/fabro-server/src/demo/mod.rs
index 4ed186b53..72f073e41 100644
--- a/lib/crates/fabro-server/src/demo/mod.rs
+++ b/lib/crates/fabro-server/src/demo/mod.rs
@@ -762,7 +762,7 @@ pub(crate) async fn get_system_info(
"uptime_secs": 42,
"runs": { "total": 3, "active": 1 },
"sandbox_provider": "local",
- "features": { "session_sandboxes": false, "retros": false }
+ "features": { "session_sandboxes": false }
})),
)
.into_response()
diff --git a/lib/crates/fabro-server/src/run_manifest.rs b/lib/crates/fabro-server/src/run_manifest.rs
index 23e3830b2..163bcb8a3 100644
--- a/lib/crates/fabro-server/src/run_manifest.rs
+++ b/lib/crates/fabro-server/src/run_manifest.rs
@@ -341,8 +341,7 @@ fn manifest_args_overrides(
..RunSandboxLayer::default()
});
- let execution_has_any =
- args.dry_run.is_some() || args.auto_approve.is_some() || args.no_retro.is_some();
+ let execution_has_any = args.dry_run.is_some() || args.auto_approve.is_some();
let execution = execution_has_any.then(|| RunExecutionLayer {
mode: args
.dry_run
@@ -354,7 +353,6 @@ fn manifest_args_overrides(
ApprovalMode::Prompt
}
}),
- retros: args.no_retro.map(|nr| !nr),
});
let run_has_any =
@@ -1711,7 +1709,6 @@ root = "/srv/fabro"
dry_run: Some(true),
label: Vec::new(),
model: None,
- no_retro: None,
preserve_sandbox: None,
provider: None,
sandbox: None,
@@ -1746,7 +1743,6 @@ override = "server"
dry_run: None,
label: Vec::new(),
model: None,
- no_retro: None,
preserve_sandbox: None,
provider: None,
sandbox: None,
diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs
index 102179da1..15ca01ebe 100644
--- a/lib/crates/fabro-server/src/server.rs
+++ b/lib/crates/fabro-server/src/server.rs
@@ -1281,15 +1281,11 @@ async fn github_webhook(
fn system_features(
server_settings: &ServerSettings,
- manifest_run_settings: &std::result::Result,
+ _manifest_run_settings: &std::result::Result,
) -> SystemFeatures {
let session_sandboxes = server_settings.features.session_sandboxes;
- let retros = manifest_run_settings
- .as_ref()
- .is_ok_and(|settings| settings.execution.retros);
SystemFeatures {
session_sandboxes: Some(session_sandboxes),
- retros: Some(retros),
}
}
diff --git a/lib/crates/fabro-server/src/server/tests.rs b/lib/crates/fabro-server/src/server/tests.rs
index 6b7292ba8..c4fa08db9 100644
--- a/lib/crates/fabro-server/src/server/tests.rs
+++ b/lib/crates/fabro-server/src/server/tests.rs
@@ -819,9 +819,6 @@ methods = ["dev-token"]
[features]
session_sandboxes = true
-
-[run.execution]
-retros = false
"#;
let server_settings = server_settings_from_toml(source);
let manifest_run_settings = resolve_manifest_run_settings(
@@ -830,11 +827,10 @@ retros = false
let features = system_features(&server_settings, &manifest_run_settings);
assert_eq!(features.session_sandboxes, Some(true));
- assert_eq!(features.retros, Some(false));
}
#[test]
-fn system_features_default_retros_when_manifest_run_settings_do_not_resolve() {
+fn system_features_ignore_manifest_run_settings_resolution() {
let source = r#"
_version = 1
@@ -854,7 +850,6 @@ provider = "invalid-provider"
let features = system_features(&server_settings, &manifest_run_settings);
assert_eq!(features.session_sandboxes, Some(true));
- assert_eq!(features.retros, Some(false));
}
#[test]
diff --git a/lib/crates/fabro-server/tests/it/api/system.rs b/lib/crates/fabro-server/tests/it/api/system.rs
index d8035983b..5f11d5f20 100644
--- a/lib/crates/fabro-server/tests/it/api/system.rs
+++ b/lib/crates/fabro-server/tests/it/api/system.rs
@@ -138,8 +138,10 @@ async fn get_system_info_returns_runtime_fields() {
assert_eq!(body["runs"]["total"], 0);
assert_eq!(body["runs"]["active"], 0);
assert!(body["uptime_secs"].as_i64().is_some());
- assert_eq!(body["features"]["session_sandboxes"], false);
- assert_eq!(body["features"]["retros"], true);
+ assert_eq!(
+ body["features"],
+ serde_json::json!({ "session_sandboxes": false })
+ );
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
diff --git a/lib/crates/fabro-store/src/run_state.rs b/lib/crates/fabro-store/src/run_state.rs
index b196ab1c2..83b1a85a7 100644
--- a/lib/crates/fabro-store/src/run_state.rs
+++ b/lib/crates/fabro-store/src/run_state.rs
@@ -241,18 +241,6 @@ impl RunProjectionReducer for RunProjection {
clone_branch: props.clone_branch.clone(),
});
}
- EventBody::RetroStarted(props) => {
- self.retro_prompt.clone_from(&props.prompt);
- }
- EventBody::RetroCompleted(props) => {
- self.retro_response.clone_from(&props.response);
- self.retro = props
- .retro
- .clone()
- .map(serde_json::from_value)
- .transpose()
- .map_err(|err| Error::InvalidEvent(format!("invalid retro payload: {err}")))?;
- }
EventBody::PullRequestCreated(props) => {
self.pull_request = Some(PullRequestRecord {
html_url: props.pr_url.clone(),
diff --git a/lib/crates/fabro-telemetry/src/sanitize.rs b/lib/crates/fabro-telemetry/src/sanitize.rs
index f0de548a3..3a7489c25 100644
--- a/lib/crates/fabro-telemetry/src/sanitize.rs
+++ b/lib/crates/fabro-telemetry/src/sanitize.rs
@@ -81,10 +81,10 @@ mod tests {
#[test]
fn simple_command_with_flag() {
let result = sanitize_command(
- &args(&["fabro", "run", "my-workflow.toml", "--no-retro"]),
+ &args(&["fabro", "run", "my-workflow.toml", "--preserve-sandbox"]),
"run",
);
- insta::assert_snapshot!(result, @"fabro run VALUE --no-retro");
+ insta::assert_snapshot!(result, @"fabro run VALUE --preserve-sandbox");
}
#[test]
diff --git a/lib/crates/fabro-types/src/lib.rs b/lib/crates/fabro-types/src/lib.rs
index eb15b5de7..224ca7042 100644
--- a/lib/crates/fabro-types/src/lib.rs
+++ b/lib/crates/fabro-types/src/lib.rs
@@ -17,7 +17,6 @@ pub mod outcome;
pub mod principal;
pub mod pull_request;
pub mod repository;
-pub mod retro;
pub mod run;
pub mod run_blob_id;
pub mod run_event;
@@ -62,10 +61,6 @@ pub use pull_request::{
PullRequestDetail, PullRequestGithubDetail, PullRequestRecord, PullRequestRef, PullRequestUser,
};
pub use repository::RepositoryReference;
-pub use retro::{
- AggregateStats, FrictionKind, FrictionPoint, Learning, LearningCategory, OpenItem,
- OpenItemKind, Retro, RetroNarrative, SmoothnessRating, StageRetro,
-};
pub use run::{
DirtyStatus, ForkSourceRef, GitContext, PreRunPushOutcome, RunClientProvenance, RunProvenance,
RunServerProvenance, RunSpec,
diff --git a/lib/crates/fabro-types/src/retro.rs b/lib/crates/fabro-types/src/retro.rs
deleted file mode 100644
index 8b1f02bff..000000000
--- a/lib/crates/fabro-types/src/retro.rs
+++ /dev/null
@@ -1,164 +0,0 @@
-use std::fmt;
-
-use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
-
-use crate::run_id::RunId;
-
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum SmoothnessRating {
- Effortless,
- Smooth,
- Bumpy,
- Struggled,
- Failed,
-}
-
-impl fmt::Display for SmoothnessRating {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let s = match self {
- Self::Effortless => "effortless",
- Self::Smooth => "smooth",
- Self::Bumpy => "bumpy",
- Self::Struggled => "struggled",
- Self::Failed => "failed",
- };
- f.write_str(s)
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum LearningCategory {
- Repo,
- Code,
- Workflow,
- Tool,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Learning {
- pub category: LearningCategory,
- pub text: String,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum FrictionKind {
- Retry,
- Timeout,
- WrongApproach,
- ToolFailure,
- Ambiguity,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct FrictionPoint {
- pub kind: FrictionKind,
- pub description: String,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub stage_id: Option,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum OpenItemKind {
- TechDebt,
- FollowUp,
- Investigation,
- TestGap,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct OpenItem {
- pub kind: OpenItemKind,
- pub description: String,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct StageRetro {
- pub stage_id: String,
- pub stage_label: String,
- pub status: String,
- pub duration_ms: u64,
- pub retries: u32,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub billing_usd_micros: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub notes: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub failure_reason: Option,
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- pub files_touched: Vec,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct AggregateStats {
- pub total_duration_ms: u64,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub total_billing_usd_micros: Option,
- pub total_retries: u32,
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- pub files_touched: Vec,
- pub stages_completed: usize,
- pub stages_failed: usize,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct RetroNarrative {
- pub smoothness: SmoothnessRating,
- pub intent: String,
- pub outcome: String,
- #[serde(default)]
- pub learnings: Vec,
- #[serde(default)]
- pub friction_points: Vec,
- #[serde(default)]
- pub open_items: Vec,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Retro {
- pub run_id: RunId,
- pub workflow_name: String,
- pub goal: String,
- pub timestamp: DateTime,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub smoothness: Option,
- pub stages: Vec,
- pub stats: AggregateStats,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub intent: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub outcome: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub learnings: Option>,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub friction_points: Option>,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub open_items: Option>,
-}
-
-impl Retro {
- pub fn apply_narrative(&mut self, narrative: RetroNarrative) {
- self.smoothness = Some(narrative.smoothness);
- self.intent = Some(narrative.intent);
- self.outcome = Some(narrative.outcome);
- self.learnings = if narrative.learnings.is_empty() {
- None
- } else {
- Some(narrative.learnings)
- };
- self.friction_points = if narrative.friction_points.is_empty() {
- None
- } else {
- Some(narrative.friction_points)
- };
- self.open_items = if narrative.open_items.is_empty() {
- None
- } else {
- Some(narrative.open_items)
- };
- }
-}
diff --git a/lib/crates/fabro-types/src/run_event/misc.rs b/lib/crates/fabro-types/src/run_event/misc.rs
index 11ee8acf0..86dba4f64 100644
--- a/lib/crates/fabro-types/src/run_event/misc.rs
+++ b/lib/crates/fabro-types/src/run_event/misc.rs
@@ -351,30 +351,3 @@ pub struct PullRequestCreatedProps {
pub struct PullRequestFailedProps {
pub error: String,
}
-
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct RetroStartedProps {
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub prompt: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub provider: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub model: Option,
-}
-
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct RetroCompletedProps {
- pub duration_ms: u64,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub response: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub retro: Option,
-}
-
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct RetroFailedProps {
- pub error: String,
- pub duration_ms: u64,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- pub exec_output_tail: Option,
-}
diff --git a/lib/crates/fabro-types/src/run_event/mod.rs b/lib/crates/fabro-types/src/run_event/mod.rs
index 9a2eecf24..50749ed5a 100644
--- a/lib/crates/fabro-types/src/run_event/mod.rs
+++ b/lib/crates/fabro-types/src/run_event/mod.rs
@@ -304,12 +304,6 @@ pub enum EventBody {
DevcontainerLifecycleCompleted(DevcontainerLifecycleCompletedProps),
#[serde(rename = "devcontainer.lifecycle.failed")]
DevcontainerLifecycleFailed(DevcontainerLifecycleFailedProps),
- #[serde(rename = "retro.started")]
- RetroStarted(RetroStartedProps),
- #[serde(rename = "retro.completed")]
- RetroCompleted(RetroCompletedProps),
- #[serde(rename = "retro.failed")]
- RetroFailed(RetroFailedProps),
Unknown {
name: String,
properties: Value,
@@ -499,9 +493,6 @@ impl EventBody {
}
Self::DevcontainerLifecycleCompleted(_) => "devcontainer.lifecycle.completed",
Self::DevcontainerLifecycleFailed(_) => "devcontainer.lifecycle.failed",
- Self::RetroStarted(_) => "retro.started",
- Self::RetroCompleted(_) => "retro.completed",
- Self::RetroFailed(_) => "retro.failed",
Self::Unknown { name, .. } => name.as_str(),
}
}
@@ -643,9 +634,6 @@ fn is_known_event_name(event: &str) -> bool {
| "devcontainer.lifecycle.command.completed"
| "devcontainer.lifecycle.completed"
| "devcontainer.lifecycle.failed"
- | "retro.started"
- | "retro.completed"
- | "retro.failed"
)
}
@@ -1504,6 +1492,41 @@ mod tests {
}
}
+ #[test]
+ fn retired_retro_events_deserialize_as_unknown() {
+ for (event_name, expected_properties) in [
+ (
+ "retro.started",
+ json!({"prompt": "Analyze the run", "provider": "openai", "model": "gpt-5"}),
+ ),
+ (
+ "retro.completed",
+ json!({"duration_ms": 1200, "response": "done", "retro": {"smoothness": "smooth"}}),
+ ),
+ (
+ "retro.failed",
+ json!({"duration_ms": 1200, "error": "state unavailable"}),
+ ),
+ ] {
+ let value = json!({
+ "id": "evt_retired_retro",
+ "ts": "2026-05-08T12:00:00.000Z",
+ "run_id": fixtures::RUN_1,
+ "event": event_name,
+ "properties": expected_properties
+ });
+
+ let parsed = RunEvent::from_value(value).unwrap();
+ match parsed.body {
+ EventBody::Unknown { name, properties } => {
+ assert_eq!(name, event_name);
+ assert_eq!(properties, expected_properties);
+ }
+ other => panic!("expected Unknown body, got {other:?}"),
+ }
+ }
+ }
+
#[test]
fn metadata_snapshot_failed_omits_empty_optional_fields() {
let body = EventBody::MetadataSnapshotFailed(MetadataSnapshotFailedProps {
@@ -1621,11 +1644,6 @@ mod tests {
success: false,
exec_output_tail: Some(tail.clone()),
}),
- EventBody::RetroFailed(RetroFailedProps {
- error: "state unavailable".to_string(),
- duration_ms: 10,
- exec_output_tail: Some(tail.clone()),
- }),
] {
let value = serde_json::to_value(&body).unwrap();
assert_eq!(
@@ -1657,11 +1675,6 @@ mod tests {
success: false,
exec_output_tail: None,
}),
- EventBody::RetroFailed(RetroFailedProps {
- error: "state unavailable".to_string(),
- duration_ms: 10,
- exec_output_tail: None,
- }),
] {
let value = serde_json::to_value(&body).unwrap();
assert!(
diff --git a/lib/crates/fabro-types/src/run_projection.rs b/lib/crates/fabro-types/src/run_projection.rs
index 4d88a45ea..667f8a831 100644
--- a/lib/crates/fabro-types/src/run_projection.rs
+++ b/lib/crates/fabro-types/src/run_projection.rs
@@ -5,7 +5,7 @@ use chrono::{DateTime, Utc};
use crate::{
BilledModelUsage, Checkpoint, Conclusion, DiffSummary, InterviewQuestionRecord,
- InvalidTransition, PullRequestRecord, Retro, RunControlAction, RunId, RunSpec, RunStatus,
+ InvalidTransition, PullRequestRecord, RunControlAction, RunId, RunSpec, RunStatus,
SandboxRecord, StageCompletion, StageHandler, StageId, StageState, StartRecord,
};
@@ -22,9 +22,6 @@ pub struct RunProjection {
pub checkpoint: Option,
pub checkpoints: Vec<(u32, Checkpoint)>,
pub conclusion: Option,
- pub retro: Option,
- pub retro_prompt: Option,
- pub retro_response: Option,
pub sandbox: Option,
pub final_patch: Option,
#[serde(default, skip_serializing_if = "Option::is_none")]
diff --git a/lib/crates/fabro-types/src/settings/run.rs b/lib/crates/fabro-types/src/settings/run.rs
index f61123785..a816c7d19 100644
--- a/lib/crates/fabro-types/src/settings/run.rs
+++ b/lib/crates/fabro-types/src/settings/run.rs
@@ -177,7 +177,6 @@ impl Default for RunPrepareSettings {
pub struct RunExecutionSettings {
pub mode: RunMode,
pub approval: ApprovalMode,
- pub retros: bool,
}
impl Default for RunExecutionSettings {
@@ -185,7 +184,6 @@ impl Default for RunExecutionSettings {
Self {
mode: RunMode::Normal,
approval: ApprovalMode::Prompt,
- retros: true,
}
}
}
diff --git a/lib/crates/fabro-workflow/Cargo.toml b/lib/crates/fabro-workflow/Cargo.toml
index 01f4e7b10..91dd3dac0 100644
--- a/lib/crates/fabro-workflow/Cargo.toml
+++ b/lib/crates/fabro-workflow/Cargo.toml
@@ -36,7 +36,6 @@ fabro-redact.workspace = true
fabro-checkpoint = { path = "../fabro-checkpoint" }
fabro-llm = { path = "../fabro-llm" }
fabro-model = { path = "../fabro-model" }
-fabro-retro = { path = "../fabro-retro" }
fabro-core = { path = "../fabro-core" }
fabro-store = { path = "../fabro-store" }
fabro-static.workspace = true
diff --git a/lib/crates/fabro-workflow/README.md b/lib/crates/fabro-workflow/README.md
index c8518a216..655bca79a 100644
--- a/lib/crates/fabro-workflow/README.md
+++ b/lib/crates/fabro-workflow/README.md
@@ -67,7 +67,7 @@ assert_eq!(graph.goal(), "Run tests");
use fabro_workflow::operations::start;
use fabro_workflow::pipeline;
-// Use `operations::start(...)` for the full initialize -> execute -> retro -> finalize flow.
+// Use `operations::start(...)` for the full initialize -> execute -> finalize flow.
// Use `pipeline::initialize(...)` + `pipeline::execute(...)` when you need partial lifecycle control.
```
diff --git a/lib/crates/fabro-workflow/src/event/convert.rs b/lib/crates/fabro-workflow/src/event/convert.rs
index 18ddd3860..3462e9dd8 100644
--- a/lib/crates/fabro-workflow/src/event/convert.rs
+++ b/lib/crates/fabro-workflow/src/event/convert.rs
@@ -1226,33 +1226,6 @@ fn event_body_from_event(event: &Event) -> EventBody {
exec_output_tail: exec_output_tail.clone(),
})
}
- Event::RetroStarted {
- prompt,
- provider,
- model,
- } => EventBody::RetroStarted(fabro_types::RetroStartedProps {
- prompt: prompt.clone(),
- provider: provider.clone(),
- model: model.clone(),
- }),
- Event::RetroCompleted {
- duration_ms,
- response,
- retro,
- } => EventBody::RetroCompleted(fabro_types::RetroCompletedProps {
- duration_ms: *duration_ms,
- response: response.clone(),
- retro: retro.clone(),
- }),
- Event::RetroFailed {
- error,
- duration_ms,
- exec_output_tail,
- } => EventBody::RetroFailed(fabro_types::RetroFailedProps {
- error: error.clone(),
- duration_ms: *duration_ms,
- exec_output_tail: exec_output_tail.clone(),
- }),
}
}
@@ -1862,24 +1835,6 @@ mod tests {
}
}
- #[test]
- fn retro_failed_maps_exec_output_tail_to_props() {
- let stored = to_run_event(&fixtures::RUN_1, &Event::RetroFailed {
- error: "state load failed".to_string(),
- duration_ms: 12,
- exec_output_tail: Some(exec_tail()),
- });
-
- match stored.body {
- EventBody::RetroFailed(props) => {
- assert_eq!(props.duration_ms, 12);
- let tail = props.exec_output_tail.expect("exec output tail");
- assert_eq!(tail.stdout.as_deref(), Some("last stdout line"));
- }
- other => panic!("expected RetroFailed body, got {other:?}"),
- }
- }
-
#[test]
fn metadata_snapshot_events_map_to_typed_bodies() {
let started = to_run_event(&fixtures::RUN_1, &Event::MetadataSnapshotStarted {
diff --git a/lib/crates/fabro-workflow/src/event/events.rs b/lib/crates/fabro-workflow/src/event/events.rs
index cf31c0df6..ab03bb16c 100644
--- a/lib/crates/fabro-workflow/src/event/events.rs
+++ b/lib/crates/fabro-workflow/src/event/events.rs
@@ -665,27 +665,6 @@ pub enum Event {
#[serde(default, skip_serializing_if = "Option::is_none")]
exec_output_tail: Option,
},
- RetroStarted {
- #[serde(default, skip_serializing_if = "Option::is_none")]
- prompt: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- provider: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- model: Option,
- },
- RetroCompleted {
- duration_ms: u64,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- response: Option,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- retro: Option,
- },
- RetroFailed {
- error: String,
- duration_ms: u64,
- #[serde(default, skip_serializing_if = "Option::is_none")]
- exec_output_tail: Option,
- },
}
impl Event {
@@ -1476,37 +1455,6 @@ impl Event {
"Devcontainer lifecycle command failed"
);
}
- Self::RetroStarted {
- prompt: _,
- provider,
- model,
- } => {
- info!(
- provider = provider.as_deref().unwrap_or(""),
- model = model.as_deref().unwrap_or(""),
- "Retro started"
- );
- }
- Self::RetroCompleted { duration_ms, .. } => {
- info!(duration_ms, "Retro completed");
- }
- Self::RetroFailed {
- error,
- duration_ms,
- exec_output_tail,
- } => {
- let tail = fabro_types::ExecOutputTail::trace_summary(exec_output_tail.as_ref());
- error!(
- error = %error,
- duration_ms,
- exec_output_tail_present = tail.present,
- exec_stdout_tail_bytes = tail.stdout_bytes,
- exec_stderr_tail_bytes = tail.stderr_bytes,
- exec_stdout_truncated = tail.stdout_truncated,
- exec_stderr_truncated = tail.stderr_truncated,
- "Retro failed"
- );
- }
}
}
}
diff --git a/lib/crates/fabro-workflow/src/event/names.rs b/lib/crates/fabro-workflow/src/event/names.rs
index 7696aaa7a..e6ce82fc5 100644
--- a/lib/crates/fabro-workflow/src/event/names.rs
+++ b/lib/crates/fabro-workflow/src/event/names.rs
@@ -148,9 +148,6 @@ pub fn event_name(event: &Event) -> &'static str {
}
Event::DevcontainerLifecycleCompleted { .. } => "devcontainer.lifecycle.completed",
Event::DevcontainerLifecycleFailed { .. } => "devcontainer.lifecycle.failed",
- Event::RetroStarted { .. } => "retro.started",
- Event::RetroCompleted { .. } => "retro.completed",
- Event::RetroFailed { .. } => "retro.failed",
}
}
@@ -164,14 +161,6 @@ mod tests {
#[test]
fn event_name_matches_new_dot_notation() {
- assert_eq!(
- event_name(&Event::RetroStarted {
- prompt: None,
- provider: None,
- model: None,
- }),
- "retro.started"
- );
assert_eq!(
event_name(&Event::ParallelBranchStarted {
parallel_group_id: StageId::new("plan", 1),
diff --git a/lib/crates/fabro-workflow/src/event/redaction.rs b/lib/crates/fabro-workflow/src/event/redaction.rs
index cc738ee27..5ae44d111 100644
--- a/lib/crates/fabro-workflow/src/event/redaction.rs
+++ b/lib/crates/fabro-workflow/src/event/redaction.rs
@@ -37,18 +37,12 @@ mod tests {
#[test]
fn build_redacted_event_payload_requires_id() {
- let stored = to_run_event(&fixtures::RUN_8, &Event::RetroStarted {
- prompt: Some("Analyze the run".to_string()),
- provider: None,
- model: None,
+ let stored = to_run_event(&fixtures::RUN_8, &Event::RunSubmitted {
+ definition_blob: None,
});
let payload = build_redacted_event_payload(&stored, &fixtures::RUN_8).unwrap();
assert_eq!(payload.as_value()["id"], stored.id);
- assert_eq!(payload.as_value()["event"], "retro.started");
- assert_eq!(
- payload.as_value()["properties"]["prompt"],
- "Analyze the run"
- );
+ assert_eq!(payload.as_value()["event"], "run.submitted");
}
#[test]
diff --git a/lib/crates/fabro-workflow/src/lib.rs b/lib/crates/fabro-workflow/src/lib.rs
index 0cdef9f8a..3778e9ebf 100644
--- a/lib/crates/fabro-workflow/src/lib.rs
+++ b/lib/crates/fabro-workflow/src/lib.rs
@@ -18,7 +18,6 @@
use std::collections::HashMap;
use std::sync::Arc;
-use fabro_retro::retro::CompletedStage;
use fabro_store::EventEnvelope;
use fabro_types::{EventBody, StageId};
@@ -30,63 +29,6 @@ pub(crate) fn millis_u64(d: std::time::Duration) -> u64 {
u64::try_from(d.as_millis()).unwrap_or(u64::MAX)
}
-/// Build `Vec` from a `Checkpoint`, mapping workflow-engine
-/// types into the flat struct expected by `fabro_retro::retro::derive_retro`.
-pub fn build_completed_stages(cp: &records::Checkpoint, run_failed: bool) -> Vec {
- use outcome::OutcomeExt;
-
- let mut stages = Vec::new();
- let mut any_stage_failed = false;
-
- for node_id in &cp.completed_nodes {
- let outcome = cp.node_outcomes.get(node_id);
- let retries = cp.node_retries.get(node_id).copied().unwrap_or(0);
-
- let status = outcome.map_or_else(|| "unknown".to_string(), |o| o.status.to_string());
-
- let succeeded = outcome.is_some_and(|o| o.status.is_successful());
- let failed = outcome.is_some_and(|o| o.status.is_failure());
- if failed {
- any_stage_failed = true;
- }
-
- stages.push(CompletedStage {
- node_id: node_id.clone(),
- status,
- succeeded,
- failed,
- retries,
- billing_usd_micros: outcome
- .and_then(|o| o.usage.as_ref())
- .and_then(|usage| usage.total_usd_micros),
- notes: outcome.and_then(|o| o.notes.clone()),
- failure_reason: outcome.and_then(|o| o.failure_reason().map(String::from)),
- files_touched: outcome.map(|o| o.files_touched.clone()).unwrap_or_default(),
- });
- }
-
- // If run failed with an error not captured in stages, mark the last stage
- if run_failed && !any_stage_failed {
- if let Some(last) = stages.last_mut() {
- last.failed = true;
- } else {
- stages.push(CompletedStage {
- node_id: "unknown".to_string(),
- status: "failed".to_string(),
- succeeded: false,
- failed: true,
- retries: 0,
- billing_usd_micros: None,
- notes: None,
- failure_reason: None,
- files_touched: vec![],
- });
- }
- }
-
- stages
-}
-
/// Extract the `duration_ms` from a `stage.completed` / `stage.failed`
/// event body, or `None` for any other variant.
fn stage_completion_duration_ms(body: &EventBody) -> Option {
@@ -128,8 +70,8 @@ pub fn total_stage_duration_by_node(events: &[EventEnvelope]) -> HashMap HashMap {
let mut entries: Vec<(StageId, u64)> = extract_stage_durations_by_stage_id(events)
.into_iter()
diff --git a/lib/crates/fabro-workflow/src/operations/fork.rs b/lib/crates/fabro-workflow/src/operations/fork.rs
index 58759b0aa..6fc175b04 100644
--- a/lib/crates/fabro-workflow/src/operations/fork.rs
+++ b/lib/crates/fabro-workflow/src/operations/fork.rs
@@ -84,9 +84,6 @@ pub async fn fork_run(
projection.start = None;
projection.sandbox = None;
projection.conclusion = None;
- projection.retro = None;
- projection.retro_prompt = None;
- projection.retro_response = None;
projection.final_patch = None;
projection.pull_request = None;
projection.superseded_by = None;
diff --git a/lib/crates/fabro-workflow/src/operations/start.rs b/lib/crates/fabro-workflow/src/operations/start.rs
index 5541e83db..f01e61adb 100644
--- a/lib/crates/fabro-workflow/src/operations/start.rs
+++ b/lib/crates/fabro-workflow/src/operations/start.rs
@@ -4,11 +4,9 @@ use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use fabro_auth::configured_providers_from_process_env;
-use fabro_config::project as project_config;
use fabro_interview::{AutoApproveInterviewer, Interviewer};
use fabro_mcp::config::{McpServerSettings, McpTransport};
use fabro_model::{Catalog, FallbackTarget, Provider};
-use fabro_retro::retro::Retro;
use fabro_sandbox::config::{
self as sandbox_config, DaytonaNetwork, DaytonaSnapshotSettings,
DockerfileSource as SandboxDockerfileSource, WorktreeMode, bridge_worktree_mode,
@@ -42,8 +40,7 @@ use crate::handler::HandlerRegistry;
use crate::outcome::Outcome;
use crate::pipeline::{
self, DevcontainerSpec, FinalizeOptions, Finalized, InitOptions, LlmSpec, Persisted,
- PullRequestOptions, RetroOptions, SandboxEnvSpec, build_conclusion_from_store,
- classify_engine_result,
+ PullRequestOptions, SandboxEnvSpec, build_conclusion_from_store, classify_engine_result,
};
use crate::records::Checkpoint;
use crate::run_control::RunControlState;
@@ -74,7 +71,6 @@ struct RunSession {
github_app: Option,
worktree_mode: Option,
registry_override: Option>,
- retro_enabled: bool,
preserve_sandbox: bool,
stop_on_terminal: bool,
pr_config: Option,
@@ -107,10 +103,8 @@ pub struct StartServices {
}
pub struct Started {
- pub finalized: Finalized,
- pub final_context: Option,
- pub retro: Option,
- pub retro_duration: Duration,
+ pub finalized: Finalized,
+ pub final_context: Option,
}
/// Start a fresh workflow run. Errors if a checkpoint already exists (use
@@ -450,7 +444,6 @@ impl RunSession {
github_app: services.github_app.clone(),
worktree_mode: Some(resolve_worktree_mode(resolved)),
registry_override: services.registry_override,
- retro_enabled: resolved.execution.retros && project_config::is_retro_enabled(),
preserve_sandbox: resolved.sandbox.preserve,
stop_on_terminal: resolved.sandbox.stop_on_terminal,
pr_config,
@@ -688,7 +681,7 @@ fn runtime_hook_type(hook_type: &ResolvedHookType) -> fabro_hooks::HookType {
}
impl RunSession {
- /// Shared engine: initialize, execute, retro, finalize, pull_request.
+ /// Shared engine: initialize, execute, finalize, pull_request.
async fn run(
self,
persisted: Persisted,
@@ -794,30 +787,11 @@ impl RunSession {
let executed = pipeline::execute(initialized).await;
store_progress_logger.flush().await;
let final_context = Some(executed.final_context.clone());
- let failed = !executed
- .outcome
- .as_ref()
- .is_ok_and(|outcome| outcome.status.is_successful());
-
- let retro_opts = RetroOptions {
- run_id: executed.run_options.run_id,
- services: Arc::clone(&executed.engine.run),
- workflow_name: executed.graph.name.clone(),
- goal: executed.graph.goal().to_string(),
- failed,
- run_duration_ms: executed.duration_ms,
- enabled: self.retro_enabled,
- model: executed.model.clone(),
- };
-
- let retro_start = Instant::now();
- let retroed = Box::pin(pipeline::retro(executed, &retro_opts)).await;
- let retro_duration = retro_start.elapsed();
let finalize_opts = FinalizeOptions {
- run_dir: retroed.run_options.run_dir.clone(),
- run_id: retroed.run_options.run_id,
- workflow_name: retroed.graph.name.clone(),
+ run_dir: executed.run_options.run_dir.clone(),
+ run_id: executed.run_options.run_id,
+ workflow_name: executed.graph.name.clone(),
preserve_sandbox: self.preserve_sandbox,
stop_on_terminal: self.stop_on_terminal,
last_git_sha: last_git_sha.lock().unwrap().clone(),
@@ -829,8 +803,7 @@ impl RunSession {
model: self.pr_model,
};
- let retro = retroed.retro.clone();
- let concluded = match Box::pin(pipeline::finalize(retroed, &finalize_opts)).await {
+ let concluded = match Box::pin(pipeline::finalize(executed, &finalize_opts)).await {
Ok(concluded) => concluded,
Err(err) => {
self.steering_hub.drain_pending_at_run_end();
@@ -851,8 +824,6 @@ impl RunSession {
Ok(Started {
finalized,
final_context,
- retro,
- retro_duration,
})
}
}
@@ -1203,7 +1174,6 @@ mod tests {
Some("sha-test")
);
assert_eq!(started.finalized.conclusion.status, StageOutcome::Succeeded);
- assert!(started.retro.is_none());
}
#[tokio::test]
diff --git a/lib/crates/fabro-workflow/src/pipeline/finalize.rs b/lib/crates/fabro-workflow/src/pipeline/finalize.rs
index 4afcfb917..c0e4e265c 100644
--- a/lib/crates/fabro-workflow/src/pipeline/finalize.rs
+++ b/lib/crates/fabro-workflow/src/pipeline/finalize.rs
@@ -1,4 +1,5 @@
use std::collections::HashMap;
+use std::sync::Arc;
use std::time::Instant;
use fabro_dump::RunDump;
@@ -8,7 +9,7 @@ use fabro_types::{BilledTokenCounts, DiffSummary, EventBody, RunProjection};
use fabro_util::error::collect_causes;
use fabro_util::time::elapsed_ms;
-use super::types::{Concluded, FinalizeOptions, Retroed};
+use super::types::{Concluded, Executed, FinalizeOptions};
use crate::error::Error;
use crate::event::{Event, RunNoticeCode, RunNoticeLevel};
use crate::outcome::{Outcome, OutcomeExt, StageOutcome};
@@ -526,15 +527,17 @@ async fn stop_sandbox_on_terminal(
/// # Errors
///
/// Returns `Error` if persisting terminal state fails.
-pub async fn finalize(retroed: Retroed, options: &FinalizeOptions) -> Result {
- let Retroed {
+pub async fn finalize(executed: Executed, options: &FinalizeOptions) -> Result {
+ let Executed {
graph,
outcome,
run_options,
duration_ms,
- services,
- retro: _,
- } = retroed;
+ final_context: _,
+ engine,
+ model: _,
+ } = executed;
+ let services = Arc::clone(&engine.run);
let (final_status, failure_reason, _run_status) = classify_engine_result(&outcome);
@@ -650,12 +653,13 @@ mod tests {
use object_store::memory::InMemory;
use super::*;
+ use crate::context::Context;
use crate::event::{Emitter, StoreProgressLogger, append_event};
- use crate::pipeline::types::Retroed;
use crate::run_metadata::{RunMetadataRuntime, RunMetadataWriterHandle};
use crate::run_options::{GitCheckpointOptions, RunOptions};
use crate::runtime_store::{RunStoreBackend, RunStoreHandle};
use crate::sandbox_git_runtime::SandboxGitRuntime;
+ use crate::services::EngineServices;
fn test_run_id() -> RunId {
fixtures::RUN_1
@@ -688,6 +692,26 @@ mod tests {
options
}
+ fn test_executed(
+ graph: Graph,
+ outcome: Result,
+ run_options: RunOptions,
+ duration_ms: u64,
+ services: Arc,
+ ) -> Executed {
+ let mut engine = EngineServices::test_default();
+ engine.run = services;
+ Executed {
+ graph,
+ outcome,
+ run_options,
+ duration_ms,
+ final_context: Context::new(),
+ engine: Arc::new(engine),
+ model: "test-model".to_string(),
+ }
+ }
+
fn test_store() -> Arc {
Arc::new(Database::new(
Arc::new(InMemory::new()),
@@ -1008,16 +1032,15 @@ mod tests {
Arc::new(RunMetadataRuntime::new()),
None,
);
- let retroed = Retroed {
- graph: Graph::new("test"),
- outcome: Ok(Outcome::success()),
- run_options: test_run_options(&run_dir),
- duration_ms: 5,
+ let executed = test_executed(
+ Graph::new("test"),
+ Ok(Outcome::success()),
+ test_run_options(&run_dir),
+ 5,
services,
- retro: None,
- };
+ );
- let concluded = finalize(retroed, &FinalizeOptions {
+ let concluded = finalize(executed, &FinalizeOptions {
run_dir: run_dir.clone(),
run_id: test_run_id(),
workflow_name: "test".to_string(),
@@ -1187,16 +1210,15 @@ mod tests {
"fabro/metadata/run",
)),
);
- let retroed = Retroed {
- graph: Graph::new("test"),
- outcome: Ok(Outcome::success()),
- run_options: test_git_run_options(repo_dir.path(), "fabro/metadata/run"),
- duration_ms: 5,
+ let executed = test_executed(
+ Graph::new("test"),
+ Ok(Outcome::success()),
+ test_git_run_options(repo_dir.path(), "fabro/metadata/run"),
+ 5,
services,
- retro: None,
- };
+ );
- finalize(retroed, &FinalizeOptions {
+ finalize(executed, &FinalizeOptions {
run_dir: repo_dir.path().to_path_buf(),
run_id: test_run_id(),
workflow_name: "test".to_string(),
@@ -1231,16 +1253,15 @@ mod tests {
Arc::new(RunMetadataRuntime::new()),
None,
);
- let retroed = Retroed {
- graph: Graph::new("test"),
- outcome: Ok(Outcome::success()),
- run_options: test_run_options(repo_dir.path()),
- duration_ms: 5,
+ let executed = test_executed(
+ Graph::new("test"),
+ Ok(Outcome::success()),
+ test_run_options(repo_dir.path()),
+ 5,
services,
- retro: None,
- };
+ );
- finalize(retroed, &FinalizeOptions {
+ finalize(executed, &FinalizeOptions {
run_dir: repo_dir.path().to_path_buf(),
run_id: test_run_id(),
workflow_name: "test".to_string(),
@@ -1266,16 +1287,15 @@ mod tests {
Arc::new(RunMetadataRuntime::new()),
None,
);
- let retroed = Retroed {
- graph: Graph::new("test"),
- outcome: Ok(Outcome::success()),
- run_options: test_run_options(repo_dir.path()),
- duration_ms: 5,
+ let executed = test_executed(
+ Graph::new("test"),
+ Ok(Outcome::success()),
+ test_run_options(repo_dir.path()),
+ 5,
services,
- retro: None,
- };
+ );
- finalize(retroed, &FinalizeOptions {
+ finalize(executed, &FinalizeOptions {
run_dir: repo_dir.path().to_path_buf(),
run_id: test_run_id(),
workflow_name: "test".to_string(),
@@ -1320,16 +1340,15 @@ mod tests {
run_branch: None,
meta_branch: None,
});
- let retroed = Retroed {
- graph: Graph::new("test"),
- outcome: Ok(Outcome::success()),
+ let executed = test_executed(
+ Graph::new("test"),
+ Ok(Outcome::success()),
run_options,
- duration_ms: 5,
+ 5,
services,
- retro: None,
- };
+ );
- finalize(retroed, &FinalizeOptions {
+ finalize(executed, &FinalizeOptions {
run_dir: repo.to_path_buf(),
run_id: test_run_id(),
workflow_name: "test".to_string(),
diff --git a/lib/crates/fabro-workflow/src/pipeline/mod.rs b/lib/crates/fabro-workflow/src/pipeline/mod.rs
index 424dd69bd..b6d6899d0 100644
--- a/lib/crates/fabro-workflow/src/pipeline/mod.rs
+++ b/lib/crates/fabro-workflow/src/pipeline/mod.rs
@@ -4,7 +4,6 @@ mod initialize;
mod parse;
mod persist;
mod pull_request;
-mod retro;
mod transform;
pub(crate) mod types;
mod validate;
@@ -22,11 +21,10 @@ pub use pull_request::{
AutoMergeOptions, OpenPullRequestRequest, PrContent, build_pr_content, maybe_open_pull_request,
pull_request,
};
-pub use retro::{retro, run_retro};
pub use transform::transform;
pub use types::{
Concluded, DevcontainerSpec, Executed, FinalizeOptions, Finalized, InitOptions, Initialized,
- LlmSpec, Parsed, Persisted, PullRequestOptions, RetroOptions, Retroed, SandboxEnvSpec,
- TransformOptions, Transformed, Validated,
+ LlmSpec, Parsed, Persisted, PullRequestOptions, SandboxEnvSpec, TransformOptions, Transformed,
+ Validated,
};
pub use validate::validate;
diff --git a/lib/crates/fabro-workflow/src/pipeline/prompts/pr_body.md b/lib/crates/fabro-workflow/src/pipeline/prompts/pr_body.md
index 9175e62db..aab063bb0 100644
--- a/lib/crates/fabro-workflow/src/pipeline/prompts/pr_body.md
+++ b/lib/crates/fabro-workflow/src/pipeline/prompts/pr_body.md
@@ -7,7 +7,7 @@ Return a JSON object with exactly two fields:
DO NOT INCLUDE in the body
- A `#` or `##` title heading at the top -- the title goes in the `title` field.
-- A "Retro" section, "Fabro Details" section, cost/duration table, or "Generated with" footer -- those are appended programmatically after your output.
+- A "Fabro Details" section, cost/duration table, or "Generated with" footer -- those are appended programmatically after your output.
- The full plan text -- the full plan is appended programmatically as a block.
- Bare `#1`, `#2` list prefixes -- GitHub auto-links those as issue references. Use plain `1.`, `2.` instead.
- A test plan unless the testing approach is non-obvious.
diff --git a/lib/crates/fabro-workflow/src/pipeline/pull_request.rs b/lib/crates/fabro-workflow/src/pipeline/pull_request.rs
index cfef50473..fdfa32e2a 100644
--- a/lib/crates/fabro-workflow/src/pipeline/pull_request.rs
+++ b/lib/crates/fabro-workflow/src/pipeline/pull_request.rs
@@ -6,7 +6,6 @@ use fabro_graphviz::parser;
use fabro_llm::client::Client;
use fabro_llm::generate::{GenerateParams, generate_object};
use fabro_model::Catalog;
-use fabro_retro::retro::Retro;
use fabro_store::RunProjection;
use fabro_types::PullRequestRecord;
use fabro_types::settings::run::MergeStrategy;
@@ -43,8 +42,8 @@ pub struct PrContent {
}
/// System prompt that instructs the LLM how to write a Fabro PR title and
-/// body. The trailing programmatic sections (Plan ``, Retro,
-/// Fabro Details, footer) are appended after the LLM body — the prompt
+/// body. The trailing programmatic sections (Plan ``, Fabro Details,
+/// footer) are appended after the LLM body — the prompt
/// explicitly forbids the LLM from duplicating them.
const PR_BODY_SYSTEM_PROMPT: &str = include_str!("prompts/pr_body.md");
@@ -159,52 +158,6 @@ fn format_duration_ms(ms: u64) -> String {
}
}
-/// Format the Retro section of the PR body.
-///
-/// Renders stats, friction points, and open items. Omits sub-sections when
-/// empty.
-fn format_retro_section(retro: &Retro) -> String {
- let mut parts = Vec::new();
- parts.push("### Retro".to_string());
- parts.push(String::new());
-
- // Stats summary
- parts.push(format!(
- "* {} stages completed, {} failed, {} retries",
- retro.stats.stages_completed, retro.stats.stages_failed, retro.stats.total_retries
- ));
- parts.push(format!(
- "* {} files modified",
- retro.stats.files_touched.len()
- ));
-
- // Friction points
- if let Some(ref fps) = retro.friction_points {
- if !fps.is_empty() {
- parts.push(String::new());
- parts.push("**Friction points:**".to_string());
- parts.push(String::new());
- for fp in fps {
- parts.push(format!("* {}", fp.description));
- }
- }
- }
-
- // Open items
- if let Some(ref items) = retro.open_items {
- if !items.is_empty() {
- parts.push(String::new());
- parts.push("**Open items:**".to_string());
- parts.push(String::new());
- for item in items {
- parts.push(format!("* {}", item.description));
- }
- }
- }
-
- parts.join("\n")
-}
-
/// Format the Fabro Details section of the PR body.
///
/// Renders a cost/duration table in a collapsible `` block, and
@@ -339,7 +292,6 @@ fn read_plan_text(state: &RunProjection) -> Option {
fn assemble_pr_body(
llm_output: &str,
plan_text: Option<&str>,
- retro_section: &str,
arc_details_section: &str,
) -> String {
let mut parts = Vec::new();
@@ -358,11 +310,6 @@ fn assemble_pr_body(
parts.push(" ".to_string());
}
- if !retro_section.is_empty() {
- parts.push(String::new());
- parts.push(retro_section.to_string());
- }
-
if !arc_details_section.is_empty() {
parts.push(String::new());
parts.push(arc_details_section.to_string());
@@ -438,7 +385,6 @@ async fn build_pr_content_with_client(
let run_state = run_state.or(loaded_run_state.as_ref());
let conclusion = conclusion.or_else(|| run_state.and_then(|state| state.conclusion.as_ref()));
let plan_text = run_state.and_then(read_plan_text);
- let retro = run_state.and_then(|state| state.retro.clone());
let run_spec = run_state.and_then(|state| state.spec.clone());
let dot_source = run_state.and_then(|state| state.graph_source.clone());
@@ -482,18 +428,12 @@ async fn build_pr_content_with_client(
generated.body
};
- let retro_section = retro.as_ref().map(format_retro_section).unwrap_or_default();
let arc_details_section = conclusion
.as_ref()
.map(|c| format_arc_details_section(c, run_spec.as_ref(), dot_source.as_deref()))
.unwrap_or_default();
- let body = assemble_pr_body(
- &llm_body,
- plan_text.as_deref(),
- &retro_section,
- &arc_details_section,
- );
+ let body = assemble_pr_body(&llm_body, plan_text.as_deref(), &arc_details_section);
info!("PR content generated");
@@ -709,9 +649,6 @@ mod tests {
use fabro_llm::client::Client;
use fabro_llm::provider::{ProviderAdapter, StreamEventStream};
use fabro_llm::types::{FinishReason, Message, Request, Response, StreamEvent, TokenCounts};
- use fabro_retro::retro::{
- AggregateStats, FrictionKind, FrictionPoint, OpenItem, OpenItemKind, StageRetro,
- };
use fabro_store::Database;
use fabro_types::{BilledTokenCounts, RunSpec, SuccessReason, first_event_seq, fixtures};
use fabro_vault::{SecretType, Vault};
@@ -902,137 +839,6 @@ mod tests {
}
}
- fn make_test_retro() -> Retro {
- Retro {
- run_id: fixtures::RUN_1,
- workflow_name: "implement".to_string(),
- goal: "Fix the bug".to_string(),
- timestamp: Utc::now(),
- smoothness: None,
- stages: vec![
- StageRetro {
- stage_id: "plan".to_string(),
- stage_label: "plan".to_string(),
- status: "succeeded".to_string(),
- duration_ms: 45_000,
- retries: 0,
- billing_usd_micros: Some(120_000),
- notes: None,
- failure_reason: None,
- files_touched: vec![],
- },
- StageRetro {
- stage_id: "implement".to_string(),
- stage_label: "implement".to_string(),
- status: "succeeded".to_string(),
- duration_ms: 90_000,
- retries: 0,
- billing_usd_micros: Some(250_000),
- notes: None,
- failure_reason: None,
- files_touched: vec!["src/main.rs".to_string(), "src/lib.rs".to_string()],
- },
- StageRetro {
- stage_id: "simplify".to_string(),
- stage_label: "simplify".to_string(),
- status: "succeeded".to_string(),
- duration_ms: 15_000,
- retries: 0,
- billing_usd_micros: Some(50_000),
- notes: None,
- failure_reason: None,
- files_touched: vec![],
- },
- ],
- stats: AggregateStats {
- total_duration_ms: 150_000,
- total_billing_usd_micros: Some(420_000),
- total_retries: 0,
- files_touched: vec!["src/lib.rs".to_string(), "src/main.rs".to_string()],
- stages_completed: 3,
- stages_failed: 0,
- },
- intent: None,
- outcome: None,
- learnings: None,
- friction_points: Some(vec![
- FrictionPoint {
- kind: FrictionKind::ToolFailure,
- description: "Daytona sandbox didn't have cargo on PATH".to_string(),
- stage_id: None,
- },
- FrictionPoint {
- kind: FrictionKind::Timeout,
- description: "Proxy timeouts during cold compilations".to_string(),
- stage_id: None,
- },
- ]),
- open_items: Some(vec![OpenItem {
- kind: OpenItemKind::TechDebt,
- description: "`ToolApprovalFn` type alias still exists".to_string(),
- }]),
- }
- }
-
- // ── format_retro_section tests ──────────────────────────────────────
-
- #[test]
- fn format_retro_section_full() {
- let retro = make_test_retro();
- let section = format_retro_section(&retro);
-
- assert!(section.contains("### Retro"));
- assert!(section.contains("3 stages completed, 0 failed, 0 retries"));
- assert!(section.contains("2 files modified"));
- assert!(section.contains("**Friction points:**"));
- assert!(section.contains("Daytona sandbox didn't have cargo on PATH"));
- assert!(section.contains("Proxy timeouts during cold compilations"));
- assert!(section.contains("**Open items:**"));
- assert!(section.contains("`ToolApprovalFn` type alias still exists"));
- }
-
- #[test]
- fn format_retro_section_no_friction_no_open() {
- let mut retro = make_test_retro();
- retro.friction_points = None;
- retro.open_items = None;
- let section = format_retro_section(&retro);
-
- assert!(section.contains("### Retro"));
- assert!(section.contains("3 stages completed"));
- assert!(!section.contains("**Friction points:**"));
- assert!(!section.contains("**Open items:**"));
- }
-
- #[test]
- fn format_retro_section_empty_stats() {
- let retro = Retro {
- run_id: fixtures::RUN_2,
- workflow_name: "test".to_string(),
- goal: "test".to_string(),
- timestamp: Utc::now(),
- smoothness: None,
- stages: vec![],
- stats: AggregateStats {
- total_duration_ms: 0,
- total_billing_usd_micros: None,
- total_retries: 0,
- files_touched: vec![],
- stages_completed: 0,
- stages_failed: 0,
- },
- intent: None,
- outcome: None,
- learnings: None,
- friction_points: None,
- open_items: None,
- };
- let section = format_retro_section(&retro);
-
- assert!(section.contains("0 stages completed, 0 failed, 0 retries"));
- assert!(section.contains("0 files modified"));
- }
-
// ── format_arc_details_section tests ────────────────────────────────
#[test]
@@ -1133,7 +939,6 @@ mod tests {
let body = assemble_pr_body(
"This is the narrative.\n\n### Plan Summary\n\n* Step 1\n* Step 2",
Some("Full plan text here"),
- "### Retro\n\n* 3 stages completed",
"### Fabro Details\n\n... ",
);
@@ -1141,7 +946,6 @@ mod tests {
assert!(body.contains("### Plan Summary"));
assert!(body.contains("\nFull plan
"));
assert!(body.contains("````md\nFull plan text here\n````"));
- assert!(body.contains("### Retro"));
assert!(body.contains("### Fabro Details"));
}
@@ -1150,30 +954,26 @@ mod tests {
let body = assemble_pr_body(
"Narrative only.",
None,
- "### Retro\n\n* stats",
"### Fabro Details\n\n... ",
);
assert!(body.contains("Narrative only."));
assert!(!body.contains("Full plan"));
- assert!(body.contains("### Retro"));
assert!(body.contains("### Fabro Details"));
}
#[test]
- fn assemble_no_retro() {
- let body = assemble_pr_body("Narrative only.", Some("Plan"), "", "");
+ fn assemble_no_details() {
+ let body = assemble_pr_body("Narrative only.", Some("Plan"), "");
assert!(body.contains("Narrative only."));
assert!(body.contains("Full plan"));
- // Empty sections should not produce extra headers
- assert!(!body.contains("### Retro"));
assert!(!body.contains("### Fabro Details"));
}
#[test]
fn assemble_narrative_only() {
- let body = assemble_pr_body("Just the narrative.", None, "", "");
+ let body = assemble_pr_body("Just the narrative.", None, "");
assert_eq!(
body,
@@ -1182,26 +982,13 @@ mod tests {
}
#[test]
- fn assemble_conclusion_without_retro() {
+ fn assemble_conclusion() {
let conclusion = make_test_conclusion();
let arc_details = format_arc_details_section(&conclusion, None, None);
- let body = assemble_pr_body("Narrative.", None, "", &arc_details);
+ let body = assemble_pr_body("Narrative.", None, &arc_details);
assert!(body.contains("### Fabro Details"));
assert!(body.contains("Ran 3 stages"));
- assert!(!body.contains("### Retro"));
- }
-
- #[test]
- fn assemble_both_conclusion_and_retro() {
- let conclusion = make_test_conclusion();
- let retro = make_test_retro();
- let retro_section = format_retro_section(&retro);
- let arc_details = format_arc_details_section(&conclusion, None, None);
- let body = assemble_pr_body("Narrative.", None, &retro_section, &arc_details);
-
- assert!(body.contains("### Retro"));
- assert!(body.contains("### Fabro Details"));
}
#[tokio::test]
@@ -1275,14 +1062,6 @@ mod tests {
})
.await
.unwrap();
- append_event(&run_store, &fixtures::RUN_1, &Event::RetroCompleted {
- duration_ms: 1,
- response: Some(String::new()),
- retro: Some(serde_json::to_value(make_test_retro()).unwrap()),
- })
- .await
- .unwrap();
-
let body = build_pr_content_with_client(
"diff --git a/src/lib.rs b/src/lib.rs\n+fn new_feature() {}\n",
"Implement feature",
@@ -1300,7 +1079,6 @@ mod tests {
.body;
assert!(body.contains("Narrative from mock."));
- assert!(body.contains("### Retro"));
assert!(body.contains("### Fabro Details"));
assert!(body.contains("test.fabro"));
}
@@ -1834,14 +1612,6 @@ mod tests {
})
.await
.unwrap();
- append_event(&run_store, &fixtures::RUN_1, &Event::RetroCompleted {
- duration_ms: 1,
- response: Some(String::new()),
- retro: Some(serde_json::to_value(make_test_retro()).unwrap()),
- })
- .await
- .unwrap();
-
let payload = pr_content_json("Mock", " \n");
let body = build_pr_content_with_client(
"diff --git a/src/lib.rs b/src/lib.rs\n+fn x() {}\n",
@@ -1859,7 +1629,6 @@ mod tests {
assert!(body.contains("The LLM did not produce a description"));
assert!(body.contains("Full plan
"));
assert!(body.contains("Plan from store"));
- assert!(body.contains("### Retro"));
assert!(body.contains("### Fabro Details"));
assert!(body.contains("Generated with [Fabro](https://fabro.sh)"));
}
diff --git a/lib/crates/fabro-workflow/src/pipeline/retro.rs b/lib/crates/fabro-workflow/src/pipeline/retro.rs
deleted file mode 100644
index 224ab5043..000000000
--- a/lib/crates/fabro-workflow/src/pipeline/retro.rs
+++ /dev/null
@@ -1,446 +0,0 @@
-use std::sync::Arc;
-
-use fabro_agent::SessionEvent;
-use fabro_dump::BlobReader;
-use fabro_llm::client::Client;
-use fabro_retro::retro::{Retro, derive_retro};
-use fabro_retro::retro_agent::{
- RETRO_DATA_DIR, build_retro_prompt, dry_run_narrative, run_retro_agent,
-};
-
-use super::types::{Executed, RetroOptions, Retroed};
-use crate::event::Event;
-
-fn exec_output_tail_from_anyhow(err: &anyhow::Error) -> Option {
- err.chain()
- .find_map(fabro_sandbox::default_redacted_output_tail)
-}
-
-pub async fn run_retro(options: &RetroOptions, dry_run: bool) -> Option {
- let services = &options.services;
- let state = match services.run_store.state().await {
- Ok(state) => state,
- Err(e) => {
- tracing::warn!(error = %e, "Could not load run state, skipping retro");
- services.emitter.emit(&Event::RetroFailed {
- error: e.to_string(),
- duration_ms: 0,
- exec_output_tail: exec_output_tail_from_anyhow(&e),
- });
- return None;
- }
- };
- let Some(ref cp) = state.checkpoint else {
- tracing::warn!("Could not load checkpoint, skipping retro");
- services.emitter.emit(&Event::RetroFailed {
- error: "checkpoint not found".to_string(),
- duration_ms: 0,
- exec_output_tail: None,
- });
- return None;
- };
-
- let completed_stages = crate::build_completed_stages(cp, options.failed);
- let events = match services.run_store.list_events().await {
- Ok(events) => events,
- Err(err) => {
- tracing::warn!(error = %err, "Could not load events from store, skipping retro");
- services.emitter.emit(&Event::RetroFailed {
- error: err.to_string(),
- duration_ms: 0,
- exec_output_tail: exec_output_tail_from_anyhow(&err),
- });
- return None;
- }
- };
- let stage_durations = crate::latest_stage_duration_by_node(&events);
- let mut retro = derive_retro(
- options.run_id,
- &options.workflow_name,
- &options.goal,
- completed_stages,
- options.run_duration_ms,
- &stage_durations,
- );
-
- let retro_start = std::time::Instant::now();
- let retro_prompt = build_retro_prompt(RETRO_DATA_DIR);
- services.emitter.emit(&Event::RetroStarted {
- prompt: Some(retro_prompt),
- provider: Some(services.provider.to_string()),
- model: Some(options.model.clone()),
- });
-
- let retro_result = if dry_run {
- Ok((dry_run_narrative(), String::new()))
- } else {
- match Client::from_source(services.llm_source.as_ref()).await {
- Ok(client) => {
- let emitter = Arc::clone(&services.emitter);
- let event_callback: Arc =
- Arc::new(move |event: SessionEvent| {
- emitter.touch();
- if !event.event.is_streaming_noise() {
- emitter.emit(&Event::Agent {
- stage: "retro".to_string(),
- visit: 1,
- event: event.event.clone(),
- session_id: Some(event.session_id.clone()),
- parent_session_id: event.parent_session_id.clone(),
- });
- }
- });
- let run_store = services.run_store.clone();
- let run_log = match services.run_store.read_run_log().await {
- Ok(log) => log,
- Err(err) => {
- tracing::warn!(
- error = %err,
- "failed to fetch run.log for retro; continuing without it"
- );
- None
- }
- };
- let blob_reader: BlobReader = Box::new(move |blob_id| {
- let run_store = run_store.clone();
- Box::pin(async move { run_store.read_blob(&blob_id).await })
- });
- run_retro_agent(
- &services.sandbox,
- &state,
- &events,
- run_log,
- Some(blob_reader),
- &client,
- services.provider,
- &options.model,
- Some(event_callback),
- )
- .await
- .map(|result| (result.narrative, result.response))
- }
- Err(err) => Err(anyhow::anyhow!(err.to_string())),
- }
- };
-
- let duration_ms = crate::millis_u64(retro_start.elapsed());
- match retro_result {
- Ok((narrative, response)) => {
- retro.apply_narrative(narrative);
- services.emitter.emit(&Event::RetroCompleted {
- duration_ms,
- response: Some(response),
- retro: serde_json::to_value(&retro).ok(),
- });
- }
- Err(e) => {
- services.emitter.emit(&Event::RetroFailed {
- error: e.to_string(),
- duration_ms,
- exec_output_tail: exec_output_tail_from_anyhow(&e),
- });
- tracing::debug!(error = %e, "Retro agent skipped");
- }
- }
-
- Some(retro)
-}
-
-/// RETRO phase: generate a retrospective for the workflow run.
-///
-/// Infallible — errors are logged, not propagated. If disabled, passes through
-/// with `retro: None`.
-pub async fn retro(executed: Executed, options: &RetroOptions) -> Retroed {
- let Executed {
- graph,
- outcome,
- run_options,
- duration_ms,
- final_context: _,
- engine,
- model: _,
- } = executed;
-
- let dry_run = run_options.dry_run_enabled();
-
- let retro = if options.enabled {
- run_retro(options, dry_run).await
- } else {
- None
- };
-
- Retroed {
- graph,
- outcome,
- run_options,
- duration_ms,
- services: Arc::clone(&engine.run),
- retro,
- }
-}
-
-#[cfg(test)]
-mod tests {
- use std::collections::HashMap;
- use std::sync::{Arc, Mutex};
- use std::time::Duration;
-
- use fabro_auth::{CredentialSource, EnvCredentialSource};
- use fabro_graphviz::graph::Graph;
- use fabro_store::Database;
- use fabro_types::{RunId, WorkflowSettings, fixtures};
- use object_store::memory::InMemory;
-
- use super::*;
- use crate::context::Context;
- use crate::event::{Emitter, Event, StoreProgressLogger, append_event};
- use crate::pipeline::types::Executed;
- use crate::records::{Checkpoint, CheckpointExt, RunSpec};
- use crate::run_options::RunOptions;
- use crate::services::{EngineServices, RunServices};
-
- fn test_run_id() -> RunId {
- fixtures::RUN_1
- }
-
- fn build_checkpoint() -> Checkpoint {
- let context = Context::new();
- context.set("response.work", serde_json::json!("done"));
- let mut outcomes = HashMap::new();
- outcomes.insert("work".to_string(), crate::outcome::Outcome::success());
- Checkpoint::from_context(
- &context,
- "work",
- vec!["work".to_string()],
- HashMap::new(),
- outcomes,
- None,
- HashMap::new(),
- HashMap::new(),
- HashMap::new(),
- )
- }
-
- fn test_store() -> Arc {
- Arc::new(Database::new(
- Arc::new(InMemory::new()),
- "",
- Duration::from_millis(1),
- None,
- ))
- }
-
- async fn test_run_store(
- run_dir: &std::path::Path,
- checkpoint: &Checkpoint,
- ) -> fabro_store::RunDatabase {
- let inner = test_store().create_run(&test_run_id()).await.unwrap();
- let run_store = inner;
- let run_spec = RunSpec {
- run_id: test_run_id(),
- settings: WorkflowSettings::default(),
- graph: Graph::new("test"),
- workflow_slug: None,
- source_directory: Some(run_dir.to_string_lossy().to_string()),
- git: None,
- labels: std::collections::HashMap::new(),
- provenance: None,
- manifest_blob: None,
- definition_blob: None,
- fork_source_ref: None,
- in_place: false,
- };
- append_event(&run_store, &test_run_id(), &Event::RunCreated {
- run_id: test_run_id(),
- settings: serde_json::to_value(&run_spec.settings).unwrap(),
- graph: serde_json::to_value(&run_spec.graph).unwrap(),
- workflow_source: None,
- workflow_config: None,
- labels: run_spec.labels.clone().into_iter().collect(),
- run_dir: run_dir.to_string_lossy().to_string(),
- source_directory: run_spec.source_directory.clone(),
- workflow_slug: None,
- db_prefix: None,
- provenance: run_spec.provenance.clone(),
- manifest_blob: None,
- git: None,
- fork_source_ref: None,
- in_place: false,
- web_url: None,
- })
- .await
- .unwrap();
- append_event(&run_store, &test_run_id(), &Event::CheckpointCompleted {
- node_id: checkpoint.current_node.clone(),
- status: "succeeded".to_string(),
- current_node: checkpoint.current_node.clone(),
- completed_nodes: checkpoint.completed_nodes.clone(),
- node_retries: checkpoint.node_retries.clone().into_iter().collect(),
- context_values: checkpoint.context_values.clone().into_iter().collect(),
- node_outcomes: checkpoint.node_outcomes.clone().into_iter().collect(),
- next_node_id: checkpoint.next_node_id.clone(),
- git_commit_sha: checkpoint.git_commit_sha.clone(),
- loop_failure_signatures: checkpoint
- .loop_failure_signatures
- .clone()
- .into_iter()
- .map(|(signature, count)| (signature.to_string(), count))
- .collect(),
- restart_failure_signatures: checkpoint
- .restart_failure_signatures
- .clone()
- .into_iter()
- .map(|(signature, count)| (signature.to_string(), count))
- .collect(),
- node_visits: checkpoint.node_visits.clone().into_iter().collect(),
- diff: None,
- diff_summary: None,
- })
- .await
- .unwrap();
- run_store
- }
-
- fn test_run_options(run_dir: &std::path::Path) -> RunOptions {
- RunOptions {
- settings: WorkflowSettings::default(),
- run_dir: run_dir.to_path_buf(),
- cancel_token: tokio_util::sync::CancellationToken::new(),
- run_id: test_run_id(),
- labels: HashMap::new(),
- workflow_slug: None,
- github_app: None,
- pre_run_git: None,
- fork_source_ref: None,
- base_branch: None,
- display_base_sha: None,
- git: None,
- }
- }
-
- fn test_llm_source() -> Arc {
- Arc::new(EnvCredentialSource::new())
- }
-
- #[tokio::test]
- async fn retro_phase_persists_retro_in_projection() {
- let temp = tempfile::tempdir().unwrap();
- let run_dir = temp.path().join("run");
- std::fs::create_dir_all(&run_dir).unwrap();
- let checkpoint = build_checkpoint();
- let run_store = test_run_store(&run_dir, &checkpoint).await;
-
- let emitter = Arc::new(Emitter::new(test_run_id()));
- let store_logger = StoreProgressLogger::new(run_store.clone());
- store_logger.register(&emitter);
- let sandbox: Arc = Arc::new(fabro_agent::LocalSandbox::new(
- std::env::current_dir().unwrap(),
- ));
- let services = RunServices::new(
- run_store.clone().into(),
- Arc::clone(&emitter),
- Arc::clone(&sandbox),
- None,
- tokio_util::sync::CancellationToken::new(),
- fabro_llm::Provider::Anthropic,
- test_llm_source(),
- Arc::new(crate::sandbox_git_runtime::SandboxGitRuntime::new()),
- Arc::new(crate::run_metadata::RunMetadataRuntime::new()),
- None,
- );
- let mut engine = EngineServices::test_default();
- engine.run = Arc::clone(&services);
- let executed = Executed {
- graph: Graph::new("test"),
- outcome: Ok(crate::outcome::Outcome::success()),
- run_options: test_run_options(&run_dir),
- duration_ms: 1,
- final_context: Context::new(),
- engine: Arc::new(engine),
- model: "test-model".to_string(),
- };
-
- let retroed = retro(executed, &RetroOptions {
- run_id: test_run_id(),
- services,
- workflow_name: "test".to_string(),
- goal: "Ship it".to_string(),
- failed: false,
- run_duration_ms: 1,
- enabled: true,
- model: "test-model".to_string(),
- })
- .await;
- store_logger.flush().await;
-
- assert!(retroed.retro.is_some());
- }
-
- #[tokio::test]
- async fn run_retro_emits_retro_events() {
- let temp = tempfile::tempdir().unwrap();
- let run_dir = temp.path().join("run");
- std::fs::create_dir_all(&run_dir).unwrap();
- let checkpoint = build_checkpoint();
-
- let emitter = Arc::new(Emitter::default());
- let seen = Arc::new(Mutex::new(Vec::new()));
- emitter.on_event({
- let seen = Arc::clone(&seen);
- move |event| seen.lock().unwrap().push(event.clone())
- });
- let services = RunServices::new(
- test_run_store(&run_dir, &checkpoint).await.into(),
- Arc::clone(&emitter),
- Arc::new(fabro_agent::LocalSandbox::new(
- std::env::current_dir().unwrap(),
- )),
- None,
- tokio_util::sync::CancellationToken::new(),
- fabro_llm::Provider::Anthropic,
- test_llm_source(),
- Arc::new(crate::sandbox_git_runtime::SandboxGitRuntime::new()),
- Arc::new(crate::run_metadata::RunMetadataRuntime::new()),
- None,
- );
-
- let retro = run_retro(
- &RetroOptions {
- run_id: test_run_id(),
- services,
- workflow_name: "test".to_string(),
- goal: "Ship it".to_string(),
- failed: false,
- run_duration_ms: 1,
- enabled: true,
- model: "test-model".to_string(),
- },
- true,
- )
- .await;
-
- assert!(retro.is_some());
- let seen = seen.lock().unwrap();
- let retro_started = seen
- .iter()
- .find(|event| event.event_name() == "retro.started")
- .unwrap();
- let retro_started_properties = retro_started.properties().unwrap();
- assert_eq!(retro_started_properties["provider"], "anthropic");
- assert_eq!(retro_started_properties["model"], "test-model");
- assert!(
- retro_started_properties["prompt"]
- .as_str()
- .is_some_and(|prompt| prompt.contains("/tmp/retro_data/events.jsonl"))
- );
-
- let retro_completed = seen
- .iter()
- .find(|event| event.event_name() == "retro.completed")
- .unwrap();
- let retro_completed_properties = retro_completed.properties().unwrap();
- assert_eq!(retro_completed_properties["response"], "");
- assert!(retro_completed_properties.get("retro").is_some());
- assert_eq!(retro_completed_properties["retro"]["smoothness"], "smooth");
- }
-}
diff --git a/lib/crates/fabro-workflow/src/pipeline/types.rs b/lib/crates/fabro-workflow/src/pipeline/types.rs
index b0113a9a3..a09d842c3 100644
--- a/lib/crates/fabro-workflow/src/pipeline/types.rs
+++ b/lib/crates/fabro-workflow/src/pipeline/types.rs
@@ -7,7 +7,6 @@ use fabro_interview::Interviewer;
use fabro_llm::Provider;
use fabro_mcp::config::McpServerSettings;
use fabro_model::FallbackTarget;
-use fabro_retro::retro::Retro;
use fabro_sandbox::SandboxSpec;
use fabro_sandbox::config::WorktreeMode;
use fabro_types::RunId;
@@ -286,17 +285,6 @@ pub struct Executed {
pub model: String,
}
-/// Output of the RETRO phase.
-#[non_exhaustive]
-pub struct Retroed {
- pub graph: Graph,
- pub outcome: Result,
- pub run_options: RunOptions,
- pub duration_ms: u64,
- pub services: Arc,
- pub retro: Option,
-}
-
/// Output of the FINALIZE phase.
#[non_exhaustive]
pub struct Concluded {
@@ -325,18 +313,6 @@ pub struct TransformOptions {
pub custom_transforms: Vec>,
}
-/// Options for the RETRO phase.
-pub struct RetroOptions {
- pub run_id: RunId,
- pub services: Arc,
- pub workflow_name: String,
- pub goal: String,
- pub failed: bool,
- pub run_duration_ms: u64,
- pub enabled: bool,
- pub model: String,
-}
-
/// Options for the FINALIZE phase.
pub struct FinalizeOptions {
pub run_dir: PathBuf,
diff --git a/lib/packages/fabro-api-client/src/models/manifest-args.ts b/lib/packages/fabro-api-client/src/models/manifest-args.ts
index 4f9c7de6e..01e109f4d 100644
--- a/lib/packages/fabro-api-client/src/models/manifest-args.ts
+++ b/lib/packages/fabro-api-client/src/models/manifest-args.ts
@@ -28,7 +28,6 @@ export interface ManifestArgs {
'verbose'?: boolean;
'dry_run'?: boolean;
'auto_approve'?: boolean;
- 'no_retro'?: boolean;
'preserve_sandbox'?: boolean;
/**
* Override `run.sandbox.local.worktree_mode` (e.g. `never` for `--in-place`).
diff --git a/lib/packages/fabro-api-client/src/models/run-execution-settings.ts b/lib/packages/fabro-api-client/src/models/run-execution-settings.ts
index d78f4d0af..d146974cd 100644
--- a/lib/packages/fabro-api-client/src/models/run-execution-settings.ts
+++ b/lib/packages/fabro-api-client/src/models/run-execution-settings.ts
@@ -23,7 +23,6 @@ import type { RunMode } from './run-mode';
export interface RunExecutionSettings {
'mode': RunMode;
'approval': ApprovalMode;
- 'retros': boolean;
}
diff --git a/lib/packages/fabro-api-client/src/models/run-projection.ts b/lib/packages/fabro-api-client/src/models/run-projection.ts
index 4bd3b6512..1ea871fb7 100644
--- a/lib/packages/fabro-api-client/src/models/run-projection.ts
+++ b/lib/packages/fabro-api-client/src/models/run-projection.ts
@@ -55,9 +55,6 @@ export interface RunProjection {
*/
'checkpoints'?: Array>;
'conclusion'?: { [key: string]: any; } | null;
- 'retro'?: { [key: string]: any; } | null;
- 'retro_prompt'?: string | null;
- 'retro_response'?: string | null;
'sandbox'?: { [key: string]: any; } | null;
'final_patch'?: string | null;
'diff_summary'?: DiffSummary | null;
diff --git a/lib/packages/fabro-api-client/src/models/system-features.ts b/lib/packages/fabro-api-client/src/models/system-features.ts
index c82e60cf2..a916f82e8 100644
--- a/lib/packages/fabro-api-client/src/models/system-features.ts
+++ b/lib/packages/fabro-api-client/src/models/system-features.ts
@@ -22,9 +22,5 @@ export interface SystemFeatures {
* Whether session sandboxes are enabled.
*/
'session_sandboxes'?: boolean;
- /**
- * Whether workflow retros are enabled.
- */
- 'retros'?: boolean;
}
diff --git a/test/retro-e2e.fabro b/test/retro-e2e.fabro
deleted file mode 100644
index 86b94cac3..000000000
--- a/test/retro-e2e.fabro
+++ /dev/null
@@ -1,12 +0,0 @@
-digraph RetroE2E {
- graph [goal="Verify retro generation works end-to-end"]
- rankdir=LR
-
- start [shape=Mdiamond, label="Start"]
- exit [shape=Msquare, label="Exit"]
-
- check [label="Check", shape=parallelogram, script="echo 'Hello from Daytona sandbox' && ls -la"]
- summarize [label="Summarize", prompt="Briefly summarize what the check stage did. Keep it under 2 sentences."]
-
- start -> check -> summarize -> exit
-}