@color-sunset/fil
v0.3.0
Published
An open-source harness for agentic software-development lifecycles.
Readme
Fil
An open-source harness for agentic software-development lifecycles.
Fil is a harness that turns a software-development lifecycle into a first-class, machine-enforced spine. A developer and their AI Agent Runtimes are guided by a state machine — a Flow — whose structure the developer chooses and which evolves as the project progresses. Fil does not run models or agent loops: it owns the orchestration spine and steers external Agent Runtimes (Claude Code, Pi, …) to do the actual work.
Why
Most agent harnesses leave the SDLC to the human's discretion. Fil makes the SDLC itself the first-class, machine-enforced, evolving spine: at each Phase the active Agent Runtime is constrained (instructions, tools, context, skills) to that phase, and leaving a phase requires passing a user-defined Gate that produces a verification Receipt.
Highlights
- Flow as XState machine code — Lifecycles are state machines authored with
createMachine(...)from@color-sunset/fil-engine— the same shape as the canonical XState examples at https://stately.ai/docs/xstate. Fil owns the wrapper so Flow code never importsxstatedirectly. - Steer, don't run — Fil runs as a sidecar governor. The human keeps their Agent Runtime's native UX; Fil reconfigures it per Phase via per-runtime Adapters.
- User-owned Gates — every transition is guarded by an executable test (shell, test suite, API call, or human-confirmation prompt). Fil runs the Gate and captures a Receipt with pass/fail + evidence.
- Evolving Flows — humans edit Flow files directly; agents propose edits as
diffs in
.fil/proposals/, validated byfil approvebefore they shape future Runs. - Sidecar model — Fil is a thin CLI (
fil) over a monorepo of focused modules; durable state lives in.fil/, Flows are committed, Runs are not. - Engine-agnostic seam — XState is the default engine today; the
FlowEngineinterface allows future engines (a Python service, Temporal, …) to replace it without rewriting Flows.
Architecture
flowchart LR
subgraph Project["Project (.fil/ + git)"]
direction TB
Flows["Flows<br/>(.fil/flows/*.js)<br/>XState machine code"]
subgraph Run["Run (one Change)"]
direction TB
Phase["Phase<br/>(active, one at a time)"]
Receipts["Receipts<br/>(per Gate)"]
end
Store["@color-sunset/fil-store<br/>run.json projection"]
Contracts["@color-sunset/fil-contract<br/>schema"]
end
subgraph Fil["Fil CLI (the spine)"]
direction TB
Orchestrator["@color-sunset/fil-orchestrator<br/>startRun / advance / back / cancel"]
GateRunner["@color-sunset/fil-gate-runner<br/>runGate -> Receipt"]
Engine["@color-sunset/fil-engine<br/>FlowEngine seam<br/>+ XState impl"]
InspectView["@color-sunset/fil-inspect-view<br/>(view-only)"]
end
subgraph Adapters["Adapters (per Agent Runtime)"]
direction TB
Claude["Claude Adapter<br/>(hooks + MCP + skills)"]
Pi["Pi Adapter<br/>(extension + tool_call)"]
end
subgraph Runtimes["Agent Runtimes (the work)"]
direction TB
CC["Claude Code"]
PiRuntime["Pi"]
end
Flows --> Orchestrator
Orchestrator --> Engine
Orchestrator --> GateRunner
Orchestrator --> Phase
GateRunner -->|Receipts| Receipts
Engine --> InspectView
Contracts -->|run.json| Adapters
Adapters --> Runtimes
Runtimes -. human + agent .-> PhaseThe lifecycle hierarchy is Project → Flow → Run → Phase. A Project holds a library of Flows and a history of Runs. A Flow defines its Phases, Transitions, and Gates (committed as XState machine code). A Run binds to one Flow, snapshots it, and carries an active Phase plus per-Gate Receipts. Fil owns the Flow, durable Run state, Gate verification, and per-Phase configuration. The Agent Runtime owns the model, the agent loop, context management, and the execution environment. Adapters translate a Phase's configuration into the runtime's native enforcement points — instruction files, permission settings, hooks, MCP servers, skills. Enforcement is tiered (advisory config → hooks → sandbox), and the restrictions strategy is user-owned.
Quickstart
# 1. Install the Fil CLI from npm.
npm install -g @color-sunset/fil
# 2. Inside a project repo, scaffold the durable layout + built-in Flows.
fil init
# 3. Start a Run bound to a Change (feature, fix, or refactor).
fil start "add idempotency keys to the webhook ingestor"
# 4. Look at the current Phase, its Gate, and per-Phase config.
fil status
# 5. Do the work in your Agent Runtime (Claude Code, Pi, …).
# The Adapter enforces the Phase's instructions, tools, and context.
# 6. Advance: Fil runs the Gate and, on pass, transitions to the next Phase.
fil next
# 7. Visualize the Flow with the active Phase highlighted.
fil inspectfil init creates .fil/ with the default Flow (Requirements → Design →
Code → Review → Done) and the hotfix Flow (Triage → Patch → Done). Flow
files are committed; Runs (runs/, run.json) and proposals are gitignored.
Usage example
A minimal Phase definition, taken from the shipped default Flow. Fil supplies
all implementations — the file is XState machine JS code, matching the
canonical example at https://stately.ai/docs/xstate.
// .fil/flows/default.js
import { createMachine } from "@color-sunset/fil-engine";
export default createMachine({
id: "default",
initial: "design",
context: {},
states: {
design: {
meta: {
phase: {
instructions: "Design the approach. Sketch the data model and the key decisions. Reference ADRs.",
allowedTools: ["read", "write", "edit"],
skills: [],
context: { files: ["docs/adr/", "docs/OVERVIEW.md"], priorResults: [] },
actorMode: "collaborative",
gates: [
{
name: "approval",
type: "human",
prompt: "Approve the design and proceed to implementation?",
},
],
},
},
on: { NEXT: "code" },
},
code: {
meta: {
phase: {
instructions: "Implement the Change. Exit gate is the test suite.",
allowedTools: ["read", "write", "edit", "bash"],
skills: ["tdd"],
context: { files: ["src/"], priorResults: ["design"] },
actorMode: "agent",
gates: [{ name: "tests", type: "testsPass", command: "npm test" }],
},
},
on: { NEXT: "review" },
},
},
});A Run prints progress as Gates fire:
$ fil next
Advanced to: design
gate approval (human): pass
$ fil next
Advanced to: code
gate tests (testsPass): passCLI reference
Every public verb in packages/cli/src. Run fil with no arguments to print
this list.
| Verb | Description | Example |
|---|---|---|
| init | Scaffold the .fil/ layout and write the built-in Flows (idempotent). | fil init |
| start <change> [--flow <name>] | Start a Run bound to a Change; snapshots the chosen Flow. | fil start "rate-limit the login endpoint" --flow default |
| next | Run the current Phase's Gate; on pass, transition and persist the Receipt. | fil next |
| status | Print the current Phase, its Gate, actor mode, allowed tools, and instructions. | fil status |
| back | Retreat one Phase. Useful when a Gate surfaces design drift. | fil back |
| cancel | End the active Run as cancelled. | fil cancel |
| propose <flow> <file> | Write a proposed Flow edit to .fil/proposals/ as a unified diff (never auto-applied). | fil propose default ./flows/default.proposed.js |
| approve <id> [--flow <name>] | Load- and reachability-validate a proposal, then apply it to the Flow. | fil approve 01J... --flow default |
| inspect | View the Flow with the active Phase highlighted (opens @statelyai/inspect in the terminal). | fil inspect |
A successful Run is one where every Gate passed and the terminal Phase
(type: "final") was reached. Receipts are stored per Run and form the
audit trail — primitive #10 (verification & observability) made literal.
Documentation
| Doc | Purpose |
|---|---|
| CONTEXT.md | Glossary. Use these terms — not synonyms. |
| docs/OVERVIEW.md | Design synthesis, including the "what Fil owns vs delegates" table. |
| docs/adr/0001-steer-dont-run.md | Steer existing Agent Runtimes; don't run one. |
| docs/adr/0002-flows-are-xstate-code.md | Flows are engine-native code; reuse the chosen engine, don't reinvent. |
| docs/adr/0003-xstate-isolated-behind-flowengine-seam.md | XState is the default engine, behind a cross-language FlowEngine seam. |
Packages
fil is a pnpm workspace monorepo. The CLI (@color-sunset/fil-cli) is a thin wiring
layer; everything else is a focused module.
| Package | Responsibility |
|---|---|
| @color-sunset/fil-contract | The .fil/run.json schema + serializers/validators — the single source of truth every Adapter reads. |
| @color-sunset/fil-engine | The FlowEngine seam (ADR-0003) plus the default XState implementation (ADR-0002). Ships createMachine (the Flow author-facing wrapper) and the built-in Flows (default, hotfix). |
| @color-sunset/fil-flow-loader | Resolves Flow files across project/user precedence and load-validates the chosen config. |
| @color-sunset/fil-store | Repository over .fil/: Runs, the run.json projection, Flow snapshots, and proposals. |
| @color-sunset/fil-orchestrator | startRun / advance / back / cancel — drives the Flow via the engine and gate-runner, persists through the store. |
| @color-sunset/fil-gate-runner | runGate(gateSpec, ctx) → Receipt. Executes shell, test-suite, and human-confirmation gates and captures evidence. |
| @color-sunset/fil-evolution | Pure validation of proposed Flow patches (load + reachability) and unified-diff helpers. |
| @color-sunset/fil-inspect-view | View-only visualizer over FlowEngine.serialize() and the active Phase. Consumes only the seam. |
| @color-sunset/fil-cli | The fil command — a thin wiring over the modules above. |
Contributing
Fil is built around the ten primitives of harness engineering: instructions,
context delivery, context management, tool interface, execution environment,
durable state, orchestration, sub-agents, skills & procedures, verification &
observability. The split between what Fil owns and what it delegates to the
Agent Runtime is documented in docs/OVERVIEW.md. When
in doubt, read CONTEXT.md first — terminology is precise
(Gate ≠ check, Phase ≠ state, Receipt ≠ log) and assumes you mean what the
glossary means.
The full contributor guide lives in CONTRIBUTING.md —
it covers local setup, the Worktrunk worktree workflow, the Conventional
Commits convention, and the Changesets-driven release process. The TL;DR:
- Read
CONTEXT.mdand the relevant ADR indocs/adr/. - Pick an issue from the
Fil MVP project board.
Triage labels follow the canonical vocabulary in
docs/agents/triage-labels.md. Issues that are grabbable carryready-for-agentand haveStatus = Todo. - Work in a Worktrunk worktree (
wt switch -c feat/<short-name>). - Add a changeset with
pnpm changesetfor any user-facing change. - Open a PR; CI runs
pnpm lint && pnpm lint:md && pnpm build && pnpm typecheck && pnpm teston Ubuntu and macOS, Node 20 and 22.
Everyone in the project is expected to follow
CODE_OF_CONDUCT.md, and security issues are
reported privately per SECURITY.md. The repository is
MIT-licensed — see LICENSE. Adapters are distributed through
each Agent Runtime's native channel (Claude Code marketplace, Pi
extensions, …), not through npm.
Status
Fil is pre-1.0 and under active development. The MVP scope is tracked in #21 — PRD: Fil MVP. Issues and PRs are tagged on the Fil MVP project board.
Releases
The @color-sunset/fil meta-package and every @color-sunset/fil-* package
are published to npm under the MIT license. Versions are driven by
Changesets and follow
Semantic Versioning:
- Each PR that changes user-facing behavior adds a
.changeset/*.mdfile (pnpm changeset). Pickpatchfor fixes,minorfor backwards-compatible additions,majorfor breaking changes. Full guidance lives in.changeset/README.md. - Pushing to
mainopens (or updates) a Version Packages PR that bumps the affected packages and writes theirCHANGELOG.md. Internal-dependency coherence (updateInternalDependencies: "patch"in.changeset/config.json) means that bumping@color-sunset/fil-enginealso bumps every package that depends on it, so published versions stay in lockstep. - Merging that PR runs the
releaseworkflow (.github/workflows/release.yml), which publishes the bumped packages to npm with provenance (OIDC, noNPM_TOKENneeded).
Releases are tagged v<version> and listed on the
GitHub releases page. Pre-releases
(alpha, beta) are published under their own npm dist-tags so users can
opt in with npm install @color-sunset/fil@beta.
Note on the name. The npm package is
@color-sunset/fil(not@fil/filor unscopedfil) because:
- the unscoped
filname is already taken on npm by an unrelated static-site generator (ubenzer/fil);- the
@filscope on npm isn't owned by anyone, so we can't publish there.The
filcommand (the bin) is unchanged — users installnpm install -g @color-sunset/filand still runfil init,fil start,fil next, etc.
License
MIT — Copyright © 2026 Remy Fevry and contributors.
