Merge remote-tracking branch 'origin/main'

This commit is contained in:
Bryan Helmkamp 2026-04-06 16:57:45 -04:00
commit 7a475ebb5a
27 changed files with 1954 additions and 3436 deletions

View file

@ -25,7 +25,6 @@ import * as RunStages from "./routes/run-stages";
import * as RunSettings from "./routes/run-settings";
import * as RunGraph from "./routes/run-graph";
import * as RunFiles from "./routes/run-files";
import * as RunVerification from "./routes/run-verification";
import * as RunUsage from "./routes/run-usage";
import * as RunRetro from "./routes/run-retro";
import * as VerificationCriteria from "./routes/verification-criteria";
@ -115,7 +114,6 @@ export const routes: RouteObject[] = [
route("settings", RunSettings),
route("graph", RunGraph),
route("files", RunFiles),
route("verification", RunVerification),
route("usage", RunUsage),
route("retro", RunRetro),
],

View file

@ -11,7 +11,6 @@ const tabs = [
{ name: "Overview", path: "", count: null },
{ name: "Stages", path: "/stages/detect-drift", count: null },
{ name: "Files Changed", path: "/files", count: null },
{ name: "Verification", path: "/verification", count: null },
{ name: "Retro", path: "/retro", count: null },
{ name: "Usage", path: "/usage", count: null },
];

View file

