Foundry
Foundry is an event-driven workflow engine for engineering automation. It replaces imperative shell scripts with composable task blocks connected by events, with throttle controlling how far each event ripples through the system.
Foundry runs as a daemon (foundryd) with a CLI controller (foundry)
communicating over gRPC. Any emitter — a scheduled job, a webhook, a
manual command — can fire an event and trigger the same downstream
workflow.
How We Got Here
Foundry has been evolving for roughly four months. It started as a patchwork of shell scripts and launchd jobs — nightly maintenance automation that grew into a linear pipeline: iterate projects, audit for vulnerabilities, cut releases, install locally. Each step waited for all previous steps to complete. Projects couldn't run in parallel. An audit couldn't start until every project finished updating. A vulnerability discovered at 2pm had to wait for the 2am maintenance window.
As the scripts accumulated, the concepts started to clarify. Events, task blocks, throttle control, self-filtering — these patterns kept emerging from the scripts, so we started extracting them into something more intentional. Foundry is the result: a strongly typed Rust framework for building event-driven engineering workflows, replacing the fragile shell glue with compiler-checked event flows and composable task blocks.
Foundry decouples the work from the scheduling. The same task blocks that run during nightly maintenance can be triggered individually, at any time, with throttle controlling how deep the ripple goes.
Where We Are Today
Foundry has two layers, both defined in Rust:
- Event library — the vocabulary of immutable facts that flow through
the system. Each event type is a variant of the
EventTypeenum with a well-defined payload structure. - Block library — the catalogue of reusable task blocks. Each block
implements the
TaskBlocktrait, declaring which events it sinks on, what work it performs, and what events it emits.
All blocks are registered into a single engine at startup. Workflows are not declared anywhere — they emerge from the sink/emit relationships between blocks. The engine routes by event type; blocks self-filter by inspecting payload fields. This means a different entry event (or a different payload) activates a different subset of blocks, producing a different workflow — all from the same block library.
See Workflow Formations for the formations that exist today and the possibilities the current block library opens up.
Where We're Headed
Events and blocks will remain Rust-defined — they benefit from strong type safety and compile-time guarantees. The composition layer is what we intend to extract: allowing teams to declare which blocks participate in a formation and how events flow between them through configuration rather than code, enabling situational customisation without recompilation.
Key Ideas
- Events are immutable facts — something happened. They carry a payload but have no opinion about what should happen next.
- Task blocks are reusable units of work. Each block sinks on specific event types, does work, and emits new events.
- Throttle sits on a task block's output side. It controls whether
downstream events are emitted:
full(everything propagates),audit_only(observers emit, mutators suppress), ordry_run(read-only, no side effects). - Workflows are compositions of task blocks wired together by events. They emerge from the emitter/sink relationships — not from a central orchestrator.
Charter
Mission
Provide a reliable, observable, event-driven engine for automating engineering workflows — from vulnerability remediation to dependency maintenance to release management.
Principles
-
Events over orchestration. Work is triggered by events, not by position in a script. Any emitter can start any workflow.
-
Composable task blocks. Small, reusable units of work. The same "Cut Release" block serves the vulnerability workflow and the maintenance workflow.
-
Throttle controls depth. Every invocation declares how far the ripple should go. Audit without releasing. Release without installing. The same workflow, different throttle.
-
Observability is paramount. Every event, every task block execution, every throttle decision is logged and traceable. If it happened, you can see it.
-
Correctness through types. Rust's type system enforces exhaustive event handling, valid throttle states, and safe concurrency. Malformed events are compiler errors, not runtime surprises.
Scope
Foundry automates engineering workflows for a registered project portfolio:
- Vulnerability detection and remediation
- Dependency maintenance (iterate, maintain, commit, push)
- Release management (tag, build, distribute)
- Local tool installation
- Release pipeline observation
It does not replace the existing evt-cli event logging system.
Foundry emits events into the same JSONL intake files, coexisting with
the current tooling. Over time, evt-cli may be rewritten in Rust to
share Foundry's event type crate.
Non-Goals
- General-purpose workflow engine (this serves specific engineering needs)
- CI/CD replacement (Foundry orchestrates local work and observes pipelines)
- Real-time monitoring dashboard (use Grafana, ops-visualizer, or similar)
Concepts
Events
An event records that something happened. It is an immutable fact appended to the event log. Events carry:
- id — deterministic hash of content (same input always produces same ID)
- event_type — what happened (e.g.,
vulnerability_detected) - project — which project this relates to
- throttle — propagated through the chain, controls downstream behaviour
- payload — event-type-specific data as JSON
- occurred_at / recorded_at — timestamps
Events have no opinion about what should happen next. That's the job of task blocks.
Task Blocks
A task block is a unit of work. It has:
- Name — human-readable identifier
- Kind —
Observer(reads/scans) orMutator(writes/deploys) - Sinks — which event types trigger this block
- Work — what it does when triggered
- Emits — which events it produces on completion
Task blocks are registered with the engine at startup. When an event arrives, the engine finds all blocks that sink on that event type and executes them.
Observer vs Mutator
The distinction matters for throttle behaviour:
| Kind | Examples | Throttle behaviour |
|---|---|---|
| Observer | Audit tag, audit main, validate project | Always executes, always emits |
| Mutator | Cut release, install locally, commit+push | Execution and emission controlled by throttle |
Throttle
The throttle sits on a task block's output side. After a block completes its work, it checks the throttle before emitting downstream events.
| Level | Observers | Mutators |
|---|---|---|
full | Execute + emit | Execute + emit |
audit_only | Execute + emit | Execute + suppress emission |
dry_run | Execute + emit | Skip execution + suppress emission |
The throttle is set when an event is first emitted (e.g., via CLI) and propagated through the entire chain. This means a single command controls how deep the ripple goes.
Workflows
A workflow is not a first-class object in Foundry. It emerges from the
emitter/sink wiring between task blocks. When you emit vulnerability_detected,
the chain of task blocks that fire in response is the vulnerability
remediation workflow.
This means workflows are composable by nature — adding a new task block that sinks on an existing event type automatically extends every workflow that produces that event.
Engine
The engine is the runtime that:
- Receives an event
- Finds task blocks that sink on that event type
- Checks throttle to decide whether to execute and/or emit
- Executes matching blocks
- Collects emitted events and feeds them back into step 2
- Continues until no more events are produced (the chain is complete)
The engine processes events depth-first within a single invocation.
Event Model
Foundry's event model defines the vocabulary of immutable facts that flow through the system. Events carry payloads describing what occurred but have no opinion about what should happen next — task blocks make those decisions.
Authoritative Source
The canonical event type definitions live in
foundry-core/src/event.rs (EventType enum). The implementation
roadmap for completing all task blocks and workflows is in
IMPLEMENTATION_PLAN.md at the project root.
Event Categories
Run Lifecycle
| Event | Emitter | Purpose |
|---|---|---|
maintenance_run_started | Orchestrator / manual | Begins a per-project maintenance chain |
maintenance_run_completed | Orchestrator (fan-in) | All projects finished; carries aggregate results |
Per-Project: Iterate / Maintain
| Event | Emitter | Purpose |
|---|---|---|
project_validation_completed | ValidateProject | Pre-flight check (dir, branch, gates) |
project_iteration_completed | RouteGateResult | One structural improvement attempted |
project_maintenance_completed | RouteGateResult | Dependencies updated, gates verified |
project_changes_committed | CommitAndPush | Git commit created |
project_changes_pushed | CommitAndPush | Pushed to remote |
Per-Project: Release Audit
| Event | Emitter | Purpose |
|---|---|---|
release_tag_audited | AuditReleaseTag | Latest tag scanned for vulnerabilities |
main_branch_audited | AuditMainBranch | Main branch checked for same vulnerability |
release_requested | Audit chain | Intent to cut a patch release |
release_completed | CutRelease | Tag pushed |
Vulnerability Remediation
| Event | Emitter | Purpose |
|---|---|---|
vulnerability_detected | External / nightly audit | Entry point for remediation workflow |
remediation_started | RemediateVulnerability | Fix attempt underway |
remediation_completed | RemediateVulnerability | Fix attempt finished (success or failure) |
Distribution Pipeline
| Event | Emitter | Purpose |
|---|---|---|
release_pipeline_completed | WatchPipeline | GitHub Actions finished building and publishing |
local_install_completed | InstallLocally | Tool reinstalled on local machine |
Event Structure
Every event has:
- id — Deterministic SHA256 hash of (type + project + occurred_at + payload)
- event_type — One of the
EventTypeenum variants - project — Which project this event relates to
- occurred_at / recorded_at — When it happened vs. when it was logged
- throttle — Propagated through the chain to control downstream behaviour
- payload — Event-type-specific JSON data
Payload Conventions
Downstream blocks read payload fields to make routing decisions (self-filtering). The engine routes by event type only — it cannot inspect payloads.
| Field | Used By | Values |
|---|---|---|
vulnerable | AuditMainBranch | true/false — whether the tag has known CVEs |
dirty | RemediateVulnerability, CutRelease | true/false — whether main still has the vulnerability |
cve | All vulnerability blocks | CVE identifier string |
status | Downstream blocks | "ok"/"error" — validation and completion status |
has_changes | CommitAndPush | Whether there are uncommitted changes to persist |
Task Block Library
Task blocks are the reusable processing units of Foundry. Each block is defined once and can participate in multiple workflows.
Implementing the TaskBlock Trait
Every task block implements foundry_core::task_block::TaskBlock:
#![allow(unused)] fn main() { pub trait TaskBlock: Send + Sync { fn name(&self) -> &'static str; fn kind(&self) -> BlockKind; fn sinks_on(&self) -> &[EventType]; fn execute( &self, trigger: &Event, ) -> Pin<Box<dyn Future<Output = anyhow::Result<TaskBlockResult>> + Send + '_>>; // Optional — defaults to no retries fn retry_policy(&self) -> RetryPolicy { RetryPolicy::default() } } }
The trait provides default implementations for should_emit() and
should_execute() based on kind() and the throttle level. Override
retry_policy() to enable automatic retry of transient failures.
See the Writing Task Blocks guide for
step-by-step instructions and a full example including RetryPolicy.
Current Blocks
Hello-World (validates engine mechanics)
| Block | Kind | Sinks On | Emits |
|---|---|---|---|
| Compose Greeting | Observer | greet_requested | greeting_composed |
| Deliver Greeting | Mutator | greeting_composed | greeting_delivered |
Vulnerability Remediation
These blocks form two paths through the vulnerability remediation workflow.
Both Remediate Vulnerability and Cut Release sink on main_branch_audited
and self-filter based on the dirty flag in the payload — only one path fires
per event.
Audit Release Tag also sinks on project_changes_pushed to perform a
post-push re-audit, confirming the fix is clean before anything downstream acts.
| Block | Kind | Sinks On | Emits | Self-filters |
|---|---|---|---|---|
| Scan Dependencies | Observer | scan_requested | vulnerability_detected | — |
| Audit Release Tag | Observer | vulnerability_detected, project_changes_pushed | release_tag_audited | Skips post-push when project not in registry |
| Audit Main Branch | Observer | release_tag_audited | main_branch_audited | Skips when vulnerable=false |
| Remediate Vulnerability | Mutator | main_branch_audited | remediation_completed | Only when dirty=true |
| Commit and Push | Mutator | remediation_completed, project_iteration_completed, project_maintenance_completed | project_changes_committed, project_changes_pushed | Skips when tree is clean or changes=false |
| Cut Release | Mutator | main_branch_audited | release_completed | Only when dirty=false |
| Watch Pipeline | Mutator | release_completed | release_pipeline_completed | — |
| Install Locally | Mutator | project_changes_pushed, release_pipeline_completed | local_install_completed | — |
Maintenance
The maintenance workflow uses an explicit routing Observer (Route Project Workflow) to delineate which sub-workflow runs. This keeps each downstream
block focused on a single responsibility.
| Block | Kind | Sinks On | Emits | Self-filters |
|---|---|---|---|---|
| Validate Project | Observer | maintenance_run_started | project_validation_completed | Skips projects not in active registry |
| Route Project Workflow | Observer | project_validation_completed | iteration_requested or maintenance_requested | Stops when status != "ok" or no actions enabled |
| Commit and Push | Mutator | project_iteration_completed, project_maintenance_completed | project_changes_committed, project_changes_pushed | Skips when tree is clean |
| Audit Release Tag | Observer | project_changes_pushed | release_tag_audited | Skips when project not in registry |
The actions.maintain flag is forwarded inside the iteration_requested
payload so that the gate routing can chain directly to maintenance_requested
after a successful iteration without re-querying the project configuration.
Gate Orchestration
These blocks provide native gate resolution, execution, and routing for iterate, maintain, and validation workflows.
| Block | Kind | Sinks On | Emits | Self-filters |
|---|---|---|---|---|
| Resolve Gates | Observer | iteration_requested, maintenance_requested, validation_requested | gate_resolution_completed | — |
| Run Preflight Gates | Observer | gate_resolution_completed | preflight_completed | Skips maintain workflow |
| Run Verify Gates | Observer | execution_completed | gate_verification_completed | — |
| Route Gate Result | Observer | gate_verification_completed | project_iteration_completed / project_maintenance_completed / retry_requested | Routes based on pass/fail and retry count |
| Route Validation Result | Observer | preflight_completed | validation_completed | Only handles validate workflow |
Iterate Workflow
These blocks form the native iterate chain, running inside the gate orchestration lifecycle.
| Block | Kind | Sinks On | Emits | Self-filters |
|---|---|---|---|---|
| Check Charter | Observer | preflight_completed | charter_validated | Only handles iterate workflow |
| Assess Project | Mutator | charter_validated | project_assessed | — |
| Triage Assessment | Mutator | project_assessed | assessment_triaged | — |
| Create Plan | Mutator | assessment_triaged | plan_created | — |
| Execute Plan | Mutator | plan_created | execution_completed | — |
Maintain Workflow
| Block | Kind | Sinks On | Emits | Self-filters |
|---|---|---|---|---|
| Execute Maintain | Mutator | gate_resolution_completed | execution_completed | Only handles maintain workflow |
| Retry Execution | Mutator | retry_requested | execution_completed | — |
| Summarize Result | Observer | project_iteration_completed, project_maintenance_completed | generate_summary | — |
Validation Workflow
A dedicated read-only workflow for checking project gate health. No Mutator blocks are involved — validation never modifies code.
validation_requested
→ Resolve Gates → gate_resolution_completed
→ Run Preflight Gates → preflight_completed
→ Route Validation Result → validation_completed
Vulnerability Workflow Chain
flowchart TD
A[scan_requested] --> B([Scan Dependencies])
B --> C[vulnerability_detected]
C --> D([Audit Release Tag])
D --> E[release_tag_audited]
E --> F([Audit Main Branch])
F --> G[main_branch_audited]
G --> H{dirty?}
H -->|dirty=true| I([Remediate Vulnerability])
I --> J[remediation_completed]
J --> K([Commit and Push])
K --> L[project_changes_committed]
K --> M[project_changes_pushed]
M --> N([Audit Release Tag post-push])
N --> O[release_tag_audited]
M --> P([Install Locally])
P --> Q[local_install_completed]
H -->|dirty=false| R([Cut Release])
R --> S[release_completed]
S --> T([Watch Pipeline])
T --> U[release_pipeline_completed]
U --> V([Install Locally])
V --> W[local_install_completed]
Maintenance Workflow Chain
flowchart TD
A([maintenance_run_started]) --> B[[Validate Project]]
B --> C([project_validation_completed])
C --> D[[Route Project Workflow]]
D -->|iterate=true| E([iteration_requested])
D -->|iterate=false, maintain=true| F([maintenance_requested])
D -->|no actions| G([end — no automation])
E --> H[[Resolve Gates]]
H --> I[[Run Preflight Gates]]
I --> J[[Check Charter]]
J --> K[[Assess Project]]
K --> L[[Triage Assessment]]
L --> M[[Create Plan]]
M --> N[[Execute Plan]]
N --> O[[Run Verify Gates]]
O --> P[[Route Gate Result]]
P -->|pass| Q([project_iteration_completed])
P -->|fail, retries left| R[[Retry Execution]]
Q -->|maintain=true| F
F --> S[[Resolve Gates]]
S --> T[[Execute Maintain]]
T --> U[[Run Verify Gates]]
U --> V[[Route Gate Result]]
V -->|pass| W([project_maintenance_completed])
V -->|fail, retries left| X[[Retry Execution]]
Gateway Pattern
Every block that executes an external process (a shell command or an audit tool)
receives its I/O capability through a gateway trait rather than calling the
implementation module directly. Two gateway traits live in gateway.rs:
ShellGateway— wrapscrate::shell::runfor arbitrary command execution.ScannerGateway— wrapscrate::scanner::run_auditfor vulnerability scanning.
Production blocks hold an Arc<dyn ShellGateway> (and/or Arc<dyn ScannerGateway>)
which is initialised to the real implementation in new(). A #[cfg(test)]
constructor accepts a fake instead, enabling hermetic unit tests for every block.
This separation means:
- The happy path and every failure/edge-case branch can be tested without spawning real processes.
shell.rsandscanner.rsstay untouched; the gateway is a thin adapter.- No
async_traitmacro is required — the return type uses an explicitPin<Box<dyn Future + Send + '_>>(the same pattern asTaskBlock::execute).
See Testing with Fakes for usage examples.
RetryPolicy
Blocks can declare automatic retry behaviour by overriding retry_policy():
#![allow(unused)] fn main() { use std::time::Duration; use foundry_core::task_block::RetryPolicy; fn retry_policy(&self) -> RetryPolicy { RetryPolicy { max_retries: 3, backoff: Duration::from_secs(5), } } }
max_retries: 0 (the default) means the block runs exactly once. With
max_retries: N, the engine retries up to N times after any failure (either a
returned Err or a TaskBlockResult { success: false, .. }), sleeping
backoff between each attempt. The final result (success or failure) is what
the engine records in the BlockExecution trace.
Crate Structure
Foundry is organised as a Cargo workspace with three crates:
foundry/
├── Cargo.toml # Workspace root
├── proto/foundry.proto # gRPC service definition
├── crates/
│ ├── foundry-core/ # Shared types (library)
│ ├── foundryd/ # Daemon (binary)
│ └── foundry-cli/ # CLI controller (binary)
└── book/ # This documentation
foundry-core
Shared types used by both the daemon and CLI:
event.rs—Eventstruct,EventTypeenum, deterministic ID generationthrottle.rs—Throttleenum (Full,AuditOnly,DryRun)task_block.rs—TaskBlocktrait,BlockKind,TaskBlockResult,RetryPolicyregistry.rs—Registry,ProjectEntry,ActionFlags,Stack,InstallConfigtrace.rs—TraceIndex,BlockExecution,ProcessResult— the structured types used to persist and display execution traces. Moved here fromfoundrydso the CLI can deserialise on-disk traces without depending on the daemon crate.
This crate has no async runtime dependency. It defines the vocabulary that the rest of the system speaks.
foundryd
The daemon process. Listens on gRPC (127.0.0.1:50051 by default) and runs
the workflow engine.
Core engine
engine.rs— event router: matches events to task blocks, executes them with retry logic, propagates emitted events respecting the throttle level. ExposesBlockExecutionandProcessResultfor structured telemetry.service.rs— gRPC service implementation (Emit,Status,Watch,Trace)
Daemon support modules
orchestrator.rs— coordinates per-project maintenance runs with concurrency control. DispatchesMaintenanceRunStartedper project, enforcesmax_concurrentvia a semaphore, and prevents double-running via an active project set with a drop-guard cleanup.event_writer.rs— appends every event to monthly JSONL files (YYYY-MM.jsonl) inside~/.foundry/events/(orFOUNDRY_EVENTS_DIR). Crash-safe: each write opens, flushes, and closes the file. AMutexserializes concurrent writes.trace_store.rs— in-memory store of recentProcessResultchains, keyed by root event ID. Used for fastTraceRPC lookups of workflows still in progress or recently completed.trace_writer.rs— persists completedProcessResultobjects to disk as pretty-printed JSON files under~/.foundry/traces/YYYY-MM-DD/{event_id}.json. Traces written here survive daemon restarts indefinitely and are read byfoundry historyandfoundry tracewhen the in-memory store has no match.workflow_tracker.rs— tracks workflows that are currently being processed by background tasks. Thread-safe viaRwLock. EachEmitRPC inserts anActiveWorkflowentry on start; a RAIIWorkflowGuardremoves it on completion or panic. TheStatusRPC reads this tracker to show live in-flight workflows.shell.rs— async shell runner used by block implementations. Runs an external command with configurable timeout (default 5 min), captures stdout and stderr, and returns aCommandResult.scanner.rs— vulnerability scanner abstraction. Dispatches to the stack-appropriate tool (cargo audit,npm audit,pip-audit,mix deps.audit) and normalizes output into aVec<Vulnerability>.gateway.rs— I/O abstraction layer for task blocks. DefinesShellGatewayandScannerGatewaytraits withProcessShellGatewayandProcessScannerGatewayproduction implementations. Also providesFakeShellGatewayandFakeScannerGatewaytest doubles (available under#[cfg(test)]only) that record invocations and return pre-configured results, enabling hermetic unit testing of every block without spawning real processes.summary.rs— renders aMaintenanceRunSummaryas a Markdown report (project table with success/failure/skipped, a failures section, and timing statistics).
Task block implementations (blocks/)
validate.rs—ValidateProject: pre-flight checks before a maintenance runresolve_gates.rs—ResolveGates: reads.hone-gates.jsonand emits gate definitionsrun_preflight_gates.rs—RunPreflightGates: runs gates on unmodified codebaserun_verify_gates.rs—RunVerifyGates: runs gates after code changesroute_gate_result.rs—RouteGateResult: routes pass/fail to completion or retryroute_validation_result.rs—RouteValidationResult: routes validation-only resultscheck_charter.rs—CheckCharter: validates project charter before iterationassess_project.rs—AssessProject: AI-driven project assessmenttriage_assessment.rs—TriageAssessment: prioritises assessment findingscreate_plan.rs—CreatePlan: generates an execution plan from triaged findingsexecute_plan.rs—ExecutePlan: executes the generated planexecute_maintain.rs—ExecuteMaintain: runs maintenance tasksretry_execution.rs—RetryExecution: retries failed executions with contextsummarize_result.rs—SummarizeResult: generates workflow summary and tracesgit_ops.rs—CommitAndPush: stages, commits, and optionally pushes changesaudit.rs—AuditReleaseTag,AuditMainBranch: vulnerability scanningrelease.rs—CutRelease,WatchPipeline: tagging and CI monitoringinstall.rs—InstallLocally: reinstalls the project locally after a fixremediate.rs—RemediateVulnerability: invokes the AI agent to fix a CVEscan.rs—ScanDependencies: scans for known vulnerabilitiesgreet.rs—ComposeGreeting,DeliverGreeting: hello-world engine validation
foundry-cli
The CLI controller. Connects to foundryd over gRPC.
main.rs—clap-based argument parsing; subcommands:emit,status,watch,trace,run,history,registrycommands.rs— async implementations of each subcommand viatonicgRPC client; also contains thehistorycommand which reads on-disk traces directly from~/.foundry/traces/without a daemon connectionregistry_commands.rs— pure I/O implementations of theregistrysubcommands (init,list,show,add,remove,edit); reads and writes~/.foundry/registry.jsonusingfoundry_core::registrytypes
proto/foundry.proto
The gRPC contract between CLI and daemon:
Emit— fire an event with type, project, throttle, and optional JSON payloadStatus— query active workflow states (all or by workflow ID)Watch— server-side streaming of live events, filterable by projectTrace— retrieve the full event chain and block execution records for a completed workflow
Getting Started
Prerequisites
- Rust 1.85+ (via
rust-toolchain.toml) protoc(Protocol Buffers compiler):brew install protobuf
Build
cd path/to/foundry
cargo build --release
This produces two binaries:
target/release/foundryd— the daemontarget/release/foundry— the CLI controller
Install Locally
Install both binaries to ~/.cargo/bin/ using the convenience script:
./install.sh
Or install each crate individually:
cargo install --path crates/foundryd
cargo install --path crates/foundry-cli
Re-run after making changes to pick up the latest version.
Start the Daemon
foundryd
You'll see the registered task blocks and the listening address:
INFO foundryd::engine: registered task block block="Compose Greeting" sinks=[GreetRequested]
INFO foundryd::engine: registered task block block="Deliver Greeting" sinks=[GreetingComposed]
INFO foundryd::engine: registered task block block="Audit Release Tag" sinks=[VulnerabilityDetected]
INFO foundryd::engine: registered task block block="Audit Main Branch" sinks=[ReleaseTagAudited]
INFO foundryd::engine: registered task block block="Remediate Vulnerability" sinks=[MainBranchAudited]
INFO foundryd::engine: registered task block block="Commit and Push" sinks=[RemediationCompleted]
INFO foundryd::engine: registered task block block="Cut Release" sinks=[MainBranchAudited]
INFO foundryd::engine: registered task block block="Watch Pipeline" sinks=[ReleaseCompleted]
INFO foundryd::engine: registered task block block="Install Locally" sinks=[ProjectChangesPushed, ReleasePipelineCompleted]
INFO foundryd: foundryd listening on 127.0.0.1:50051
Send Your First Event
In another terminal:
foundry emit greet_requested \
--project hello \
--payload '{"name": "World"}'
In the daemon logs you'll see the event chain:
INFO foundryd::service: processing event event_type=greet_requested project=hello throttle=full
INFO foundryd::engine: executing task block block="Compose Greeting" ...
INFO foundryd::blocks::greet: composed greeting greeting=Hello, World!
INFO foundryd::engine: executing task block block="Deliver Greeting" ...
INFO foundryd::blocks::greet: delivering greeting greeting=Hello, World!
INFO foundryd::service: event chain complete total_events=3
Three events in the chain: greet_requested → greeting_composed → greeting_delivered.
Try Throttle Control
# Observers run, mutators suppress emission
foundry emit greet_requested \
--project hello \
--throttle audit_only \
--payload '{"name": "World"}'
Now you'll see only 2 events — greeting_delivered is suppressed because
Deliver Greeting is a Mutator and the throttle is audit_only.
Quality Gates
cargo test # Run all tests
cargo clippy --all-targets --all-features -- -D warnings # Lint
cargo fmt --check # Format check
cargo deny check # License + advisory audit
Emitting Events
From the CLI
foundry emit <event_type> --project <project> [--throttle <level>] [--payload <json>]
Arguments
| Argument | Required | Description |
|---|---|---|
event_type | Yes | Event type name (e.g., greet_requested, vulnerability_detected) |
--project | Yes | Target project name |
--throttle | No | full (default), audit_only, or dry_run |
--payload | No | JSON string with event-specific data |
--addr | No | Daemon address (default: http://127.0.0.1:50051) |
Examples
# Simple event, default throttle
foundry emit greet_requested --project hello
# With payload
foundry emit greet_requested --project hello --payload '{"name": "Stacey"}'
# Audit only — observe without mutating
foundry emit vulnerability_detected \
--project my-tool \
--throttle audit_only \
--payload '{"cve": "CVE-2026-1234", "severity": "high"}'
# Dry run — no side effects at all
foundry emit maintenance_run_started \
--project evt-cli \
--throttle dry_run
What Happens When You Emit
- The CLI sends the event to
foundrydvia gRPC - The engine creates an
Eventwith a deterministic ID - The engine finds all task blocks that sink on the event type
- For each matching block:
- Check throttle: should this block execute? Should it emit?
- Execute the block's work
- Collect emitted events
- Feed emitted events back into step 3 (the chain continues)
- Return the initial event ID to the CLI
The chain continues until no more events are produced.
Inspecting a Completed Chain
After emitting an event, use foundry trace with the returned event ID to
see the full propagation tree and what each block did:
$ foundry emit greet_requested --project hello --payload '{"name": "Stacey"}'
Event emitted: evt_47fcb603e1b18c8435b8cc3b
$ foundry trace evt_47fcb603e1b18c8435b8cc3b
greet_requested (evt_47fcb603e1b18c8435b8cc3b) project=hello
→ ComposeGreeting: ok — composed greeting for Stacey
greeting_composed (evt_a1b2c3d4e5f6) project=hello
→ DeliverGreeting: ok — delivered greeting: Hello, Stacey!
greeting_delivered (evt_f6e5d4c3b2a1) project=hello
Pass --verbose to also show trigger payloads, emitted payloads, raw shell
output, and paths to any audit artefacts produced.
Persistent Trace Storage
Every completed event chain is written to disk under
~/.foundry/traces/YYYY-MM-DD/{event_id}.json (overridable via
FOUNDRY_TRACES_DIR). Traces persist indefinitely across daemon restarts —
there is no expiry TTL on disk.
To browse past traces, use foundry history:
# Show the last 7 days
foundry history
# Show a specific date
foundry history 2026-03-22
# Filter by project
foundry history --project my-tool
You can retrieve the full trace for any on-disk event with foundry trace,
even if the daemon has been restarted since the event was processed.
The Project Registry
The registry is Foundry's source of truth for which projects exist on your machine and what automation applies to each one. Without a populated registry, the daemon starts successfully but skips all project-specific work.
Where the Registry Lives
By default: ~/.foundry/registry.json
Override the path with the environment variable:
export FOUNDRY_REGISTRY_PATH=/path/to/my-registry.json
The daemon reads the registry on startup. If the file is missing it logs a warning and continues with an empty registry (no projects will be processed).
Registry Format (v2)
{
"version": 2,
"projects": [
{
"name": "my-tool",
"path": "/Users/alice/projects/my-tool",
"stack": "rust",
"agent": "claude",
"repo": "alice/my-tool",
"branch": "main",
"skip": false,
"actions": {
"iterate": true,
"maintain": true,
"push": true,
"audit": true,
"release": false
},
"install": {
"command": "cargo install --path ."
}
}
]
}
Top-level fields
| Field | Type | Description |
|---|---|---|
version | number | Must be 2 |
projects | array | List of ProjectEntry objects |
ProjectEntry fields
| Field | Required | Type | Description |
|---|---|---|---|
name | Yes | string | Unique human-readable identifier used in events and logs |
path | Yes | string | Absolute path to the project on your local filesystem |
stack | Yes | string | Technology stack — see Stack values |
agent | Yes | string | AI agent name used for automation (e.g. "claude") |
repo | Yes | string | GitHub repository slug (owner/repo) used by Watch Pipeline |
branch | Yes | string | Default branch (e.g. "main") — validation checks out this branch |
skip | No | string or null | Absent or null means not skipped; a non-empty string value is the skip reason. Accepts true (treated as "skipped") and false/null for backwards compatibility |
actions | No | object | Which automation steps are enabled; all default to false |
install | No | object | How to reinstall locally after automation — see InstallConfig |
notes | No | string | Human-readable notes about the project (informational only) |
timeout_secs | No | number | Timeout in seconds for long-running commands. Defaults to 3600 (60 minutes) when absent |
Stack values
The stack field tells Foundry which audit tool to use and how to run
stack-specific commands.
| Value | Audit tool | Notes |
|---|---|---|
"rust" | cargo audit --json | Requires cargo-audit to be installed |
"typescript" | npm audit --json | Exit code 1 = vulnerabilities found (not a tool failure) |
"python" | pip-audit --format=json | Requires pip-audit to be installed |
"elixir" | mix deps.audit --format=json | — |
"cpp" | — | Placeholder for C++ projects; audit tooling not yet wired |
ActionFlags
The actions object controls which steps run during a maintenance run. All
flags default to false when the actions key is absent.
| Flag | What it enables |
|---|---|
iterate | Runs the iterate workflow (assess, plan, execute) after project validation passes |
maintain | Runs the maintain workflow — either after iterate completes (when both are enabled) or directly after validation (when only maintain is enabled) |
push | Pushes commits to the remote via git push after a commit is made |
audit | (Reserved for future use — currently informational only) |
release | (Reserved for future use — currently informational only) |
InstallConfig
The install field configures how the project is reinstalled locally after
automation completes. Exactly one variant is used per entry:
Command — runs an arbitrary shell command in the project directory:
"install": { "command": "cargo install --path ." }
Brew — installs via a Homebrew formula:
"install": { "brew": "my-formula" }
Minimal Project Entry
Only the six required fields are needed. All optional fields default to safe values (no actions enabled, no install step, not skipped):
{
"version": 2,
"projects": [
{
"name": "minimal-project",
"path": "/Users/alice/projects/minimal-project",
"stack": "rust",
"agent": "claude",
"repo": "alice/minimal-project",
"branch": "main"
}
]
}
Excluding a Project Temporarily
Set skip to a string reason to pause automation without removing the entry:
{
"name": "on-hold",
"path": "/Users/alice/projects/on-hold",
"stack": "typescript",
"agent": "claude",
"repo": "alice/on-hold",
"branch": "main",
"skip": "Waiting for CI to stabilise"
}
The value of skip is the human-readable reason displayed in foundry registry show.
Absent, null, false, or an empty string all mean "not skipped".
For backwards compatibility, true is treated as the reason string "skipped".
The Validate Project block silently acknowledges skipped projects (emits
project_validation_completed with status: "skipped") so the engine trace
remains complete.
Managing the Registry with the CLI
Rather than editing registry.json by hand, use the foundry registry
subcommands. All commands respect FOUNDRY_REGISTRY_PATH for non-default
locations.
# Create an empty registry
foundry registry init
# List all projects
foundry registry list
# Inspect one project
foundry registry show my-tool
# Add a new project
foundry registry add \
--name my-tool \
--path /Users/alice/projects/my-tool \
--stack rust \
--agent claude \
--repo alice/my-tool \
--iterate --maintain --push \
--install-command "cargo install --path ." \
--notes "Main CLI toolchain"
# Edit an existing project
foundry registry edit my-tool --timeout-secs 3600
# Skip a project temporarily
foundry registry edit my-tool --skip "Waiting for CI to stabilise"
# Clear a skip (resume automation)
foundry registry edit my-tool --skip ""
# Remove a project
foundry registry remove my-tool
See CLI Commands — foundry registry for the full option reference.
Multiple Projects
A single registry file can declare any number of projects. They are processed
concurrently during a maintenance run (up to max_concurrent at a time, which
defaults to the number of active projects unless the orchestrator is configured
otherwise):
{
"version": 2,
"projects": [
{
"name": "api-server",
"path": "/Users/alice/projects/api-server",
"stack": "rust",
"agent": "claude",
"repo": "alice/api-server",
"branch": "main",
"actions": { "iterate": true, "maintain": true, "push": true }
},
{
"name": "frontend",
"path": "/Users/alice/projects/frontend",
"stack": "typescript",
"agent": "claude",
"repo": "alice/frontend",
"branch": "main",
"actions": { "maintain": true, "push": true }
}
]
}
Workflow Formations
A workflow formation is not a first-class object in Foundry. It is the logical result of which blocks sink on which events — a chain that emerges when you emit a particular entry event. All blocks live in one engine. The formation that fires depends entirely on the entry event and the payload values that flow through it.
This page documents the formations that exist today and explores how the current block library could be recombined for different purposes.
The Block Library at a Glance
Every block declares its sinks (what triggers it), its emits (what it produces), and its kind (Observer or Mutator). The engine does the rest.
Shared Infrastructure
These blocks appear in multiple formations:
| Block | Kind | Sinks On | Emits |
|---|---|---|---|
| Resolve Gates | Observer | charter_check_completed, maintenance_requested, validation_requested | gate_resolution_completed |
| Run Preflight Gates | Observer | gate_resolution_completed | preflight_completed |
| Run Verify Gates | Observer | execution_completed | gate_verification_completed |
| Route Gate Result | Observer | gate_verification_completed | project_iteration_completed / project_maintenance_completed / retry_requested |
| Retry Execution | Mutator | retry_requested | execution_completed |
| Summarize Result | Observer | project_iteration_completed, project_maintenance_completed | summarize_completed |
| Commit and Push | Mutator | remediation_completed, project_iteration_completed, project_maintenance_completed | project_changes_committed, project_changes_pushed |
Iteration Blocks
| Block | Kind | Sinks On | Emits |
|---|---|---|---|
| Check Charter | Observer | iteration_requested | charter_check_completed |
| Assess Project | Observer | preflight_completed | assessment_completed |
| Triage Assessment | Observer | assessment_triaged | triage_completed |
| Create Plan | Observer | triage_completed | plan_completed |
| Execute Plan | Mutator | plan_completed | execution_completed |
Maintenance Blocks
| Block | Kind | Sinks On | Emits |
|---|---|---|---|
| Execute Maintain | Mutator | gate_resolution_completed | execution_completed |
Vulnerability Blocks
| Block | Kind | Sinks On | Emits |
|---|---|---|---|
| Scan Dependencies | Observer | scan_requested | vulnerability_detected (per CVE) |
| Audit Release Tag | Observer | vulnerability_detected, project_changes_pushed | release_tag_audited |
| Audit Main Branch | Observer | release_tag_audited | main_branch_audited |
| Remediate Vulnerability | Mutator | main_branch_audited | remediation_completed |
| Cut Release | Mutator | main_branch_audited | release_completed |
| Watch Pipeline | Mutator | release_completed | release_pipeline_completed |
| Install Locally | Mutator | project_changes_pushed, release_pipeline_completed | local_install_completed |
Prompt Workflow Blocks
| Block | Kind | Sinks On | Emits |
|---|---|---|---|
| Direct Prompt | Observer | preflight_completed (workflow=prompt) | plan_completed |
Strategic Loop Blocks
| Block | Kind | Sinks On | Emits |
|---|---|---|---|
| Strategic Assessor | Observer | iteration_requested (strategic=true) | strategic_assessment_completed |
| Strategic Loop Controller | Observer | strategic_assessment_completed, inner_iteration_completed | iteration_requested (loop) / project_iteration_completed (done) |
Orchestration Blocks
| Block | Kind | Sinks On | Emits |
|---|---|---|---|
| Validate Project | Observer | maintenance_run_started | project_validation_completed |
| Route Project Workflow | Observer | project_validation_completed | iteration_requested / maintenance_requested |
Current Formations
These are the formations that fire today, depending on which entry event you emit.
The Full Nightly Run
Entry event: maintenance_run_started
This is the broadest formation. It validates the project, routes to iteration and/or maintenance based on the project's registry flags, and chains the two sub-workflows together when both are enabled.
flowchart TD
A([maintenance_run_started]) --> B[[Validate Project]]
B --> C[[Route Project Workflow]]
C -->|iterate=true| D([iteration_requested])
C -->|maintain=true| E([maintenance_requested])
D --> F[Iterate Formation]
F -->|success + maintain=true| E
E --> G[Maintain Formation]
Iterate Formation
Entry event: iteration_requested
The full assessment-to-execution pipeline with gate verification and bounded retry.
flowchart TD
A([iteration_requested]) --> B[[Check Charter]]
B -->|passed| C[[Resolve Gates]]
C --> D[[Run Preflight Gates]]
D -->|passed| E[[Assess Project]]
E --> F[[Triage Assessment]]
F -->|accepted| G[[Create Plan]]
G --> H[[Execute Plan]]
H --> I[[Run Verify Gates]]
I --> J[[Route Gate Result]]
J -->|pass| K([project_iteration_completed])
J -->|fail, retries left| L[[Retry Execution]]
L --> I
K --> M[[Summarize Result]]
K --> N[[Commit and Push]]
Prompt Formation
Entry event: prompt_execution_requested
A streamlined formation that executes an arbitrary user-provided prompt with gate verification. It skips assessment, triage, and plan creation — the user's prompt IS the plan.
flowchart TD
A([prompt_execution_requested]) --> B[[Check Charter]]
B -->|passed| C[[Resolve Gates]]
C --> D[[Run Preflight Gates]]
D -->|passed| E[[Direct Prompt]]
E --> F[[Execute Plan]]
F --> G[[Run Verify Gates]]
G --> H[[Route Gate Result]]
H -->|pass| I([project_iteration_completed])
H -->|fail, retries left| J[[Retry Execution]]
J --> G
I --> K[[Summarize Result]]
I --> L[[Commit and Push]]
Usage:
foundry emit prompt_execution_requested my-project \
--payload '{"prompt": "Pick the highest priority interaction from et and implement it."}'
Strategic Iterate Formation
Entry event: iteration_requested with strategic: true
A nested loop that wraps the iterate formation. The strategic assessor identifies multiple areas for improvement, then the loop controller enters the inner iterate formation for each area. After each inner iteration completes, an AI assessment decides whether to continue. Changes are committed per iteration.
flowchart TD
A([iteration_requested<br/>strategic=true]) --> B[[Strategic Assessor]]
B --> C([strategic_assessment_completed])
C --> D[[Strategic Loop Controller]]
D --> E([iteration_requested<br/>with loop_context])
E --> F[Iterate Formation<br/>inner loop]
F --> G([inner_iteration_completed])
G --> H[[Commit and Push]]
G --> D
D -->|continue| E
D -->|done| I([project_iteration_completed])
I --> J[[Summarize Result]]
I --> K[[Commit and Push]]
The inner iterate formation runs exactly as documented above, with one
difference: Route Gate Result emits inner_iteration_completed
instead of project_iteration_completed when loop_context is present
in the payload. This allows the strategic loop controller to intercept
the completion and decide whether to continue.
Terminal blocks (Summarize Result and Commit and Push) self-filter
on loop_context — they skip intermediate completions and only fire
on the final project_iteration_completed emitted by the strategic
loop controller (which strips loop_context before emitting it).
Maintain Formation
Entry event: maintenance_requested
Dependency updates and general maintenance. Preflight gates are skipped (the codebase may be in a pre-maintenance state), but verification gates run after execution.
flowchart TD
A([maintenance_requested]) --> B[[Resolve Gates]]
B --> C[[Run Preflight Gates]]
C -->|skipped| D[[Execute Maintain]]
D --> E[[Run Verify Gates]]
E --> F[[Route Gate Result]]
F -->|pass| G([project_maintenance_completed])
F -->|fail, retries left| H[[Retry Execution]]
H --> E
G --> I[[Summarize Result]]
G --> J[[Commit and Push]]
Vulnerability Remediation Formation
Entry event: vulnerability_detected
Two paths through the same blocks, governed by the dirty payload flag.
flowchart TD
A([vulnerability_detected]) --> B[[Audit Release Tag]]
B --> C[[Audit Main Branch]]
C --> D{dirty?}
D -->|true| E[[Remediate Vulnerability]]
E --> F[[Commit and Push]]
F --> G[[Install Locally]]
D -->|false| H[[Cut Release]]
H --> I[[Watch Pipeline]]
I --> J[[Install Locally]]
Scan Formation
Entry event: scan_requested
A broader entry point that discovers vulnerabilities and feeds them into
the remediation formation. Scan Dependencies emits one
vulnerability_detected event per CVE found, so a single scan can
trigger multiple parallel remediation chains.
flowchart TD
A([scan_requested]) --> B[[Scan Dependencies]]
B -->|per CVE| C([vulnerability_detected])
C --> D[Remediation Formation]
Validation Formation
Entry event: validation_requested
A read-only health check. No Mutator blocks fire — it just resolves gates, runs them, and reports the results.
flowchart TD
A([validation_requested]) --> B[[Resolve Gates]]
B --> C[[Run Preflight Gates]]
C --> D[[Route Validation Result]]
D --> E([validation_completed])
Possible Formations
The block library already supports formations that aren't part of the nightly run. Because the engine routes by event type and blocks self-filter on payload, you can trigger these directly.
Iterate Without Maintenance
Emit iteration_requested with actions.maintain=false. The iterate
formation runs, and on success Route Gate Result emits
project_iteration_completed without chaining to maintenance_requested.
foundry emit iteration_requested my-project \
--payload '{"actions":{"iterate":true,"maintain":false}}'
This is useful when you want to improve code quality without touching dependencies — a focused structural improvement pass.
Maintenance Without Iteration
Emit maintenance_requested directly. The maintain formation runs on its
own, skipping assessment, triage, and planning entirely.
foundry emit maintenance_requested my-project
This is a pure dependency update pass — update libraries, run gates, commit if they pass.
Scan Without Remediation
Emit scan_requested with audit_only throttle. Scan Dependencies and
the audit blocks run (they are Observers), but Remediate Vulnerability
and Cut Release (Mutators) suppress their output.
foundry emit scan_requested my-project --throttle audit_only
This tells you what vulnerabilities exist and whether main is dirty, without making any changes.
Remediation Without Scanning
Emit vulnerability_detected directly with the CVE details. This skips
the scan entirely and jumps straight into the audit-and-fix chain.
foundry emit vulnerability_detected my-project \
--payload '{"cve":"CVE-2026-1234","vulnerable":true,"dirty":true}'
This is how you would handle a vulnerability reported through a channel other than Foundry's scanner — a security advisory, a colleague's finding, or a CI notification.
Post-Push Audit
The Audit Release Tag block also sinks on project_changes_pushed.
This means that after an iterate or maintain formation commits and pushes,
the release tag is automatically re-audited. If the push introduced a
vulnerability (or resolved one), the audit chain picks it up without a
separate scan.
Strategic Iteration
Emit iteration_requested with strategic: true to enter the nested
loop. The strategic assessor analyses the codebase holistically and
the loop controller runs multiple inner iterate cycles until the AI
determines the codebase has plateaued.
foundry emit iteration_requested my-project \
--payload '{"strategic":true,"max_iterations":5}'
The max_iterations field caps the loop to prevent runaway iterations.
Each inner cycle commits its changes independently.
Gate Check Only
Emit validation_requested to run all gates without modifying anything.
This is the lightest formation — it tells you whether the project is
healthy right now.
foundry emit validation_requested my-project
Designing New Formations
The current block library is a toolkit. The formations above are the ones we use today, but the same blocks can participate in formations we haven't built yet. A few principles guide what's possible:
-
Entry events define scope. The deeper into a chain you emit, the narrower the formation. Emitting
maintenance_run_startedruns everything; emittingplan_completedskips assessment entirely and just executes a plan you provide. -
Payload values steer routing. Blocks self-filter on payload fields like
dirty,accepted,workflow, andactions. Changing a payload value changes which blocks fire without changing any code. -
Throttle controls depth. The same formation behaves differently under
full,audit_only, anddry_run. This gives you three versions of every formation for free. -
Shared blocks multiply formations.
Commit and Pushsinks on three different event types.Install Locallysinks on two. Every block that participates in multiple formations is a junction point where chains can converge or diverge.
Vulnerability Remediation Workflow
The vulnerability remediation workflow is Foundry's primary use case. It replaces a linear shell script pipeline with an event-driven chain of task blocks that branches based on the state of the codebase.
The Two Paths
When a vulnerability is detected, the chain branches based on whether the main branch still contains the vulnerability:
flowchart TD
A[vulnerability_detected] --> B([Audit Release Tag])
B --> C[release_tag_audited]
C --> D([Audit Main Branch])
D --> E[main_branch_audited]
E --> F{dirty?}
F -->|"dirty=true (vulnerability present on main)"| G([Remediate Vulnerability])
G --> H[remediation_completed]
H --> I([Commit and Push])
I --> J[project_changes_committed]
I --> K[project_changes_pushed]
K --> L([Install Locally])
L --> M[local_install_completed]
F -->|"dirty=false (main already fixed, no release cut yet)"| N([Cut Release])
N --> O[release_completed]
O --> P([Watch Pipeline])
P --> Q[release_pipeline_completed]
Q --> R([Install Locally])
R --> S[local_install_completed]
Dirty path — the vulnerability exists on main. Foundry remediates it (e.g., dependency update), commits, pushes, and reinstalls locally.
Clean path — main is already fixed (perhaps by a developer), but no release has been cut. Foundry tags a patch release, watches the CI pipeline, and reinstalls locally.
If the release tag is not vulnerable at all, the chain stops after
release_tag_audited — Audit Main Branch self-filters and emits nothing.
Self-Filtering
The engine routes events by type only — it cannot inspect payloads. When
both Remediate Vulnerability and Cut Release sink on main_branch_audited,
both blocks receive every main_branch_audited event. Each block checks the
dirty flag in the payload and returns an empty result if the condition
doesn't match. This ensures only one path fires.
Running the Workflow
Full run (default)
All blocks execute and emit. The complete chain runs to local_install_completed:
foundry emit vulnerability_detected \
--project my-tool \
--payload '{"cve": "CVE-2026-1234", "vulnerable": true, "dirty": true}'
Then inspect the trace:
foundry trace <event_id>
vulnerability_detected (evt_...) project=my-tool
→ Audit Release Tag: ok — Release tag audited: CVE-2026-1234 vulnerable=true
release_tag_audited (evt_...) project=my-tool
→ Audit Main Branch: ok — Main branch audited: CVE-2026-1234 dirty=true
main_branch_audited (evt_...) project=my-tool
→ Remediate Vulnerability: ok — Remediated CVE-2026-1234
remediation_completed (evt_...) project=my-tool
→ Commit and Push: ok — Committed and pushed fix for CVE-2026-1234
project_changes_committed (evt_...) project=my-tool
project_changes_pushed (evt_...) project=my-tool
→ Install Locally: ok — Installed locally
local_install_completed (evt_...) project=my-tool
→ Cut Release: ok — Skipped: main branch is dirty
Notice that Cut Release still appears in the trace — it received the event
but self-filtered and returned an empty result.
Audit only
Observers run and emit, mutators run but suppress downstream events:
foundry emit vulnerability_detected \
--project my-tool \
--throttle audit_only \
--payload '{"cve": "CVE-2026-1234", "vulnerable": true, "dirty": true}'
This tells you what would happen without actually remediating, committing,
or releasing. The audit blocks produce their findings, but the chain stops
at main_branch_audited.
Dry run
Only observers execute. Mutators are skipped entirely:
foundry emit vulnerability_detected \
--project my-tool \
--throttle dry_run \
--payload '{"cve": "CVE-2026-1234", "vulnerable": true, "dirty": true}'
The chain produces vulnerability_detected → release_tag_audited →
main_branch_audited and stops. No remediation, no commits, no releases.
Clean Path Example
When the main branch is already fixed but no release has been cut:
foundry emit vulnerability_detected \
--project my-tool \
--payload '{"cve": "CVE-2026-5678", "vulnerable": true, "dirty": false}'
The trace shows the release path instead:
vulnerability_detected (evt_...) project=my-tool
→ Audit Release Tag: ok — ...
release_tag_audited (evt_...) project=my-tool
→ Audit Main Branch: ok — Main branch audited: CVE-2026-5678 dirty=false
main_branch_audited (evt_...) project=my-tool
→ Remediate Vulnerability: ok — Skipped: main branch is clean
→ Cut Release: ok — Cut patch release for CVE-2026-5678
release_completed (evt_...) project=my-tool
→ Watch Pipeline: ok — Release pipeline completed successfully
release_pipeline_completed (evt_...) project=my-tool
→ Install Locally: ok — Installed locally
local_install_completed (evt_...) project=my-tool
Not Vulnerable
If the release tag has no vulnerability, the chain stops early:
foundry emit vulnerability_detected \
--project my-tool \
--payload '{"cve": "CVE-2026-9999", "vulnerable": false}'
Only two events: vulnerability_detected → release_tag_audited. The
Audit Main Branch block sees vulnerable=false and emits nothing.
Payload Fields
| Field | Used By | Values | Default |
|---|---|---|---|
cve | All blocks | CVE identifier string | "unknown" |
vulnerable | Audit Release Tag, Audit Main Branch | true / false | true |
dirty | Audit Main Branch, Remediate, Cut Release | true / false | true |
Real Implementations
All blocks in the vulnerability remediation workflow perform real work:
- Audit Release Tag — shells out to the stack-appropriate tool (
cargo audit --json,npm audit --json,pip-audit --format=json,mix deps.audit) via theScannerGatewayabstraction - Audit Main Branch — checks out the main branch and runs the same scan
- Remediate Vulnerability — invokes the configured AI agent (e.g. via the
claudeCLI) to apply a fix - Commit and Push — runs
git add,git commit, and optionallygit push - Cut Release — creates and pushes a git tag using the AI agent
- Watch Pipeline — polls the GitHub Actions API until the release workflow completes or times out
- Install Locally — runs the project's configured install command
(e.g.
cargo install --path .) or Homebrew formula after a successful release
Iteration Workflow
The iteration workflow is Foundry's code quality improvement engine. It identifies the most-violated engineering principle in a project, creates a targeted fix plan, executes it, and verifies the result against quality gates — with automatic retries on failure.
The Chain
flowchart TD
A([iteration_requested]) --> B[[Resolve Gates]]
B --> C([gate_resolution_completed])
C --> D[[Run Preflight Gates]]
D --> E([preflight_completed])
E --> F[[Check Charter]]
F --> G([charter_validated])
G --> H[[Assess Project]]
H --> I([project_assessed])
I --> J[[Triage Assessment]]
J --> K([assessment_triaged])
K --> L[[Create Plan]]
L --> M([plan_created])
M --> N[[Execute Plan]]
N --> O([execution_completed])
O --> P[[Run Verify Gates]]
P --> Q([gate_verification_completed])
Q --> R[[Route Gate Result]]
R -->|pass| S([project_iteration_completed])
R -->|fail, retries left| T[[Retry Execution]]
T --> O
R -->|fail, retries exhausted| U([project_iteration_completed — failure])
S -->|maintain=true| V([maintenance_requested])
Phase by Phase
1. Gate Resolution
Resolve Gates reads .hone-gates.json from the project directory and
emits the gate definitions with workflow: "iterate". The actions object
from the trigger event is forwarded through the chain so that gate routing
can chain into the maintenance workflow afterwards.
2. Preflight Gates
Run Preflight Gates executes every gate against the unmodified codebase.
If any required gate fails, all_passed is false and the chain stops —
Assess Project self-filters on preflight success.
This establishes a baseline: the iterate workflow only attempts improvements when the project is already in a passing state.
3. Charter Validation
Check Charter verifies that the project has intent documentation
(e.g. CHARTER.md). If the charter is missing, passed is false and
the chain stops at this point — there is no assessment without context.
4. Assessment
Assess Project invokes an AI agent with reasoning capability and
read-only access to analyse the codebase. It identifies the single
most-violated engineering principle, returning a severity score (1–10),
the principle name, category, and a detailed assessment.
A second quick agent call generates a kebab-case audit filename for traceability.
5. Triage
Triage Assessment uses a quick agent call to decide whether the finding
is worth acting on. It rejects issues with severity below 4 or findings
that amount to busy-work rather than substantive improvement. On agent
failure, triage defaults to accepted — it is better to attempt a fix than
to silently skip.
If triage rejects the finding, Create Plan self-filters on
accepted=false and the chain stops cleanly.
6. Plan Creation
Create Plan invokes an AI agent with reasoning capability and read-only
access to produce a step-by-step correction plan. The plan is concrete —
it names exact files and functions — minimal, and testable. Gate definitions
are forwarded so the execution phase knows what must pass.
7. Execution
Execute Plan is the only Mutator in the assessment-to-execution pipeline.
It invokes an AI agent with coding capability and full filesystem access.
The agent receives the plan plus gate context (the gates it must satisfy)
and applies the changes.
Under audit_only or dry_run throttle, this block returns a simulated
success without modifying any files.
8. Gate Verification
Run Verify Gates re-reads .hone-gates.json from disk (a fresh read,
not cached from phase 1) and runs every gate against the modified codebase.
The retry_count from the payload tracks which attempt this is.
9. Routing and Retry
Route Gate Result makes the terminal decision:
| Condition | Action |
|---|---|
| All required gates pass | Emit project_iteration_completed (success) |
| Required gates fail, retry count < 3 | Emit retry_requested with failure context |
| Required gates fail, retry count ≥ 3 | Emit project_iteration_completed (failure) |
When retrying, Retry Execution receives the failure context (which gates
failed and their output) and invokes a coding agent to fix only the issues
causing those failures. The result loops back to Run Verify Gates for
another round.
The maximum is 3 retries (4 total attempts: 1 initial + 3 retries).
10. Chaining to Maintenance
On success, if actions.maintain=true was forwarded through the chain,
Route Gate Result also emits maintenance_requested. This triggers the
Maintenance Workflow without re-querying the
project configuration.
Self-Filtering
Several blocks use self-filtering to stop the chain gracefully without errors:
- Assess Project — skips when
workflow != "iterate"or preflight failed - Check Charter — skips when
workflow != "iterate" - Create Plan — skips when
accepted != true(triage rejected) - Summarize Result — skips when
success != true
The engine routes by event type only and cannot inspect payloads. Each block checks the relevant payload fields and returns an empty result when the condition does not match.
Running the Workflow
Direct trigger
To run the iterate workflow for a single project:
foundry emit iteration_requested my-project \
--payload '{"actions":{"iterate":true,"maintain":false}}'
With maintenance chaining
To iterate and then run maintenance:
foundry emit iteration_requested my-project \
--payload '{"actions":{"iterate":true,"maintain":true}}'
Via the maintenance run
The full maintenance lifecycle triggers iteration automatically when
iterate=true in the project's registry entry:
foundry emit maintenance_run_started my-project
Audit only
Observers run and emit, but mutators suppress downstream events:
foundry emit iteration_requested my-project \
--throttle audit_only \
--payload '{"actions":{"iterate":true,"maintain":false}}'
This runs the assessment and triage phases but stops at execution — useful for seeing what would be improved without modifying any files.
Dry run
Only observers execute. Mutators are skipped entirely:
foundry emit iteration_requested my-project \
--throttle dry_run \
--payload '{"actions":{"iterate":true,"maintain":false}}'
Agent Capabilities
Foundry delegates AI work to the Claude CLI, mapping each block's capability hint to a concrete model:
| Capability | Model | Use Case |
|---|---|---|
| Reasoning | claude-opus-4-6 | Deep analysis and planning |
| Coding | claude-sonnet-4-6 | Code generation and modification |
| Quick | claude-haiku-4-5-20251001 | Fast, lightweight decisions |
Access levels control which CLI tools the agent may use:
| Access | Allowed Tools |
|---|---|
| Read-only | Read, Glob, Grep, WebFetch, WebSearch |
| Full | All tools (no restrictions) |
Each phase in the iterate workflow maps to a specific capability and access level:
| Phase | Capability | Model | Access | Purpose |
|---|---|---|---|---|
| Assessment | Reasoning | Opus | Read-only | Deep analysis of codebase |
| Audit naming | Quick | Haiku | Read-only | Generate kebab-case filename |
| Triage | Quick | Haiku | Read-only | Accept/reject decision |
| Plan creation | Reasoning | Opus | Read-only | Step-by-step correction plan |
| Execution | Coding | Sonnet | Full | Apply code changes |
| Retry | Coding | Sonnet | Full | Fix gate failures |
| Summarisation | Quick | Haiku | Read-only | Generate headline and summary |
All agent invocations use the --print flag (non-interactive output) and
--dangerously-skip-permissions (unattended execution). Blocks that
reference a project agent file pass it via --agent. Timeouts are set
per-project from the registry entry, except Triage and Summarisation which
use a fixed 120-second timeout for their lightweight Quick calls.
Payload Fields
| Field | Carried By | Purpose |
|---|---|---|
actions | All events | {iterate, maintain} flags for workflow routing |
gates | Gates resolved through plan creation | Gate definitions for execution context |
audit_name | Assessment through plan creation | Kebab-case audit filename for traceability |
severity | Assessment through plan creation | Violation severity (1–10) |
principle | Assessment through plan creation | Name of the violated principle |
category | Assessment through plan creation | Category of the violation |
assessment | Assessment through plan creation | Detailed assessment text |
retry_count | Execution through gate routing | Current retry attempt (0-based) |
failure_context | Retry requested | Gate output from failed verification |
Strategic Iteration
Strategic iteration wraps the standard iterate workflow in an outer loop. Instead of finding and fixing one issue, it assesses the project holistically, identifies multiple areas for improvement, and works through them one at a time — committing changes after each cycle and re-evaluating whether further work is warranted.
This is a two-layer nested loop:
- Outer loop (strategic) — AI identifies areas to improve, decides when to stop
- Inner loop (operational) — the existing iterate formation: assess, triage, plan, execute, verify gates, retry
Prerequisites
Before running a strategic iteration you need the same setup as a standard iterate workflow:
- A project registered in the Foundry registry (
foundry registry add) - A
CHARTER.md(or equivalent intent documentation) in the project root - A
.hone-gates.jsondefining quality gates - The
foundryddaemon running
If you can successfully run foundry iterate my-project, you are ready
for strategic iteration.
Step 1: Start the Daemon
foundryd
Verify the strategic blocks are registered in the startup logs:
INFO foundryd::engine: registered task block block="Strategic Assessor" sinks=[IterationRequested]
INFO foundryd::engine: registered task block block="Strategic Loop Controller" sinks=[StrategicAssessmentCompleted, InnerIterationCompleted]
Step 2: Dry Run First
Before committing to a full strategic run, use dry_run throttle to see
the shape of the event chain without modifying any files:
foundry emit iteration_requested my-project \
--throttle dry_run \
--payload '{"strategic": true, "max_iterations": 3}'
Watch the event stream in another terminal:
foundry watch --project my-project
You should see events flowing through the strategic assessment, into
the inner iterate chain, and back to the strategic controller. Mutator
blocks (Execute Plan, Commit and Push) emit simulated success events
under dry_run.
Step 3: Run a Strategic Iteration
foundry emit iteration_requested my-project \
--payload '{"strategic": true, "max_iterations": 5}'
What happens
flowchart TD
A([iteration_requested<br/>strategic=true]) --> B[[Strategic Assessor]]
B -->|AI holistic assessment| C([strategic_assessment_completed])
C --> D[[Strategic Loop Controller]]
D -->|pick first area| E([iteration_requested<br/>with loop_context])
E --> F[Inner Iterate Formation]
F --> G([inner_iteration_completed])
G --> H[[Commit and Push<br/>per-iteration commit]]
G --> D
D -->|AI: continue?| I{More work?}
I -->|yes| E
I -->|no| J([project_iteration_completed])
J --> K[[Summarize Result]]
J --> L[[Commit and Push]]
-
Strategic Assessor analyses the codebase and produces a ranked list of improvement areas (e.g., "test coverage in auth module", "inconsistent error handling in API layer").
-
Strategic Loop Controller picks the top area and enters the inner iterate formation by emitting
iteration_requestedwith aloop_contextpayload. -
The inner iterate formation runs exactly as described in the Iteration Workflow guide — charter check, gate resolution, preflight, assessment, triage, plan, execute, verify, retry. The only difference is that
Route Gate Resultemitsinner_iteration_completedinstead ofproject_iteration_completed. -
Commit and Push fires on
inner_iteration_completed, creating a per-iteration commit so each improvement is isolated in git history. -
Strategic Loop Controller receives
inner_iteration_completedand asks an AI agent: "Is there more meaningful work to do, or has the codebase reached a plateau?" Based on the response and the iteration cap, it either re-enters the inner loop or completes. -
When the loop completes, the controller emits
project_iteration_completedwithoutloop_context, which triggersSummarize Resultand a finalCommit and Push.
Step 4: Monitor Progress
Watch the live event stream:
foundry watch --project my-project
Key events to look for:
| Event | Meaning |
|---|---|
strategic_assessment_completed | AI identified areas; check areas in payload |
iteration_requested (with loop_context) | Inner loop entered for an area |
inner_iteration_completed | One cycle done; check success |
project_changes_committed | Per-iteration commit created |
project_iteration_completed | Strategic loop finished |
summarize_completed | Final summary generated |
Step 5: Review the Trace
After completion, inspect the full event chain:
foundry trace <event-id>
Use the event ID from the original iteration_requested event. The
trace shows every block that executed, what it emitted, and how long
it took — across all iterations of the loop.
Payload Reference
Entry payload
| Field | Type | Default | Purpose |
|---|---|---|---|
strategic | bool | false | Must be true to activate strategic mode |
max_iterations | integer | 5 | Maximum number of inner loop cycles |
strategic_prompt | string | (built-in) | Custom directive for the assessment and continue checks (see below) |
actions | object | — | Optional {maintain: true} to chain maintenance after the loop completes |
loop_context (internal)
The loop_context object is managed by the strategic blocks. You do not
set it manually — it is created by the Strategic Assessor and threaded
through the chain automatically.
{
"loop_context": {
"strategic": {
"iteration": 2,
"max": 5,
"total_areas": 3,
"current_area": {"area": "test coverage", "severity": 8}
}
}
}
All blocks in the iterate chain forward loop_context transparently.
Terminal blocks (Summarize Result, Commit and Push) skip when they
see it on a completion event — they only fire on the final completion
that the Strategic Loop Controller emits without loop_context.
Controlling the Loop
Iteration cap
The max_iterations field is the hard stop. Even if the AI wants to
continue, the loop completes when this count is reached. Start with a
low number (2–3) and increase once you are comfortable with the results.
Custom prompt
By default the Strategic Assessor analyses broad code quality and the
continue check looks for remaining violations. You can replace both
directives with a strategic_prompt to steer the loop toward a
specific goal:
foundry emit iteration_requested my-project \
--payload '{
"strategic": true,
"max_iterations": 5,
"strategic_prompt": "Pick the highest priority interaction from et and implement it."
}'
The prompt you supply is used in two places:
- Assessment — the Strategic Assessor wraps your prompt in a
request to produce the
areasJSON. The AI reads the codebase with your directive as context and returns a ranked list of work items. - Continue check — after each inner iteration, the Strategic Loop
Controller asks the AI the same prompt again to decide whether the
loop should continue. The AI responds with
{"continue": true/false}.
The prompt is stored inside loop_context.strategic.prompt so it
survives the full inner chain and is available on every re-entry.
Some examples:
| Goal | Prompt |
|---|---|
| Product work from a backlog | "Pick the highest priority interaction from et and implement it." |
| Focused refactoring | "Find the module with the worst test coverage and add tests." |
| Dependency modernisation | "Identify deprecated dependencies and upgrade them one at a time." |
| Security hardening | "Find the most critical OWASP Top 10 risk in this codebase and fix it." |
AI continue assessment
After each inner iteration, the Strategic Loop Controller asks an AI
agent whether further improvement is warranted. When no strategic_prompt
is set, the agent considers:
- Are there remaining violations with severity >= 4?
- Would another iteration produce meaningful improvement?
- Has the codebase reached a diminishing-returns plateau?
When strategic_prompt is set, your prompt replaces this default
reasoning — the AI uses your directive to decide whether more work
remains.
If the inner iteration failed (gates did not pass after retries), the loop stops immediately rather than attempting further iterations on a broken codebase.
Throttle interaction
| Throttle | Strategic Assessor | Inner Iterate | Commits |
|---|---|---|---|
full | Runs | Runs (modifies files) | Real commits |
audit_only | Runs | Runs, but mutator events suppressed | No commits |
dry_run | Runs | Simulated success | No commits |
Chaining with Maintenance
To run a strategic iteration and then chain into maintenance:
foundry emit iteration_requested my-project \
--payload '{"strategic": true, "max_iterations": 3, "actions": {"maintain": true}}'
Maintenance chaining is suppressed during the inner loop (to prevent
maintenance from running after every cycle). It only fires after the
strategic loop completes — the final project_iteration_completed
event triggers Route Gate Result's normal chaining logic.
Example: Full Session
# Terminal 1: start daemon
foundryd
# Terminal 2: watch events
foundry watch --project my-api
# Terminal 3: trigger strategic iteration
foundry emit iteration_requested my-api \
--payload '{"strategic": true, "max_iterations": 3}'
# After completion, review the trace
foundry trace evt_abc123def456
Expected event flow for a 2-iteration run:
iteration_requested (strategic=true)
strategic_assessment_completed (areas: [test coverage, error handling])
iteration_requested (loop_context: iteration=1)
charter_check_completed
gate_resolution_completed
preflight_completed
assessment_completed
triage_completed
plan_completed
execution_completed
gate_verification_completed
inner_iteration_completed (success=true)
project_changes_committed
iteration_requested (loop_context: iteration=2)
charter_check_completed
...
inner_iteration_completed (success=true)
project_changes_committed
project_iteration_completed (no loop_context — loop done)
summarize_completed
project_changes_committed
Agent Capabilities
Strategic iteration adds two blocks with their own agent invocations on top of the standard iterate chain:
| Phase | Capability | Model | Access | Purpose |
|---|---|---|---|---|
| Strategic Assessor | Reasoning | claude-opus-4-6 | Read-only | Holistic codebase analysis producing ranked improvement areas |
| Continue check | Quick | claude-haiku-4-5-20251001 | Read-only | Decide whether the loop should continue after each cycle |
The inner iterate formation uses the same model assignments described in the Iteration Workflow guide.
When a strategic_prompt is set, both the assessment and continue-check
prompts incorporate it — the same custom directive steers area selection
and termination decisions.
Comparison with Standard Iterate
| Aspect | Standard Iterate | Strategic Iterate |
|---|---|---|
| Entry payload | {} or {actions: ...} | {strategic: true, max_iterations: N} |
| Scope | One issue per run | Multiple issues per run |
| Commits | One at the end | One per inner cycle + one final |
| Continue logic | None (single pass) | AI re-assessment after each cycle |
| Inner retry | Up to 3 retries on gate failure | Same (unchanged) |
| Maintenance chaining | After single completion | After loop completion only |
Maintenance Workflow
The maintenance workflow runs iterate and maintain automation against each
registered project, committing and pushing any changes they produce. It is
designed to be triggered nightly via launchd or manually via foundry emit.
How It Works
Each project goes through its own independent chain. The chain is driven entirely by events — no mutable state is shared between projects.
Per-Project Chain
flowchart TD
A([maintenance_run_started]) --> B[[Validate Project]]
B --> C([project_validation_completed])
C --> D[[Route Project Workflow]]
D -->|iterate=true| E([iteration_requested])
D -->|iterate=false, maintain=true| F([maintenance_requested])
D -->|no actions enabled| G([end])
E --> H[[Resolve Gates]]
H --> I[[Run Preflight Gates]]
I --> J[[Check Charter]]
J --> K[[Assess Project]]
K --> L[[Triage Assessment]]
L --> M[[Create Plan]]
M --> N[[Execute Plan]]
N --> O[[Run Verify Gates]]
O --> P[[Route Gate Result]]
P -->|pass| Q([project_iteration_completed])
P -->|fail, retries left| R[[Retry Execution]]
Q -->|maintain=true| F
F --> S[[Resolve Gates]]
S --> T[[Execute Maintain]]
T --> U[[Run Verify Gates]]
U --> V[[Route Gate Result]]
V -->|pass| W([project_maintenance_completed])
V -->|fail, retries left| X[[Retry Execution]]
Routing Logic
Route Project Workflow reads the actions flags forwarded in the
project_validation_completed payload and makes a single decision:
| Condition | Emits |
|---|---|
status != "ok" | nothing — chain stops |
actions.iterate = true | iteration_requested |
actions.iterate = false, actions.maintain = true | maintenance_requested |
| both false | nothing — no automation enabled |
When iterate = true, the actions.maintain flag is forwarded inside the
iteration_requested payload. After a successful iteration, the gate routing
emits maintenance_requested automatically when that flag is true,
so the maintain sub-workflow starts without an extra routing step.
Triggering a Maintenance Run
To run maintenance for a single project:
foundry emit project_validation_completed my-project \
--payload '{"status":"ok","actions":{"iterate":true,"maintain":true}}'
To trigger the full nightly cycle:
foundry emit maintenance_run_started my-project
Throttle Behaviour
| Throttle | Effect |
|---|---|
full | All blocks execute and emit events |
audit_only | Observers emit; mutators suppress output |
dry_run | Observers emit; mutators are skipped entirely |
Under dry_run, only iteration_requested or maintenance_requested are
emitted (by the Observer router). No execution blocks run.
Agent Capabilities
The maintenance workflow uses a single agent invocation in Execute Maintain:
| Phase | Capability | Model | Access | Purpose |
|---|---|---|---|---|
| Execute Maintain | Coding | claude-sonnet-4-6 | Full | Update dependencies, fix vulnerabilities, resolve gate failures |
Gate definitions are passed as context so the agent knows what must pass
after its changes. If the project has an agent file registered, it is
supplied via --agent.
See the Iteration Workflow for the full model-to-capability mapping and CLI parameters used across all agent invocations.
Throttle Control
Throttle controls how far an event ripples through the task block chain. It's set at invocation time and propagated through every event in the chain.
Levels
| Level | Observers | Mutators | Use case |
|---|---|---|---|
full | Execute + emit | Execute + emit | Automated runs, nightly maintenance |
audit_only | Execute + emit | Execute + suppress emission | "Just check" — audit without releasing |
dry_run | Execute + emit | Skip execution entirely | Preview what would happen |
How It Works
The throttle is a property of the event, not the task block. When a block emits downstream events, those events carry the same throttle as the triggering event. This means the throttle decision is made once (at invocation) and respected throughout the chain.
foundry emit vulnerability_detected --project my-tool --throttle audit_only
vulnerability_detected (throttle: audit_only)
→ Audit Main Branch (Observer) → executes, emits main_branch_audited
→ Cut Release (Mutator) → executes, but SUPPRESSES release_completed
(chain stops here — no further propagation)
Observer vs Mutator
The key design question for every task block: is it an Observer or a Mutator?
- Observer: reads state, runs scans, checks conditions. Never changes the world. Always runs, always emits, regardless of throttle.
- Mutator: writes files, pushes commits, cuts releases, installs tools. Changes the world. Throttle controls whether it runs and emits.
At audit_only, a Mutator still executes (so you see what it would
do in the logs) but suppresses emission (so downstream blocks don't
fire). At dry_run, Mutators don't execute at all.
CLI Usage
# Default: full
foundry emit greet_requested --project hello
# Explicit throttle
foundry emit greet_requested --project hello --throttle audit_only
foundry emit greet_requested --project hello --throttle dry_run
Writing Task Blocks
To add a new task block to Foundry:
- Implement the
TaskBlocktrait - Register it with the engine in
main.rs
The TaskBlock Trait
#![allow(unused)] fn main() { use std::pin::Pin; use foundry_core::event::{Event, EventType}; use foundry_core::task_block::{BlockKind, TaskBlock, TaskBlockResult}; pub struct MyBlock; impl TaskBlock for MyBlock { fn name(&self) -> &'static str { "My Block" } fn kind(&self) -> BlockKind { BlockKind::Observer // or BlockKind::Mutator } fn sinks_on(&self) -> &[EventType] { &[EventType::GreetRequested] // which events trigger this block } fn execute( &self, trigger: &Event, ) -> Pin<Box<dyn std::future::Future<Output = anyhow::Result<TaskBlockResult>> + Send + '_>> { let project = trigger.project.clone(); let throttle = trigger.throttle; Box::pin(async move { // Do your work here... Ok(TaskBlockResult { events: vec![ Event::new( EventType::GreetingComposed, project, throttle, serde_json::json!({"result": "done"}), ), ], success: true, summary: "Did the thing".to_string(), raw_output: None, exit_code: None, audit_artifacts: vec![], }) }) } } }
Key Points
- Propagate throttle: always pass
trigger.throttleto emitted events - Clone what you need: extract data from
triggerbefore theasync moveblock - Return events: the engine handles routing them to downstream blocks
- Observer vs Mutator: choose based on whether your block has side effects
TaskBlockResult Fields
| Field | Type | Description |
|---|---|---|
events | Vec<Event> | Events to emit downstream (subject to throttle) |
success | bool | Whether the block's work succeeded |
summary | String | Human-readable one-line summary shown in traces |
raw_output | Option<String> | Combined stdout+stderr from any shell command — shown in foundry trace --verbose |
exit_code | Option<i32> | Exit code from any shell command — useful for observability |
audit_artifacts | Vec<String> | Paths to files produced by this block (e.g. audit logs). Listed under artifacts: in verbose trace output |
Blocks that do not run external processes should set raw_output: None,
exit_code: None, and audit_artifacts: vec![]. Blocks that shell out should
populate raw_output with the combined output and exit_code with the process
exit code so that traces provide full observability without needing to reproduce
the command.
Registering
In foundryd/src/main.rs:
#![allow(unused)] fn main() { let mut engine = engine::Engine::new(); engine.register(Box::new(blocks::MyBlock)); }
RetryPolicy
Override retry_policy() to enable automatic retry of transient failures.
The default is zero retries (execute exactly once).
#![allow(unused)] fn main() { use std::time::Duration; use foundry_core::task_block::RetryPolicy; fn retry_policy(&self) -> RetryPolicy { RetryPolicy { max_retries: 3, backoff: Duration::from_secs(5), } } }
With max_retries: N, the engine tries the block up to N + 1 times total
(1 initial attempt plus up to N retries), sleeping backoff between each
attempt. Both Err results and TaskBlockResult { success: false, .. } trigger
a retry. The final attempt's outcome is what appears in the BlockExecution
trace.
Use retries for operations that may fail transiently (network calls, shell commands that occasionally time out). Do not use retries for operations that are expected to fail deterministically (e.g. self-filtering by payload).
Gateway Pattern
Task blocks that execute external processes (shell commands, audit tools) receive those capabilities through gateway traits rather than calling the implementation directly. This isolates I/O at the block boundary and makes every block fully testable without spawning real processes.
The ShellGateway Trait
#![allow(unused)] fn main() { pub trait ShellGateway: Send + Sync { fn run<'a>( &'a self, working_dir: &'a Path, command: &'a str, args: &'a [&'a str], env: Option<&'a [(String, String)]>, timeout: Option<Duration>, ) -> Pin<Box<dyn Future<Output = anyhow::Result<CommandResult>> + Send + 'a>>; } }
In production, ProcessShellGateway delegates to crate::shell::run. Blocks
accept the gateway through their constructor:
#![allow(unused)] fn main() { pub struct MyBlock { registry: Arc<Registry>, shell: Arc<dyn ShellGateway>, } impl MyBlock { pub fn new(registry: Arc<Registry>) -> Self { Self { registry, shell: Arc::new(ProcessShellGateway), } } #[cfg(test)] fn with_shell(registry: Arc<Registry>, shell: Arc<dyn ShellGateway>) -> Self { Self { registry, shell } } } }
Testing with Fakes
gateway::fakes (available only under #[cfg(test)]) provides pre-built fakes:
#![allow(unused)] fn main() { use crate::gateway::fakes::{FakeShellGateway, FakeScannerGateway}; use crate::shell::CommandResult; // Always return a successful, empty result. let shell = FakeShellGateway::success(); // Always return a failure with the given stderr. let shell = FakeShellGateway::failure("not installed"); // Return a fixed result every time. let shell = FakeShellGateway::always(CommandResult { ... }); // Return results in sequence (last one repeats). let shell = FakeShellGateway::sequence(vec![first_result, second_result]); // Inspect recorded invocations after the fact. let invocations = shell.invocations(); assert_eq!(invocations[0].command, "git"); }
For scanner-based blocks:
#![allow(unused)] fn main() { let scanner = FakeScannerGateway::clean(); let scanner = FakeScannerGateway::with_vulnerabilities(vec![...]); let scanner = FakeScannerGateway::with_error("cargo audit not installed"); }
This pattern allows testing every code path — including failure modes and edge cases — without any real I/O:
#![allow(unused)] fn main() { #[tokio::test] async fn detached_head_recovery_succeeds() { let dir = tempfile::tempdir().expect("tempdir"); let registry = make_registry(/* ... */); // First call returns "HEAD" (detached); second call (checkout) succeeds. let shell = FakeShellGateway::sequence(vec![ CommandResult { stdout: "HEAD\n".into(), exit_code: 0, success: true, .. }, CommandResult { stdout: String::new(), exit_code: 0, success: true, .. }, ]); let block = ValidateProject::with_shell(registry, shell); let result = block.execute(&trigger).await.unwrap(); assert_eq!(result.events[0].payload["status"], "ok"); } }
File Organisation
Place block implementations in foundryd/src/blocks/:
blocks/
├── mod.rs # pub use declarations
├── greet.rs # hello-world blocks (ComposeGreeting, DeliverGreeting)
├── validate.rs # ValidateProject
├── resolve_gates.rs # ResolveGates
├── run_preflight_gates.rs # RunPreflightGates
├── run_verify_gates.rs # RunVerifyGates
├── route_gate_result.rs # RouteGateResult
├── route_validation_result.rs # RouteValidationResult
├── check_charter.rs # CheckCharter
├── assess_project.rs # AssessProject
├── triage_assessment.rs # TriageAssessment
├── create_plan.rs # CreatePlan
├── execute_plan.rs # ExecutePlan
├── execute_maintain.rs # ExecuteMaintain
├── retry_execution.rs # RetryExecution
├── summarize_result.rs # SummarizeResult
├── git_ops.rs # CommitAndPush
├── audit.rs # AuditReleaseTag, AuditMainBranch
├── release.rs # CutRelease, WatchPipeline
├── install.rs # InstallLocally
├── remediate.rs # RemediateVulnerability
└── scan.rs # ScanDependencies
CLI Commands
The foundry CLI communicates with a running foundryd daemon over gRPC.
Global Options
| Option | Default | Description |
|---|---|---|
--addr <url> | http://127.0.0.1:50051 | Daemon address |
foundry emit
Emit an event into the system. May trigger a workflow chain.
foundry emit <event_type> --project <project> [--throttle <level>] [--payload <json>] [--wait]
| Argument | Required | Description |
|---|---|---|
event_type | Yes | Event type name (positional) |
--project | Yes | Target project |
--throttle | No | full, audit_only, or dry_run (default: full) |
--payload | No | JSON string with event-specific data |
--wait | No | Block until processing completes, then display the trace |
By default, emit returns immediately after the daemon accepts the event.
Use --wait to block until the full event chain finishes and then display
the trace output (equivalent to running foundry trace after completion).
Output (default):
Event emitted: evt_47fcb603e1b18c8435b8cc3b
Output (with --wait):
Event emitted: evt_47fcb603e1b18c8435b8cc3b
Waiting for processing to complete...
greet_requested (evt_47fcb603e1b18c8435b8cc3b) project=hello
→ Compose Greeting (1ms): ok — composed greeting for Stacey
greeting_composed (evt_a1b2c3d4e5f6) project=hello
→ Deliver Greeting (0ms): ok — delivered greeting: Hello, Stacey!
---
Total: 2ms (blocks: 1ms)
foundry status
Show status of active workflows. Queries the daemon for workflows that are
currently being processed in the background. The daemon tracks these via an
in-memory WorkflowTracker that is populated when each Emit request spawns
a background task and cleared on completion.
foundry status [workflow_id]
Without an argument, shows all active workflows. With a workflow ID, shows details for that specific workflow.
Output example:
evt_47fcb603e1b18c8435b8cc3b [iteration_requested] foundry — running
If no workflows are currently running:
No active workflows.
foundry watch
Stream live events as they are emitted in real time.
foundry watch [--project <project>]
| Option | Required | Description |
|---|---|---|
--project | No | Filter by project name; omit to see all projects |
Server-side streaming — stays open until interrupted (Ctrl-C). Each line shows
the event type, event ID, project, and payload (when non-empty).
Output example:
maintenance_run_started evt_abc project=my-tool
project_validation_completed evt_def project=my-tool
payload: {"status":"ok","has_gates":true}
project_iteration_completed evt_ghi project=my-tool
foundry run
Trigger a maintenance run for all active projects or a single named project.
foundry run [--project <project>] [--throttle <level>]
| Option | Required | Description |
|---|---|---|
--project | No | Limit run to a single project by name; omit to run all projects |
--throttle | No | full, audit_only, or dry_run (default: full) |
foundry run emits a maintenance_run_started event which triggers the
maintenance workflow chain: validate → iterate (if enabled) → maintain (if
enabled) → commit and push → post-push audit.
The command streams progress events in real time and exits automatically
when the daemon broadcasts a maintenance_run_completed event at the end of
the processing chain. This differs from foundry watch, which streams
indefinitely.
When --project is omitted, the project name sent to the daemon is "system",
which causes all active (non-skipped) projects to be processed.
Output:
Triggered maintenance run for my-tool
Event: evt_47fcb603e1b18c8435b8cc3b
[my-tool] maintenance_run_started
[my-tool] project_validation_completed (ok)
[my-tool] maintenance_run_completed (ok)
Use foundry trace <event_id> to inspect the full trace after the run completes.
foundry validate
Validate quality gates for one or more projects without running iterate or maintain workflows. This is a read-only operation — no code changes are made.
foundry validate <project>...
foundry validate --all
| Argument | Required | Description |
|---|---|---|
project | Yes (unless --all) | One or more project names (positional) |
--all | No | Validate all active projects in the registry |
For each project, emits a validation_requested event which triggers:
Resolve Gates → Run Preflight Gates → Route Validation Result →
validation_completed. No Mutator blocks are involved, so throttle level
is irrelevant.
Output example:
Validating mojentic-ts...
mojentic-ts: PASS
lint: ok (required)
format: ok (required)
test: ok (required)
build: ok (required)
security: ok (optional)
validation_requested (evt_007572156d627d7b1211d76f) project=mojentic-ts
→ Resolve Gates (0ms): ok — mojentic-ts: resolved 5 gates for validate workflow
gate_resolution_completed (evt_92531a666649d6464e569dc2) project=mojentic-ts
→ Run Preflight Gates (6931ms): ok — mojentic-ts: preflight gates passed
preflight_completed (evt_08b0f626599a23ee8c648a8c) project=mojentic-ts
→ Route Validation Result (3ms): ok — mojentic-ts: validation passed
validation_completed (evt_e60a246dfa9072414890fa24) project=mojentic-ts
---
Exits with code 0 if all projects pass, non-zero if any required gate fails. Optional gate failures are reported but do not affect the exit code.
foundry trace
View the trace of a completed event chain.
foundry trace <event_id> [--verbose]
| Argument | Required | Description |
|---|---|---|
event_id | Yes | Root event ID returned by foundry emit (positional) |
--verbose | No | Show trigger payloads, emitted payloads, raw shell output, and audit artifact paths |
Displays the full event propagation tree with block execution results.
Traces are stored on disk indefinitely under ~/.foundry/traces/YYYY-MM-DD/
and survive daemon restarts.
Output (default):
greet_requested (evt_47fcb603e1b18c8435b8cc3b) project=hello
→ ComposeGreeting: ok — composed greeting for Stacey
greeting_composed (evt_a1b2c3d4e5f6) project=hello
→ DeliverGreeting: ok — delivered greeting: Hello, Stacey!
greeting_delivered (evt_f6e5d4c3b2a1) project=hello
---
Total: 2ms (blocks: 1ms)
Output (with --verbose):
greet_requested (evt_47fcb603e1b18c8435b8cc3b) project=hello
→ ComposeGreeting (1ms): ok — composed greeting for Stacey
trigger: {"name":"Stacey"}
emitted[0]: {"greeting":"Hello, Stacey!"}
greeting_composed (evt_a1b2c3d4e5f6) project=hello
→ DeliverGreeting (0ms): ok — delivered greeting: Hello, Stacey!
---
Total: 2ms (blocks: 1ms)
If the trace is unknown:
No trace found for evt_unknown (expired or unknown).
foundry history
Browse completed traces stored on disk.
foundry history [<date>] [--project <project>]
| Argument | Required | Description |
|---|---|---|
date | No | Date in YYYY-MM-DD format; omit to show the last 7 days |
--project | No | Filter results by project name |
Traces are read from ~/.foundry/traces/ (or FOUNDRY_TRACES_DIR). Each
row shows the event ID, success status, duration, event type, and project.
Dates with no traces are omitted.
Output example:
2026-03-22
┌──────────────────────────────┬────────┬──────────┬──────────────────────────┬───────────┐
│ Event ID │ Status │ Duration │ Type │ Project │
╞══════════════════════════════╪════════╪══════════╪══════════════════════════╪═══════════╡
│ evt_47fcb603e1b18c8435b8cc3b │ ok │ 312ms │ maintenance_run_started │ my-tool │
│ evt_a1b2c3d4e5f6789012345678 │ ok │ 48ms │ greet_requested │ hello │
└──────────────────────────────┴────────┴──────────┴──────────────────────────┴───────────┘
If no traces are found:
No traces found in the last 7 days.
foundry registry
Manage the project registry without editing the JSON file directly.
foundry registry <subcommand>
foundry registry init
Create an empty registry file at the default path (~/.foundry/registry.json).
Does nothing if the file already exists.
foundry registry init
foundry registry list
List all projects in the registry as a table.
foundry registry list
Output example:
┌──────────┬────────────┬──────┬──────────────────────────┬───────┐
│ Name │ Stack │ Skip │ Actions │ Skill │
╞══════════╪════════════╪══════╪══════════════════════════╪═══════╡
│ my-tool │ rust │ no │ iterate, maintain, push │ auto │
│ frontend │ typescript │ yes │ maintain, push │ │
└──────────┴────────────┴──────┴──────────────────────────┴───────┘
The Skill column shows auto (default derived command), cmd (custom command), off (explicitly disabled), or blank (not configured).
foundry registry show <name>
Show all details for a single project.
foundry registry show my-tool
Output example:
Name: my-tool
Path: /Users/alice/projects/my-tool
Stack: rust
Agent: claude
Repo: alice/my-tool
Branch: main
Skip: no
Actions: iterate, maintain, push
Install: brew: my-tool
Installs skill: yes (default -- runs my-tool init --global --force)
Timeout: 3600s (default)
foundry registry add
Add a new project to the registry. If the registry file does not exist, it is created automatically.
foundry registry add \
--name my-tool \
--path /Users/alice/projects/my-tool \
--stack rust \
--agent claude \
--repo alice/my-tool \
--branch main \
[--iterate] [--maintain] [--push] [--audit] [--release] \
[--install-command "cargo install --path ."] \
[--install-brew my-formula] \
[--notes "Human-readable notes about the project"] \
[--timeout-secs 3600]
| Option | Required | Description |
|---|---|---|
--name | Yes | Unique project name |
--path | Yes | Absolute path to the project |
--stack | Yes | Technology stack: rust, python, typescript, elixir, cpp |
--agent | Yes | AI agent name (e.g. claude) |
--repo | Yes | GitHub slug (owner/repo) |
--branch | No | Default branch (default: main) |
--iterate | No | Enable iterate action |
--maintain | No | Enable maintain action |
--push | No | Enable push action |
--audit | No | Enable audit action |
--release | No | Enable release action |
--install-command | No | Shell command to run for local install |
--install-brew | No | Homebrew formula name |
--notes | No | Human-readable notes |
--timeout-secs | No | Command timeout in seconds (default: 1800) |
foundry registry remove <name>
Remove a project from the registry. Errors if the project is not found.
foundry registry remove my-tool
foundry registry edit <name>
Update settings for an existing project. Only the fields you pass are changed; all others are left as-is.
foundry registry edit my-tool \
--skip "Waiting for CI to stabilise" \
--timeout-secs 3600
| Option | Description |
|---|---|
--path | Update the project path |
--stack | Update the technology stack |
--agent | Update the agent name |
--repo | Update the GitHub slug |
--branch | Update the default branch |
--skip | Set a skip reason (pass empty string "" to clear the skip) |
--iterate | Set iterate action (true/false) |
--maintain | Set maintain action |
--push | Set push action |
--audit | Set audit action |
--release | Set release action |
--install-command | Set install command |
--install-brew | Set Homebrew formula |
--notes | Set notes (pass empty string "" to clear) |
--timeout-secs | Set command timeout in seconds |
gRPC API
The Foundry service is defined in proto/foundry.proto.
Service: Foundry
Emit(EmitRequest) → EmitResponse
Fire an event into the system. The engine spawns processing as a background
task and returns the event ID immediately. Use Trace to check for
completion, Status to see in-flight workflows, or Watch for real-time
event streaming.
Request:
| Field | Type | Description |
|---|---|---|
event_type | string | Event type name |
project | string | Target project |
throttle | Throttle enum | THROTTLE_FULL, THROTTLE_AUDIT_ONLY, THROTTLE_DRY_RUN |
payload_json | string | Optional JSON payload |
Response:
| Field | Type | Description |
|---|---|---|
event_id | string | Deterministic ID of the created event |
workflow_id | string | ID of the triggered workflow (if any) |
Status(StatusRequest) → StatusResponse
Query active workflow states.
Request:
| Field | Type | Description |
|---|---|---|
workflow_id | string | Specific workflow (empty for all active) |
Response:
| Field | Type | Description |
|---|---|---|
workflows | repeated WorkflowStatus | Active workflow states |
Watch(WatchRequest) → stream WatchResponse
Server-side streaming of live events as they are processed by the engine. Optionally filtered by project name.
Request:
| Field | Type | Description |
|---|---|---|
project | string | Project name to filter by; empty string for all projects |
Response (stream):
| Field | Type | Description |
|---|---|---|
event_id | string | Event identifier |
event_type | string | Event type name |
project | string | Target project |
payload_json | string | Event payload as JSON |
Trace(TraceRequest) → TraceResponse
Retrieve the trace of a completed event chain. Returns all events produced during processing and a record of each block execution.
Request:
| Field | Type | Description |
|---|---|---|
event_id | string | Root event ID to look up |
Response:
| Field | Type | Description |
|---|---|---|
found | bool | Whether a trace was found for the given event ID |
events | repeated TraceEvent | All events in the chain |
block_executions | repeated TraceBlockExecution | Record of each block execution |
Completed traces are persisted to disk under ~/.foundry/traces/YYYY-MM-DD/
and survive daemon restarts. The Trace RPC checks the in-memory store first
(for recently completed chains) and falls back to disk for older traces.
Messages
WorkflowStatus
| Field | Type | Description |
|---|---|---|
workflow_id | string | Workflow identifier |
workflow_type | string | Workflow type name |
project | string | Target project |
state | string | pending, running, completed, failed |
started_at | string | ISO 8601 timestamp |
completed_at | string | ISO 8601 timestamp (empty if running) |
task_blocks | repeated TaskBlockStatus | Per-block status |
TaskBlockStatus
| Field | Type | Description |
|---|---|---|
name | string | Block name |
state | string | pending, running, completed, skipped, failed |
started_at | string | ISO 8601 timestamp |
completed_at | string | ISO 8601 timestamp |
throttled | bool | True if emission was suppressed by throttle |
TraceEvent
| Field | Type | Description |
|---|---|---|
event_id | string | Deterministic event identifier |
event_type | string | Event type name |
project | string | Target project |
occurred_at | string | ISO 8601 timestamp |
throttle | Throttle enum | Throttle level for this event |
TraceBlockExecution
| Field | Type | Description |
|---|---|---|
block_name | string | Name of the block that executed |
trigger_event_id | string | Event ID that triggered this block |
success | bool | Whether the block succeeded |
summary | string | Human-readable summary of the result |
emitted_event_ids | repeated string | IDs of events emitted by this block |
duration_ms | uint64 | Wall-clock milliseconds for this block execution (including retries) |
raw_output | string | Combined stdout+stderr from any shell command run by this block |
exit_code | int32 | Exit code from any shell command run by this block |
trigger_payload_json | string | JSON payload of the event that triggered this block |
emitted_payload_jsons | repeated string | JSON payloads of events emitted by this block |
audit_artifacts | repeated string | Paths to audit artefact files produced by this block |
Throttle (enum)
| Value | Number | Description |
|---|---|---|
THROTTLE_FULL | 0 | All blocks emit |
THROTTLE_AUDIT_ONLY | 1 | Observers emit, mutators suppress |
THROTTLE_DRY_RUN | 2 | Read-only, no side effects |
Event Types
All event types are defined in foundry-core/src/event.rs as the EventType
enum. The string representation uses snake_case.
Every event carries these common fields:
| Field | Type | Description |
|---|---|---|
id | string | Deterministic SHA-256 derived ID, prefixed evt_ |
event_type | string | Snake-case event type name |
project | string | Project this event relates to |
occurred_at | RFC 3339 timestamp | When the event happened |
recorded_at | RFC 3339 timestamp | When the event was logged |
throttle | string | full, audit_only, or dry_run |
payload | JSON object | Event-type-specific fields (see below) |
Hello-World (engine validation)
| Type | Description |
|---|---|
greet_requested | Request to compose and deliver a greeting |
greeting_composed | Greeting message has been composed |
greeting_delivered | Greeting has been delivered (side effect) |
greet_requested payload
| Field | Type | Description |
|---|---|---|
name | string | Name to greet |
greeting_composed payload
| Field | Type | Description |
|---|---|---|
greeting | string | Composed greeting text |
Vulnerability Remediation
| Type | Description |
|---|---|
scan_requested | Request to scan a project for known vulnerabilities |
vulnerability_detected | A vulnerability was found (or injected externally) |
release_tag_audited | Latest release tag scanned for the vulnerability |
main_branch_audited | Main branch checked for the same vulnerability |
remediation_started | Automated fix attempt initiated |
remediation_completed | Fix attempt finished (success or failure) |
vulnerability_detected payload
| Field | Type | Description |
|---|---|---|
cve | string | CVE or advisory ID (e.g. "CVE-2026-1234") |
vulnerable | bool | Whether the project is affected |
dirty | bool (optional) | Whether the main branch still contains the vulnerability |
release_tag_audited payload
| Field | Type | Description |
|---|---|---|
cve | string | CVE from the scan or forwarded from the trigger |
vulnerable | bool | Whether the release tag is affected |
dirty | bool (optional) | Forwarded from the upstream trigger for downstream routing |
main_branch_audited payload
| Field | Type | Description |
|---|---|---|
cve | string | CVE identifier |
dirty | bool | true if the vulnerability is still present on main |
remediation_completed payload
| Field | Type | Description |
|---|---|---|
cve | string | CVE that was remediated |
success | bool | Whether the fix was applied successfully |
Release Lifecycle
| Type | Description |
|---|---|
release_requested | Decision made to cut a patch release |
release_completed | Release tag created and pushed |
release_pipeline_completed | GitHub Actions build/publish workflow finished |
release_completed payload
| Field | Type | Description |
|---|---|---|
cve | string | CVE that prompted the release |
release | string | Release type (e.g. "patch") |
new_tag | string or null | Semver tag extracted from Claude CLI output |
success | bool | Whether the Claude CLI invocation succeeded |
release_pipeline_completed payload
| Field | Type | Description |
|---|---|---|
status | string | "success" or "failure" |
conclusion | string (optional) | GitHub Actions conclusion label |
Project Lifecycle
| Type | Description |
|---|---|
project_validation_completed | Pre-flight checks for a maintenance run |
project_iteration_completed | Iterate workflow finished |
project_maintenance_completed | Maintain workflow finished |
project_changes_committed | Git commit created |
project_changes_pushed | Changes pushed to remote |
project_validation_completed payload
| Field | Type | Description |
|---|---|---|
status | string | "ok", "error", or "skipped" |
reason | string (optional) | Human-readable explanation when status is not "ok" |
has_gates | bool (optional) | Whether .hone-gates.json is present (only on "ok") |
project_iteration_completed payload
| Field | Type | Description |
|---|---|---|
project | string | Project name |
workflow | string | "iterate" |
success | bool | Whether the iterate workflow succeeded |
summary | string | Human-readable summary of the result |
changes | bool (optional) | Whether code changes were made |
project_maintenance_completed payload
| Field | Type | Description |
|---|---|---|
project | string | Project name |
workflow | string | "maintain" |
success | bool | Whether the maintain workflow succeeded |
summary | string | Human-readable summary of the result |
changes | bool (optional) | Whether code changes were made |
project_changes_committed payload
| Field | Type | Description |
|---|---|---|
cve | string | CVE or "unknown" (from remediation path) |
message | string | Git commit message used |
project_changes_pushed payload
| Field | Type | Description |
|---|---|---|
cve | string | CVE or "unknown" (from remediation path) |
Local Install
| Type | Description |
|---|---|
local_install_completed | Local tool reinstallation finished |
Maintenance Workflow
| Type | Payload | Description |
|---|---|---|
iteration_requested | { project } | Triggers the iterate sub-workflow for a validated project |
maintenance_requested | { project } | Triggers the maintain sub-workflow for a validated project |
Gate Orchestration
| Type | Description |
|---|---|
gate_resolution_completed | Gate definitions loaded from .hone-gates.json |
preflight_completed | Gates passed/failed on unmodified codebase |
execution_completed | Code changes applied (emitted by future execution blocks) |
gate_verification_completed | Gates passed/failed after execution |
retry_requested | Gate failure triggers bounded retry |
gate_resolution_completed payload
| Field | Type | Description |
|---|---|---|
project | string | Project name |
workflow | string | "iterate", "maintain", or "validate" |
gates | array | Gate definitions (name, command, required, timeout_secs) |
actions | object (optional) | Forwarded actions from the trigger event |
preflight_completed payload
| Field | Type | Description |
|---|---|---|
project | string | Project name |
workflow | string | Workflow that triggered the preflight |
all_passed | bool | Whether every gate passed |
required_passed | bool | Whether all required gates passed |
results | array | Per-gate results (name, passed, required, output, exit_code) |
gate_verification_completed payload
| Field | Type | Description |
|---|---|---|
project | string | Project name |
workflow | string | Originating workflow |
all_passed | bool | Whether every gate passed |
required_passed | bool | Whether all required gates passed |
retry_count | number | Current retry count (0 on first attempt) |
results | array | Per-gate results |
retry_requested payload
| Field | Type | Description |
|---|---|---|
project | string | Project name |
workflow | string | Originating workflow |
retry_count | number | Incremented retry count |
failure_context | string | Gate output from the failed verification |
actions | object (optional) | Forwarded actions |
Validation
| Type | Description |
|---|---|
validation_requested | Request to validate a project's gate health |
validation_completed | Terminal event with per-gate pass/fail results |
validation_requested payload
| Field | Type | Description |
|---|---|---|
project | string | Project name |
validation_completed payload
| Field | Type | Description |
|---|---|---|
project | string | Project name |
success | bool | Whether all required gates passed |
results | array | Per-gate results (name, passed, required, output snippet) |
Maintenance Run Lifecycle
| Type | Description |
|---|---|
maintenance_run_started | A maintenance run was triggered for a project |
maintenance_run_completed | All projects processed, summary available |
maintenance_run_started payload
| Field | Type | Description |
|---|---|---|
project | string | Project name this run covers |
maintenance_run_completed payload
| Field | Type | Description |
|---|---|---|
total | number | Total number of projects processed |
succeeded | number | Projects that completed successfully |
failed | number | Projects that encountered an error |
skipped | number | Projects that were skipped (already active or skip=true) |
projects | array | Per-project result objects (name, status, duration_secs) |
Release Tag Audit
| Type | Description |
|---|---|
release_tag_audited | Latest release tag scanned (see payload above) |