@ -1,146 +0,0 @@
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from "@headlessui/react";
import {
CheckCircleIcon,
XCircleIcon,
MinusCircleIcon,
ChevronRightIcon,
} from "@heroicons/react/20/solid";
import {
statusConfig,
typeConfig,
getCriteriaSummary,
} from "../data/verifications";
import type {
VerificationResult,
VerificationType,
VerificationCategory,
} from "../data/verifications";
import { apiJson } from "../api";
import type { PaginatedRunVerificationList } from "@qltysh/fabro-api-client";
export async function loader({ request, params }: any) {
const { data: apiCategories } = await apiJson<PaginatedRunVerificationList>(`/runs/${params.id}/verification`, { request });
const categories: VerificationCategory[] = apiCategories.map((cat) => ({
name: cat.name,
question: cat.question,
status: cat.status as VerificationResult,
criteria: cat.controls.map((c) => ({
name: c.name,
description: c.description,
type: (c.type ?? null) as VerificationType | null,
status: c.status as VerificationResult,
})),
}));
return { categories };
}
function StatusIcon({
status,
className = "size-5",
}: {
status: VerificationResult;
className?: string;
}) {
switch (status) {
case "pass":
return <CheckCircleIcon className={`${className} text-mint`} />;
case "fail":
return <XCircleIcon className={`${className} text-coral`} />;
case "na":
return <MinusCircleIcon className={`${className} text-fg-muted`} />;
}
}
function TypeBadge({ type }: { type: VerificationType | null }) {
if (type === null) return null;
const config = typeConfig[type];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
function CategoryCard({ category }: { category: VerificationCategory }) {
const criteriaStats = getCriteriaSummary(category.criteria);
const applicable = criteriaStats.total - criteriaStats.na;
const config = statusConfig[category.status];
return (
<Disclosure
as="div"
defaultOpen={category.status === "fail"}
className={`rounded-md border border-line overflow-hidden border-l-2 ${config.border}`}
>
<DisclosureButton className="group flex w-full items-center gap-3 px-4 py-3.5 text-left transition-colors hover:bg-overlay">
<StatusIcon status={category.status} />
<div className="min-w-0 flex-1">
<div className="flex items-baseline gap-3">
<span className="shrink-0 text-sm font-semibold text-fg">
{category.name}
</span>
<span className="truncate text-xs text-fg-muted">
{category.question}
</span>
</div>
</div>
<span className="shrink-0 font-mono text-xs tabular-nums text-fg-muted">
{criteriaStats.passing}/{applicable}
</span>
<ChevronRightIcon className="size-4 shrink-0 text-fg-muted transition-transform duration-200 group-data-open:rotate-90" />
</DisclosureButton>
<DisclosurePanel
transition
className="origin-top transition duration-200 ease-out data-closed:-translate-y-1 data-closed:opacity-0"
>
<div className="border-t border-line">
<table className="w-full text-sm">
<tbody>
{category.criteria.map((criterion) => (
<tr
key={criterion.name}
className="border-b border-line last:border-b-0 cursor-pointer transition-colors hover:bg-overlay"
>
<td className="w-8 py-2.5 pl-5 pr-0">
<span
className={`inline-block size-2 rounded-full ${statusConfig[criterion.status].dot}`}
/>
</td>
<td className="whitespace-nowrap py-2.5 pl-2 pr-3 font-medium text-fg-2">
{criterion.name}
</td>
<td className="py-2.5 px-3 text-fg-muted">
{criterion.description || (
<span className="italic">Not configured</span>
)}
</td>
<td className="whitespace-nowrap py-2.5 pl-3 pr-4 text-right">
<TypeBadge type={criterion.type} />
</td>
</tr>
))}
</tbody>
</table>
</div>
</DisclosurePanel>
</Disclosure>
);
}
export default function RunVerifications({ loaderData }: any) {
const { categories } = loaderData;
return (
<div className="space-y-3">
{categories.map((category) => (
<CategoryCard key={category.name} category={category} />
))}
</div>
);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -61,7 +61,7 @@
<script type="module" src="/assets/chunk-sadshphz.js"></script>
<script type="module" src="/assets/chunk-pmthkscp.js"></script>
<script type="module" src="/assets/chunk-v61ks9f7.js"></script>
<script type="module" src="/assets/entry-psq398et.js"></script>
<script type="module" src="/assets/entry-k7vnt1v8.js"></script>
<script type="module" src="/assets/chunk-n1k68xa8.js"></script>
<script type="module" src="/assets/chunk-rsph5pvm.js"></script>
<script type="module" src="/assets/chunk-9t57pdty.js"></script>

View file

@ -12,7 +12,7 @@ tags:
- name: Human-in-the-Loop
description: Questions, answers, and steering for runs
- name: Run Outputs
description: Files and verifications produced by runs
description: Files produced by runs
- name: Run Internals
description: Internal run details (stages, turns, context, configuration)
- name: Workflows
@ -875,30 +875,6 @@ paths:
schema:
$ref: "#/components/schemas/ErrorResponse"
/api/v1/runs/{id}/verification:
get:
operationId: retrieveRunVerification
tags: [Run Outputs]
summary: Retrieve Run Verification
description: Returns verification results for a run, organized by criterion with individual control statuses.
parameters:
- $ref: "#/components/parameters/RunId"
- $ref: "#/components/parameters/PageLimit"
- $ref: "#/components/parameters/PageOffset"
responses:
"200":
description: Array of verification criteria with controls
content:
application/json:
schema:
$ref: "#/components/schemas/PaginatedRunVerificationList"
"404":
description: Run not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/api/v1/runs/{id}/settings:
get:
operationId: retrieveRunSettings
@ -2638,20 +2614,6 @@ components:
meta:
$ref: "#/components/schemas/PaginationMeta"
PaginatedRunVerificationList:
description: Paginated list of run verification categories.
type: object
required:
- data
- meta
properties:
data:
type: array
items:
$ref: "#/components/schemas/RunVerification"
meta:
$ref: "#/components/schemas/PaginationMeta"
PaginatedVerificationCriterionList:
description: Paginated list of verification criteria.
type: object
@ -4283,58 +4245,6 @@ components:
- analysis
- ai-analysis
RunVerificationControl:
description: A verification control result within a run.
type: object
required:
- name
- slug
- description
- type
- status
properties:
name:
type: string
description: Human-readable control name.
example: Motivation
slug:
type: string
description: URL-safe slug for linking to verification detail page.
example: motivation
description:
type: string
description: Short description of what the control verifies.
example: Origin of proposal identified
type:
$ref: "#/components/schemas/VerificationType"
status:
$ref: "#/components/schemas/VerificationResult"
RunVerification:
description: Verification results for a category within a run.
type: object
required:
- name
- question
- status
- controls
properties:
name:
type: string
description: Category name.
example: Traceability
question:
type: string
description: The guiding question for this verification category.
example: Do we understand what this change is and why we're making it?
status:
$ref: "#/components/schemas/VerificationResult"
controls:
type: array
description: Individual control results within this category.
items:
$ref: "#/components/schemas/RunVerificationControl"
SteerRequest:
description: Request body for sending inline steering guidance to a running agent.
type: object

View file

@ -493,24 +493,6 @@ fabro graph run.toml --format svg
| `-o, --output <FILE>` | Output file path. Defaults to stdout. |
| `-d, --direction <DIR>` | Graph direction: `lr` or `tb`. If omitted, uses the Graphviz file's own `rankdir`. |
## `fabro skill install`
Install the built-in `fabro-create-workflow` skill for AI assistants (Claude Code, Codex). The skill teaches AI assistants Fabro's Graphviz syntax, node types, and run configuration format.
```bash
# Install into the current project (.claude/skills/ or .agents/skills/)
fabro skill install --for project --dir claude
# Install for all projects (user-level, ~/.claude/skills/)
fabro skill install --for user --dir claude
```
| Flag | Description |
|---|---|
| `--for <SCOPE>` | `user` (default) — installs to `~/<dir>/skills/`. `project` — installs to `./<dir>/skills/`. |
| `--dir <DIR>` | Directory convention: `claude` (`.claude/skills/`) or `agents` (`.agents/skills/`). Required. |
| `--force` | Overwrite an existing installation without prompting. |
## `fabro rewind`
Rewind a workflow run to an earlier checkpoint. This resets both the run branch and metadata branch refs so that `fabro resume` continues from the target checkpoint.

View file

@ -648,33 +648,6 @@ pub(crate) struct SettingsArgs {
pub(crate) workflow: Option<PathBuf>,
}
#[derive(Clone, ValueEnum)]
pub(crate) enum SkillDir {
Claude,
Agents,
}
#[derive(Clone, ValueEnum)]
pub(crate) enum SkillScope {
User,
Project,
}
#[derive(Args)]
pub(crate) struct SkillInstallArgs {
/// Where to install: user-level or project-level
#[arg(long = "for", default_value = "user")]
pub(crate) scope: SkillScope,
/// Target directory convention
#[arg(long)]
pub(crate) dir: SkillDir,
/// Overwrite existing skill without prompting
#[arg(long)]
pub(crate) force: bool,
}
#[derive(Args)]
pub(crate) struct PrCreateArgs {
#[command(flatten)]
@ -945,9 +918,6 @@ pub(crate) enum Commands {
Install(InstallArgs),
/// Pull request operations
Pr(PrNamespace),
/// Skill management
#[command(hide = true)]
Skill(SkillNamespace),
/// Manage server-owned secrets
Secret(SecretNamespace),
/// Inspect effective settings
@ -1045,9 +1015,6 @@ impl Commands {
WorkflowCommand::List(_) => "workflow list",
WorkflowCommand::Create(_) => "workflow create",
},
Self::Skill(ns) => match &ns.command {
SkillCommand::Install(_) => "skill install",
},
Self::Discord => "discord",
Self::Docs => "docs",
Self::Upgrade(_) => "upgrade",
@ -1253,10 +1220,6 @@ pub(crate) enum RepoCommand {
pub(crate) struct RepoInitArgs {
#[command(flatten)]
pub(crate) target: ServerTargetArgs,
/// Also install the fabro-create-workflow skill
#[arg(long, hide = true)]
pub(crate) skill: bool,
}
#[derive(Args)]
@ -1296,15 +1259,3 @@ pub(crate) struct CompletionArgs {
/// Shell to generate completions for
pub shell: clap_complete::Shell,
}
#[derive(Args)]
pub(crate) struct SkillNamespace {
#[command(subcommand)]
pub(crate) command: SkillCommand,
}
#[derive(Subcommand)]
pub(crate) enum SkillCommand {
/// Install a built-in skill
Install(SkillInstallArgs),
}

View file

@ -15,7 +15,6 @@ pub(crate) mod runs;
pub(crate) mod sandbox;
pub(crate) mod secret;
pub(crate) mod server;
pub(crate) mod skill;
pub(crate) mod store;
pub(crate) mod system;
pub(crate) mod upgrade;

View file

@ -10,10 +10,6 @@ pub(crate) async fn dispatch(ns: RepoNamespace, globals: &GlobalArgs) -> Result<
match ns.command {
RepoCommand::Init(args) => {
let created = init::run_init(&args, globals).await?;
if args.skill {
let base = std::env::current_dir()?.join(".claude").join("skills");
super::skill::install_skill_to(&base)?;
}
if globals.json {
print_json_pretty(&serde_json::json!({ "created": created }))?;
}

View file

@ -1,134 +0,0 @@
use std::path::Path;
use anyhow::{Result, bail};
use tracing::{debug, info};
use crate::args::{GlobalArgs, SkillDir, SkillInstallArgs, SkillScope};
use crate::shared::{absolute_or_current, print_json_pretty};
const SKILL_MD: &str = include_str!("../../../../../../skills/fabro-create-workflow/SKILL.md");
const REF_DOT_LANGUAGE: &str =
include_str!("../../../../../../skills/fabro-create-workflow/references/dot-language.md");
const REF_EXAMPLE_WORKFLOWS: &str =
include_str!("../../../../../../skills/fabro-create-workflow/references/example-workflows.md");
const REF_RUN_CONFIGURATION: &str =
include_str!("../../../../../../skills/fabro-create-workflow/references/run-configuration.md");
const SKILL_FILES: &[(&str, &str)] = &[
("SKILL.md", SKILL_MD),
("references/dot-language.md", REF_DOT_LANGUAGE),
("references/example-workflows.md", REF_EXAMPLE_WORKFLOWS),
("references/run-configuration.md", REF_RUN_CONFIGURATION),
];
/// Install all skill files under `base_dir/fabro-create-workflow/`.
pub(crate) fn install_skill_to(base_dir: &Path) -> Result<()> {
let skill_dir = base_dir.join("fabro-create-workflow");
for (rel_path, content) in SKILL_FILES {
let dest = skill_dir.join(rel_path);
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent)?;
}
debug!(file = %rel_path, "Writing skill file");
std::fs::write(&dest, content)?;
}
info!(path = %skill_dir.display(), "Skill installed");
Ok(())
}
pub(super) fn run_skill_install(args: &SkillInstallArgs, globals: &GlobalArgs) -> Result<()> {
let base_dir = resolve_base_dir(&args.scope, &args.dir)?;
let skill_dir = base_dir.join("fabro-create-workflow");
if globals.json && skill_dir.exists() && !args.force {
globals.require_no_json()?;
}
if skill_dir.exists() && !args.force {
let confirm = dialoguer::Confirm::new()
.with_prompt(format!(
"Skill directory already exists at {}. Overwrite?",
skill_dir.display()
))
.default(false)
.interact()?;
if !confirm {
bail!("Aborted: skill directory already exists");
}
}
install_skill_to(&base_dir)?;
if globals.json {
print_json_pretty(&serde_json::json!({
"skill": "fabro-create-workflow",
"path": absolute_or_current(&skill_dir),
"files": SKILL_FILES.iter().map(|(rel_path, _)| (*rel_path).to_string()).collect::<Vec<_>>(),
}))?;
}
Ok(())
}
fn resolve_base_dir(scope: &SkillScope, dir: &SkillDir) -> Result<std::path::PathBuf> {
let dir_name = match dir {
SkillDir::Claude => ".claude",
SkillDir::Agents => ".agents",
};
let root = match scope {
SkillScope::User => {
dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?
}
SkillScope::Project => std::env::current_dir()?,
};
Ok(root.join(dir_name).join("skills"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn embedded_files_are_non_empty() {
assert!(!SKILL_MD.is_empty());
assert!(!REF_DOT_LANGUAGE.is_empty());
assert!(!REF_EXAMPLE_WORKFLOWS.is_empty());
assert!(!REF_RUN_CONFIGURATION.is_empty());
}
#[test]
fn install_writes_all_files() {
let tmp = tempfile::tempdir().unwrap();
let base = tmp.path().join("skills");
install_skill_to(&base).unwrap();
for (rel_path, content) in SKILL_FILES {
let path = base.join("fabro-create-workflow").join(rel_path);
assert!(path.exists(), "Missing file: {rel_path}");
let written = std::fs::read_to_string(&path).unwrap();
assert_eq!(written, *content, "Content mismatch: {rel_path}");
}
}
#[test]
fn install_overwrites_existing_files() {
let tmp = tempfile::tempdir().unwrap();
let base = tmp.path().join("skills");
install_skill_to(&base).unwrap();
let sentinel_path = base.join("fabro-create-workflow/SKILL.md");
std::fs::write(&sentinel_path, "old content").unwrap();
install_skill_to(&base).unwrap();
let content = std::fs::read_to_string(&sentinel_path).unwrap();
assert_eq!(content, SKILL_MD);
}
}

View file

@ -1,13 +0,0 @@
mod install;
use anyhow::Result;
use crate::args::{GlobalArgs, SkillCommand, SkillNamespace};
pub(crate) use install::install_skill_to;
pub(crate) fn dispatch(ns: SkillNamespace, globals: &GlobalArgs) -> Result<()> {
match ns.command {
SkillCommand::Install(args) => install::run_skill_install(&args, globals),
}
}

View file

@ -227,7 +227,6 @@ async fn main_inner() -> (String, Result<()>) {
Commands::Secret(ns) => commands::secret::dispatch(ns, &globals).await?,
Commands::Settings(args) => commands::config::execute(&args, &globals).await?,
Commands::Workflow(ns) => commands::workflow::dispatch(ns, &globals)?,
Commands::Skill(ns) => commands::skill::dispatch(ns, &globals)?,
Commands::Upgrade(args) => {
commands::upgrade::run_upgrade(args, &globals).await?;
}

View file

@ -72,46 +72,3 @@ fn test_repo_deinit_fails_when_not_initialized() {
error: not initialized fabro.toml not found
");
}
#[test]
fn test_repo_init_skill_installs_skill_files() {
let context = test_context!();
context.git_init();
context.repo().args(["init", "--skill"]).assert().success();
// Skill files should be installed under .claude/skills/fabro-create-workflow/
let skill_dir = context
.temp_dir
.join(".claude/skills/fabro-create-workflow");
assert!(skill_dir.join("SKILL.md").exists(), "SKILL.md should exist");
assert!(
skill_dir.join("references/dot-language.md").exists(),
"dot-language.md should exist"
);
}
#[test]
fn test_repo_init_help_does_not_show_skill() {
let context = test_context!();
let mut cmd = context.repo();
cmd.args(["init", "--help"]);
fabro_snapshot!(context.filters(), cmd, @"
success: true
exit_code: 0
----- stdout -----
Initialize a new project
Usage: fabro repo init [OPTIONS]
Options:
--json Output as JSON [env: FABRO_JSON=]
--server <SERVER> Fabro server target: http(s) URL or absolute Unix socket path [env: FABRO_SERVER=]
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
--quiet Suppress non-essential output [env: FABRO_QUIET=]
--verbose Enable verbose output [env: FABRO_VERBOSE=]
-h, --help Print help
----- stderr -----
");
}

View file

@ -117,15 +117,6 @@ pub(crate) async fn get_run_usage(
(StatusCode::OK, Json(runs::usage())).into_response()
}
pub(crate) async fn get_run_verification(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
Query(pagination): Query<PaginationParams>,
) -> Response {
paginated_response(runs::verifications(), &pagination)
}
pub(crate) async fn get_run_settings(
_auth: AuthenticatedService,
State(_state): State<Arc<AppState>>,
@ -1495,10 +1486,6 @@ mod runs {
}
}
pub(super) fn verifications() -> Vec<RunVerification> {
super::verifications::run_verifications()
}
pub(super) fn questions() -> Vec<ApiQuestion> {
vec![
ApiQuestion {
@ -2037,7 +2024,7 @@ mod verifications {
f1: Option<f64>,
pass_at_1: Option<f64>,
evaluations: &'static [VerificationResult],
// Run-level status
#[allow(dead_code)]
run_status: VerificationResult,
// Detail fields
detail_description: &'static str,
@ -2898,24 +2885,6 @@ mod verifications {
.collect()
}
fn category_run_status(cat: &CategoryDef) -> VerificationResult {
if cat
.controls
.iter()
.any(|c| c.run_status == VerificationResult::Fail)
{
VerificationResult::Fail
} else if cat
.controls
.iter()
.all(|c| c.run_status == VerificationResult::Na)
{
VerificationResult::Na
} else {
VerificationResult::Pass
}
}
fn slugify(name: &str) -> String {
name.to_lowercase()
.chars()
@ -3051,28 +3020,6 @@ mod verifications {
}
None
}
pub(super) fn run_verifications() -> Vec<RunVerification> {
ALL_CATEGORIES
.iter()
.map(|cat| RunVerification {
name: cat.name.into(),
question: cat.question.into(),
status: category_run_status(cat),
controls: cat
.controls
.iter()
.map(|c| RunVerificationControl {
name: c.name.into(),
slug: c.slug.into(),
description: c.description.into(),
type_: c.type_,
status: c.run_status,
})
.collect(),
})
.collect()
}
}
mod signoffs {

View file

@ -420,7 +420,6 @@ fn demo_routes() -> Router<Arc<AppState>> {
get(not_implemented),
)
.route("/runs/{id}/usage", get(demo::get_run_usage))
.route("/runs/{id}/verification", get(demo::get_run_verification))
.route("/runs/{id}/settings", get(demo::get_run_settings))
.route("/runs/{id}/steer", post(demo::steer_run_stub))
.route("/runs/{id}/preview", post(demo::generate_preview_url_stub))
@ -531,7 +530,6 @@ fn real_routes() -> Router<Arc<AppState>> {
get(get_stage_artifact),
)
.route("/runs/{id}/usage", get(not_implemented))
.route("/runs/{id}/verification", get(not_implemented))
.route("/runs/{id}/settings", get(not_implemented))
.route("/runs/{id}/steer", post(not_implemented))
.route("/runs/{id}/preview", post(generate_preview_url))

View file

@ -85,10 +85,6 @@ const ENDPOINTS: &[PaginatedEndpoint] = &[
path: "/api/v1/runs/run-1/stages",
name: "listRunStages",
},
PaginatedEndpoint {
path: "/api/v1/runs/run-1/verification",
name: "retrieveRunVerification",
},
];
#[tokio::test]

View file

@ -119,7 +119,6 @@ models/paginated-retro-list.ts
models/paginated-run-file-list.ts
models/paginated-run-list.ts
models/paginated-run-stage-list.ts
models/paginated-run-verification-list.ts
models/paginated-saved-query-list.ts
models/paginated-session-list.ts
models/paginated-signoff-list.ts
@ -173,8 +172,6 @@ models/run-status-response.ts
models/run-status.ts
models/run-timings.ts
models/run-usage.ts
models/run-verification-control.ts
models/run-verification.ts
models/sandbox-file-entry.ts
models/sandbox-file-list-response.ts
models/sandbox-resources.ts

View file

@ -24,8 +24,6 @@ import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError
// @ts-ignore
import type { ErrorResponse } from '../models';
// @ts-ignore
import type { PaginatedRunVerificationList } from '../models';
// @ts-ignore
import type { RunUsage } from '../models';
/**
* RunOutputsApi - axios parameter creator
@ -68,57 +66,6 @@ export const RunOutputsApiAxiosParamCreator = function (configuration?: Configur
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns verification results for a run, organized by criterion with individual control statuses.
* @summary Retrieve Run Verification
* @param {string} id Unique run identifier (ULID).
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveRunVerification: async (id: string, pageLimit?: number, pageOffset?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('retrieveRunVerification', 'id', id)
const localVarPath = `/api/v1/runs/{id}/verification`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (pageLimit !== undefined) {
localVarQueryParameter['page[limit]'] = pageLimit;
}
if (pageOffset !== undefined) {
localVarQueryParameter['page[offset]'] = pageOffset;
}
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
@ -146,21 +93,6 @@ export const RunOutputsApiFp = function(configuration?: Configuration) {
const localVarOperationServerBasePath = operationServerMap['RunOutputsApi.retrieveRunUsage']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns verification results for a run, organized by criterion with individual control statuses.
* @summary Retrieve Run Verification
* @param {string} id Unique run identifier (ULID).
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async retrieveRunVerification(id: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PaginatedRunVerificationList>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveRunVerification(id, pageLimit, pageOffset, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['RunOutputsApi.retrieveRunVerification']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
}
};
@ -180,18 +112,6 @@ export const RunOutputsApiFactory = function (configuration?: Configuration, bas
retrieveRunUsage(id: string, options?: RawAxiosRequestConfig): AxiosPromise<RunUsage> {
return localVarFp.retrieveRunUsage(id, options).then((request) => request(axios, basePath));
},
/**
* Returns verification results for a run, organized by criterion with individual control statuses.
* @summary Retrieve Run Verification
* @param {string} id Unique run identifier (ULID).
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveRunVerification(id: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise<PaginatedRunVerificationList> {
return localVarFp.retrieveRunVerification(id, pageLimit, pageOffset, options).then((request) => request(axios, basePath));
},
};
};
@ -209,18 +129,5 @@ export class RunOutputsApi extends BaseAPI {
public retrieveRunUsage(id: string, options?: RawAxiosRequestConfig) {
return RunOutputsApiFp(this.configuration).retrieveRunUsage(id, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns verification results for a run, organized by criterion with individual control statuses.
* @summary Retrieve Run Verification
* @param {string} id Unique run identifier (ULID).
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public retrieveRunVerification(id: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig) {
return RunOutputsApiFp(this.configuration).retrieveRunVerification(id, pageLimit, pageOffset, options).then((request) => request(this.axios, this.basePath));
}
}

View file

@ -96,7 +96,6 @@ export * from './paginated-retro-list';
export * from './paginated-run-file-list';
export * from './paginated-run-list';
export * from './paginated-run-stage-list';
export * from './paginated-run-verification-list';
export * from './paginated-saved-query-list';
export * from './paginated-session-list';
export * from './paginated-signoff-list';
@ -150,8 +149,6 @@ export * from './run-status-record';
export * from './run-status-response';
export * from './run-timings';
export * from './run-usage';
export * from './run-verification';
export * from './run-verification-control';
export * from './sandbox-file-entry';
export * from './sandbox-file-list-response';
export * from './sandbox-resources';

View file

@ -1,185 +0,0 @@
---
name: fabro-create-workflow
description: Create Fabro workflow DOT graphs and TOML run configurations from natural language requirements. Use when the user wants to create a new workflow, build a pipeline, design a multi-step agent process, or write a .fabro or .toml file for Fabro. Covers topology selection, node types, model assignment, edge routing, and run configuration.
---
# Fabro Create Workflow
Turn requirements into a runnable Fabro workflow: a `.fabro` graph file defining the pipeline structure and an optional `.toml` run configuration.
## Workflow
### Step 1: Fetch Current Model Catalog
Run `fabro model list` to get available models and providers. Never guess model IDs or provider names -- they change frequently.
### Step 2: Understand Requirements
Clarify what the workflow should accomplish:
- What is the end goal?
- What tools or languages are involved?
- Does it need human approval gates?
- Should multiple models or providers be used?
- Is parallelism needed (e.g., multi-perspective review, ensemble)?
- Does it need a verify/fix loop?
- What sandbox environment is appropriate (local, docker, daytona)?
### Step 3: Choose Topology
Pick the simplest topology that satisfies the requirements. See `references/example-workflows.md` for complete examples of each pattern.
| Pattern | When to use |
|---|---|
| **Linear** | Simple sequential steps, no branching |
| **Command-then-analyze** | Shell output feeds into LLM analysis |
| **Implement-test-fix loop** | Code generation with validation cycle |
| **Human approval gate** | Needs human review before proceeding |
| **Plan-approve-implement** | Complex changes needing upfront planning |
| **Parallel fan-out** | Independent analyses merged into synthesis |
| **Multi-model ensemble** | Multiple providers give independent opinions |
| **Production pipeline** | Toolchain checks + implement + verify + fixup loops |
Combine patterns as needed. For example, a production pipeline might include a human gate after planning and a parallel fan-out for review.
### Step 4: Write the DOT Graph
See `references/dot-language.md` for the full language reference.
**Required elements:**
1. `digraph Name { ... }` wrapper
2. `graph [goal="..."]` attribute
3. `rankdir=LR` (preferred for readability)
4. Exactly one `start [shape=Mdiamond, label="Start"]`
5. Exactly one `exit [shape=Msquare, label="Exit"]`
**Choose node shapes by purpose:**
- `box` (default) -- agent with tools (implement, fix, write code)
- `tab` -- single LLM call without tools (analyze, plan, review, synthesize)
- `parallelogram` -- shell command (build, test, lint)
- `diamond` -- conditional routing (no prompt, only conditions on edges)
- `hexagon` -- human decision gate
- `component` -- parallel fan-out
- `tripleoctagon` -- merge parallel results
**Prompt guidelines:**
- Be specific and actionable in prompts
- Tell the agent exactly what to do, what files to create/modify, what output to produce
- Use `shape=tab` for nodes that only need to think, not act
- Use `prompt="@path/to/file.md"` for long prompts (path relative to DOT file)
- Set `reasoning_effort="low"` on simple analysis or summary nodes
**Edge routing:**
- Use `condition="outcome=success"` and unconditional fallback for check gates
- Diamond nodes must have multiple outgoing edges with conditions
- Use `max_visits` on fix/retry nodes to prevent infinite loops (typically 3)
- Use `goal_gate=true` on verification nodes that must pass for the workflow to succeed
- Set `retry_target` on goal gates to specify where to jump on failure
### Step 5: Assign Models via Stylesheet
Use `model_stylesheet` for model assignment rather than per-node attributes:
```dot
graph [model_stylesheet="
* { model: claude-sonnet-4-6;}
.coding { model: claude-opus-4-6;}
.review { model: gemini-3.1-pro-preview;}
"]
```
- Use `*` for the default model (usually a fast/cheap model)
- Use `.class` selectors for role-based assignment (`.coding`, `.review`, `.verify`)
- Use `#nodeid` selectors for specific node overrides
- Assign `class="coding"` etc. on nodes to match stylesheet rules
- **Critical:** Use semicolons between properties in stylesheet rules
**Model selection heuristics:**
- Fast/cheap models for simple analysis, summaries, routing: `claude-haiku-4-5`, `gemini-3-flash-preview`, `gpt-5-mini`
- Strong models for coding, complex reasoning: `claude-opus-4-6`, `claude-sonnet-4-6`, `gpt-5.4`
- Use `reasoning_effort="high"` for complex coding tasks
- For ensembles, pick models from different providers for diversity
### Step 6: Write the TOML Run Configuration (if needed)
See `references/run-configuration.md` for the full reference.
A TOML file is optional for simple workflows (you can run `fabro run workflow.fabro` directly). Create one when you need:
- Sandbox configuration (provider, environment variables)
- Setup commands (install dependencies)
- Variable definitions
- LLM fallbacks
- Hooks
- Asset collection
Minimal TOML:
```toml
version = 1
graph = "workflow.fabro"
```
Common additions:
```toml
[sandbox]
provider = "local"
[sandbox.local]
worktree_mode = "always"
[sandbox.env]
NODE_ENV = "test"
```
### Step 7: Validate
Run `fabro preflight workflow.toml` (or `fabro preflight workflow.fabro`) to validate without executing.
If validation fails, fix the reported errors and re-validate.
## Guardrails
- **Diamond nodes route only.** Never put a `prompt` on a `diamond` node -- it only evaluates edge conditions.
- **All nodes reachable from start.** No orphan nodes.
- **No edges into start or out of exit.**
- **LLM nodes need prompts.** Every `box` and `tab` node must have a `prompt` attribute.
- **Conditional nodes need multiple outgoing edges** with `condition` attributes.
- **Prevent infinite loops.** Use `max_visits` on retry/fix nodes. Typical value: 3.
- **Use `goal_gate=true`** on critical verification steps that must succeed.
- **Use `outcome=success` conditions** on edges leaving command/conditional nodes.
- **Model IDs must match the catalog.** Always run `fabro model list` first.
- **Semicolons in stylesheets.** Properties must be separated by semicolons.
## File Organization
Place workflow files together in a directory:
```
my-workflow/
workflow.fabro # the graph
workflow.toml # run configuration (optional)
prompts/ # external prompt files (optional)
implement.md
review.md
```
## Running Workflows
```bash
fabro run workflow.fabro # run graph directly
fabro run workflow.toml # run with TOML config
fabro run workflow.toml --dry-run # simulated LLM backend
fabro run workflow.toml --no-retro # skip retro (faster for testing)
fabro run workflow.toml --auto-approve # auto-approve human gates
fabro run workflow.toml --model claude-opus-4-6 # override model
fabro run workflow.toml --sandbox local # override sandbox
fabro validate workflow.fabro # validate only
```
## References
- `references/dot-language.md` -- Complete DOT language reference (node types, attributes, edges, conditions, stylesheets, fidelity, variables)
- `references/run-configuration.md` -- Complete TOML run configuration reference (sandbox, setup, hooks, LLM, vars, assets)
- `references/example-workflows.md` -- 9 complete example workflows from simple to production-grade
{{user_input}}

View file

@ -1,228 +0,0 @@
# DOT Language Reference for Arc Workflows
## Graph Structure
Every workflow is a `digraph` with a name and body. Three required elements:
1. A `goal` attribute on the graph
2. Exactly one `start` node with `shape=Mdiamond`
3. Exactly one `exit` node with `shape=Msquare`
Only `digraph` is supported (not `graph` or `strict`).
```dot
digraph MyWorkflow {
graph [goal="Describe the objective"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
// nodes and edges here
start -> node1 -> node2 -> exit
}
```
Workflows **can include loops** (unlike DAGs).
## Value Types
| Type | Syntax | Examples |
|---|---|---|
| String | Double-quoted | `"Run tests"` |
| Integer | Bare digits | `42`, `-1` |
| Float | Digits with decimal | `3.14` |
| Boolean | Keywords | `true`, `false` |
| Duration | Integer + unit | `250ms`, `30s`, `15m`, `2h`, `1d` |
| Bare string | Identifier | `claude-sonnet-4-6` |
Comments: `//` line and `/* */` block.
## Graph-Level Attributes
| Attribute | Type | Description |
|---|---|---|
| `goal` | String | Workflow objective (required) |
| `rankdir` | Identifier | Layout: `LR` or `TB` |
| `model_stylesheet` | String | CSS-like model assignment rules |
| `default_max_retries` | Integer | Default retry count for all nodes (default: 3) |
| `retry_target` | String | Default node to jump to on retry |
| `fallback_retry_target` | String | Fallback retry target |
| `default_fidelity` | String | Default fidelity for all nodes |
| `default_thread` | String | Default thread ID for all nodes |
| `max_node_visits` | Integer | Max visits per node (0 = unlimited) |
| `stall_timeout` | Duration | Timeout for stalled workflows (default: 1800s) |
## Node Types (by shape)
| Shape | Handler | Purpose |
|---|---|---|
| `Mdiamond` | start | Entry point (exactly one) |
| `Msquare` | exit | Terminal (exactly one) |
| `box` (default) | agent | Multi-turn LLM with tool access |
| `tab` | prompt | Single LLM call, no tools |
| `parallelogram` | command | Execute a shell script |
| `hexagon` | human | Human-in-the-loop decision gate |
| `diamond` | conditional | Route based on conditions |
| `component` | parallel | Fan-out to concurrent branches |
| `tripleoctagon` | parallel.fan_in | Merge parallel branch results |
| `insulator` | wait | Pause for a duration |
| `house` | stack.manager_loop | Sub-workflow orchestration |
### Agent Nodes (box, default)
Multi-turn LLM with tools (shell, read_file, write_file, grep, glob, web_search, web_fetch, edit_file).
Key attributes: `prompt`, `reasoning_effort` (low/medium/high), `max_tokens`, `fidelity`, `thread_id`, `timeout`, `backend` (api or cli), `model`, `provider`, `project_memory`.
### Prompt Nodes (tab)
Single LLM call, no tool use. Same attributes as agent but never invokes tools.
### Command Nodes (parallelogram)
Run a shell script. Attributes: `script` (shell command), `language` ("shell" or "python").
### Human Nodes (hexagon)
Pause for human choice. Edge labels define options. Supports:
- Keyboard accelerators: `[A] Approve`, `A) Approve`, `A - Approve`
- Freeform input: `freeform=true` on an edge
- Default on timeout: `human.default_choice`
### Conditional Nodes (diamond)
Route execution based on conditions. Must have multiple outgoing edges with `condition` attributes. No prompt attribute.
### Parallel Fan-Out (component)
Attributes: `join_policy` (wait_all, first_success), `max_parallel` (default: 4).
### Fan-In / Merge (tripleoctagon)
Collects results from parallel branches. Results available as `parallel_results.json`.
### Wait Nodes (insulator)
Attribute: `duration` (e.g. `"30s"`, `"2m"`).
### Sub-workflow (house)
Attributes: `stack.child_dotfile`, `stack.child_dot_source`, `manager.max_cycles` (default: 1000), `manager.poll_interval` (default: 45s), `manager.stop_condition`.
## Common Node Attributes
| Attribute | Description |
|---|---|
| `label` | Display name |
| `class` | Space-separated classes for stylesheet targeting |
| `max_visits` | Max times this node can execute |
| `goal_gate` | When `true`, workflow fails if this node doesn't succeed |
| `max_retries` | Override default retry count |
| `retry_policy` | Preset: `none`, `standard`, `aggressive`, `linear`, `patient` |
| `retry_target` | Node ID to jump to on retry |
| `auto_status` | Auto-generate status updates |
## Edges and Transitions
After each node, Arc evaluates outgoing edges in priority order:
1. **Condition match** -- edges with `condition`, highest `weight` wins
2. **Preferred label** -- from human gate or LLM routing directive
3. **Suggested next** -- node suggests a next node ID
4. **Unconditional fallback** -- edges without conditions, `weight` tiebreak
Edge attributes: `label`, `condition`, `weight` (higher wins, default: 0), `fidelity`, `thread_id`, `loop_restart`.
### Condition Grammar
```
Expr ::= OrExpr
OrExpr ::= AndExpr ('||' AndExpr)*
AndExpr ::= UnaryExpr ('&&' UnaryExpr)*
UnaryExpr ::= '!' UnaryExpr | Clause
Clause ::= Key Op Value | Key
```
Operators: `=`, `!=`, `>`, `<`, `>=`, `<=`, `contains`, `matches`.
Common patterns:
- `condition="outcome=success"` -- stage succeeded
- `condition="outcome=fail"` -- stage failed
- `condition="context.tests_passed=true"` -- check context variable
## Model Stylesheets
CSS-like syntax for assigning models to nodes:
```dot
graph [model_stylesheet="
* { model: claude-haiku-4-5;}
.coding { model: claude-sonnet-4-6;}
#review { model: gemini-3.1-pro-preview;}
"]
```
Selectors by specificity (low to high): `*` (universal, 0), shape name (1), `.class` (2), `#nodeid` (3). Higher specificity wins. Same specificity: last rule wins. Explicit node attributes override stylesheets.
Properties: `model`, `provider` (optional — auto-inferred from the model catalog), `reasoning_effort`, `backend`.
**Critical:** Use semicolons between properties (e.g. `model: foo; provider: bar;`).
## Variables
Define in `[vars]` section of TOML. Expanded into DOT source before parsing with `$variable` syntax. Undefined variables raise an error. Escape literal `$` with `$$`. Built-in: `$goal`.
## External Prompt Files
Reference external files with `prompt="@path/to/file.md"` (path relative to DOT file).
## Subgraphs
Group nodes visually and apply scoped defaults:
```dot
subgraph cluster_impl {
label = "Implementation"
node [thread_id="impl", fidelity="full"]
plan [label="Plan"]
implement [label="Implement"]
}
```
When a subgraph has a `label`, it's converted to a CSS class applied to all nodes within.
## Validation Rules
Enforced at parse time:
- Exactly one start node and one exit node
- All nodes reachable from start
- No incoming edges to start, no outgoing edges from exit
- Edge targets reference existing nodes
- Condition expressions parse correctly
- Stylesheet syntax is valid
- LLM nodes have a `prompt` attribute
- `@file` references point to existing files
- Conditional (diamond) nodes have multiple outgoing edges with conditions
## Retry Policies
| Preset | Attempts | Backoff |
|---|---|---|
| `none` | 1 | No retries |
| `standard` | 5 | 5s initial, 2x exponential |
| `aggressive` | 5 | 500ms initial, 2x exponential |
| `linear` | 3 | 500ms fixed |
| `patient` | 3 | 2s initial, 3x exponential |
## Fidelity Levels
Controls how much prior context is passed to a node:
| Value | Behavior |
|---|---|
| `compact` | Structured summary (default) |
| `full` | Complete context |
| `summary:high` | Detailed summary |
| `summary:medium` | Moderate summary |
| `summary:low` | Brief summary |
| `truncate` | Minimal -- only goal and run ID |

View file

@ -1,265 +0,0 @@
# Example Workflows
Use these as starting points. Choose the simplest topology that fits the requirements.
## 1. Linear Pipeline (simplest)
One-shot prompt, no tools:
```dot
digraph Hello {
graph [goal="Write a haiku about software workflows"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
compose [label="Compose", prompt="Write a haiku (5-7-5 syllable) about software workflows. Output only the haiku, nothing else.", shape=tab, reasoning_effort="low"]
start -> compose -> exit
}
```
## 2. Command-Then-Analyze Pipeline
Shell command feeds into LLM analysis:
```dot
digraph Pipeline {
graph [goal="Analyze the current directory and suggest improvements"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
scan [label="Scan Files", shape=parallelogram, script="find . -maxdepth 2 -type f | head -30"]
analyze [label="Analyze", prompt="Review the file listing from the previous step. Identify what kind of project this is and summarize its structure in 3-4 bullet points.", shape=tab, reasoning_effort="low"]
suggest [label="Suggest", prompt="Based on the analysis, suggest 3 concrete improvements to the project structure. Be specific and actionable.", shape=tab, reasoning_effort="low"]
start -> scan -> analyze -> suggest -> exit
}
```
## 3. Implement-Test-Fix Loop
Agent writes code, command validates, conditional routes back on failure:
```dot
digraph BranchLoop {
graph [goal="Create a Python script that passes its test suite"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
plan [label="Plan", prompt="Plan a small Python script (fizzbuzz.py) and a test file (test_fizzbuzz.py) using pytest. Describe what you will create.", shape=tab, reasoning_effort="low"]
implement [label="Implement", prompt="Create fizzbuzz.py and test_fizzbuzz.py as planned. Write the files to disk."]
validate [label="Validate", shape=parallelogram, script="python3 -m pytest test_fizzbuzz.py -v 2>&1 || true"]
gate [shape=diamond, label="Tests passing?"]
start -> plan -> implement -> validate -> gate
gate -> exit [label="Pass", condition="outcome=success"]
gate -> implement [label="Fix"]
}
```
## 4. Human Approval Gate
Draft, get human approval, then apply:
```dot
digraph HumanGate {
graph [goal="Propose and implement a README improvement"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
draft [label="Draft Proposal", prompt="Read the README.md (or note its absence). Propose a specific improvement. Describe your proposed changes clearly but do NOT make any changes yet.", shape=tab]
approve [label="Approve Changes?", shape=hexagon]
apply [label="Apply Changes", prompt="Apply the proposed README changes that were approved."]
skip [label="Skip", prompt="Acknowledged. No changes made.", shape=tab, reasoning_effort="low"]
start -> draft -> approve
approve -> apply [label="[A] Approve"]
approve -> skip [label="[S] Skip"]
apply -> exit
skip -> exit
}
```
## 5. Plan-Approve-Implement with Revision Loop
```dot
digraph PlanImplement {
graph [goal="Plan, approve, implement, and simplify a change"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
plan [label="Plan", prompt="Analyze the goal and codebase. Write a clear, step-by-step implementation plan to plan.md. Include what files will change and why.", reasoning_effort="high"]
approve [shape=hexagon, label="Approve Plan"]
implement [label="Implement", prompt="Read plan.md and implement every step. Make all the code changes described in the plan."]
simplify [label="Simplify", prompt="Review the changes just made. Simplify and clean up the code without changing behavior."]
start -> plan -> approve
approve -> implement [label="[A] Approve"]
approve -> plan [label="[R] Revise"]
implement -> simplify -> exit
}
```
## 6. Parallel Fan-Out Review
Multiple independent analyses merged into a synthesis:
```dot
digraph Parallel {
graph [goal="Perform a multi-perspective code review"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
fork [label="Fork Analysis", shape=component, join_policy="wait_all"]
security [label="Security Audit", prompt="Examine the codebase for security concerns: hardcoded secrets, injection risks, unsafe dependencies. List findings as bullet points.", shape=tab, reasoning_effort="low"]
architecture [label="Architecture Review", prompt="Assess the codebase architecture: separation of concerns, dependency structure, modularity. List findings as bullet points.", shape=tab, reasoning_effort="low"]
quality [label="Code Quality", prompt="Check code quality: naming conventions, dead code, test coverage gaps, error handling. List findings as bullet points.", shape=tab, reasoning_effort="low"]
merge [label="Merge Findings", shape=tripleoctagon]
report [label="Final Report", prompt="Synthesize the security, architecture, and code quality findings into a prioritized summary report with top 5 action items.", shape=tab]
start -> fork
fork -> security
fork -> architecture
fork -> quality
security -> merge
architecture -> merge
quality -> merge
merge -> report -> exit
}
```
## 7. Multi-Model with Stylesheet
Different models for different roles:
```dot
digraph MultiModel {
graph [
goal="Build and review a utility function using multiple models",
model_stylesheet="
* { model: claude-haiku-4-5;reasoning_effort: low; }
.coding { model: claude-sonnet-4-6;reasoning_effort: high; }
#review { model: claude-sonnet-4-6;reasoning_effort: high; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
spec [label="Write Spec", prompt="Write a brief spec for a TypeScript string utility module with 3 functions: slugify, truncate, and capitalize. Output the spec only.", shape=tab]
implement [label="Implement", prompt="Implement the TypeScript string utility module from the spec. Write it to string-utils.ts.", class="coding"]
test [label="Write Tests", prompt="Write tests for the string utility module using Bun's test runner. Write to string-utils.test.ts.", class="coding"]
review [label="Code Review", prompt="Review the implementation and tests. Check for edge cases, type safety, and correctness. Provide a brief verdict.", shape=tab]
start -> spec -> implement -> test -> review -> exit
}
```
## 8. Multi-Provider Ensemble
Independent opinions from multiple providers, then synthesize:
```dot
digraph Ensemble {
graph [
goal="Get independent opinions from multiple providers, then synthesize",
model_stylesheet="
#opus { model: claude-opus-4-6; }
#gemini { model: gemini-3.1-pro-preview;}
#codex { model: gpt-5.3-codex; }
#synth { model: claude-opus-4-6; reasoning_effort: high; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
fork [label="Fan Out", shape=component, join_policy="wait_all"]
opus [label="Opus", prompt="Analyze the goal. Provide your independent assessment and recommendations. Be thorough.", shape=tab]
gemini [label="Gemini", prompt="Analyze the goal. Provide your independent assessment and recommendations. Be thorough.", shape=tab]
codex [label="Codex", prompt="Analyze the goal. Provide your independent assessment and recommendations. Be thorough.", shape=tab]
merge [label="Merge", shape=tripleoctagon]
synth [label="Synthesize", prompt="You have received independent analyses from three different models. Compare their perspectives: identify consensus, highlight disagreements, and synthesize the strongest ideas into a single coherent recommendation.", shape=tab]
start -> fork
fork -> opus
fork -> gemini
fork -> codex
opus -> merge
gemini -> merge
codex -> merge
merge -> synth -> exit
}
```
## 9. Production Implement-and-Simplify with Verification
Full pipeline with toolchain checks, lint loops, and verification gates:
```dot
digraph ImplementAndSimplify {
graph [
goal="Implement and simplify",
model_stylesheet="
* { backend: api; model: claude-opus-4-6;}
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan."]
simplify [label="Simplify", prompt="Review the changes just made. Simplify and clean up the code without changing behavior."]
verify [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings and test failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=success"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=success"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=success"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify -> verify
verify -> exit [condition="outcome=success"]
verify -> fixup
fixup -> verify
}
```
Paired TOML:
```toml
version = 1
graph = "workflow.fabro"
[sandbox]
provider = "local"
[sandbox.local]
worktree_mode = "always"
```

View file

@ -1,143 +0,0 @@
# Run Configuration (TOML) Reference
The TOML file configures how a workflow is executed. It is separate from the DOT graph which defines the workflow structure.
## Minimal Config
```toml
version = 1
graph = "workflow.fabro"
goal = "Implement the login feature"
```
**Required:** `version` (must be 1), `graph` (path relative to TOML file's directory).
## All Sections
### Top-level
| Field | Type | Description |
|---|---|---|
| `version` | Integer | Must be `1` |
| `graph` | String | Path to DOT file (relative to TOML) |
| `goal` | String | Override workflow goal |
| `directory` | String | Working directory for the run |
### `[llm]`
```toml
[llm]
model = "claude-opus-4-6"
provider = "anthropic"
[llm.fallbacks]
anthropic = ["gemini-3.1-pro-preview", "gpt-5.2"]
openai = ["claude-sonnet-4-6"]
```
### `[setup]`
Sequential shell commands run before the workflow starts:
```toml
[setup]
commands = ["npm install", "npm run build"]
timeout_ms = 300000
```
### `[sandbox]`
```toml
[sandbox]
provider = "local" # local, docker, daytona, exe
preserve = false
devcontainer = false
[sandbox.local]
worktree_mode = "always" # always, auto, never
[sandbox.daytona]
auto_stop_interval = "30m"
[sandbox.daytona.snapshot]
name = "my-snapshot"
cpu = 4
memory = 8
disk = 20
dockerfile = { path = "Dockerfile" }
[sandbox.env]
NODE_ENV = "test"
API_KEY = "${env.API_KEY}" # passthrough from host
```
### `[vars]`
Variables expanded into DOT source before parsing:
```toml
[vars]
language = "typescript"
framework = "react"
```
Used in DOT as `$language`, `$framework`.
### `[checkpoint]`
```toml
[checkpoint]
exclude_globs = ["node_modules/**", ".git/**"]
```
### `[assets]`
```toml
[assets]
include = ["output/**", "*.png"]
```
### `[pull_request]`
```toml
[pull_request]
enabled = true
draft = true
```
### `[[hooks]]`
Lifecycle event hooks:
```toml
[[hooks]]
event = "stage_complete"
type = "command"
command = "echo 'Stage done'"
[[hooks]]
event = "run_complete"
type = "http"
url = "https://example.com/webhook"
```
Events: run_start, run_complete, run_failed, stage_start, stage_complete, stage_failed, stage_retrying, edge_selected, parallel_start, parallel_complete, sandbox_ready, sandbox_cleanup, checkpoint_saved, pre_tool_use, post_tool_use, post_tool_use_failure.
### `[mcp_servers]`
```toml
[mcp_servers.my-server]
transport = "stdio"
command = "node"
args = ["server.js"]
```
## Precedence (first match wins)
Node-level attribute > Stylesheet > TOML config > CLI flags > Server defaults > DOT graph attributes > Built-in defaults
## Validation
```bash
fabro preflight workflow.toml # validate without executing
```