Based on the fantastic work from StrongDM's Software Factory and the Attractor project.
StrongDM built a software factory — an automated development system where AI agents write code, run validation, and improve continuously without humans writing or reviewing code. Attractor is the core orchestration engine that makes this possible: a pipeline runner where you define workflows as directed graphs (using Graphviz DOT format) and Attractor executes them, dispatching work to LLMs, waiting on human gates, and routing conditionally between branches.
The name "Attractor" comes from dynamical systems theory — an attractor is a state or pattern that a system naturally converges toward over time. In the context of agentic pipelines, the idea is that well-defined goal-oriented graphs will pull the AI's execution toward a desired outcome, even across retries, branches, and failures. Rather than scripting every step imperatively, you describe the shape of the solution and let the system find its way there.
This repository is a personal implementation of that concept, built as a learning project to explore agentic workflow orchestration.
A DOT-based pipeline runner that orchestrates multi-stage AI workflows. You define pipelines as directed graphs in Graphviz DOT format, and Attractor executes each node by dispatching work to an LLM, waiting for human review, running parallel branches, or following conditional edges — all observable in real time through a built-in web dashboard.
- DOT pipelines — define workflows as
.dotdirected graphs; nodes are tasks, edges are transitions - LLM integration — nodes call Anthropic Claude, OpenAI GPT, Google Gemini, GitHub Copilot (CLI), or any OpenAI-compatible endpoint (Ollama, LM Studio, vLLM) based on configuration
- Conditional branching — edges carry
condition=attributes that route execution based on node outcomes - Parallel execution — fan-out / fan-in nodes run multiple branches concurrently
- Human gates —
type="wait.human"nodes pause execution for interactive approval/rejection - Retry & back-off — configurable per-node retry policy with exponential back-off
- Persist & resume — run state is stored in SQLite (default), MySQL, or PostgreSQL; configured via
ATTRACTOR_DB_*environment variables; crashed runs can be resumed from checkpoints - Web dashboard — real-time SSE-powered UI at
http://localhost:7070; supports multiple concurrent pipelines; upload.dotfiles via the browser - Documentation — full docs published at coreydaley.github.io/attractor; the Docs button in the nav bar opens the site in a new tab
- REST API v1 — 35-endpoint versioned REST API at
/api/v1/; seedocs/api/rest-v1.mdfor the full reference
- Java 21 (Gradle 8.7 is incompatible with Java 25+)
- git — required for project git-integration features
- Graphviz (
dot) — required for DOT-to-SVG graph rendering in the dashboard - Gradle 8.7 (wrapper included, or use the system Gradle)
- GNU Make (included on macOS and most Linux distros)
make install-runtime-deps # install Java 21, git, and graphviz (interactive, detects OS/package manager)
make build # compile and assemble
make run # start the web interface on port 7070| Target | Description |
|---|---|
make help |
List all available targets and options |
make build |
Compile and assemble the application |
make jar |
Build only the fat JAR (build/libs/attractor-server-*.jar) |
make run |
Run via Gradle — picks up source changes without rebuilding the JAR |
make run-jar |
Build the fat JAR (if needed) and run it directly (faster startup) |
make test |
Run the test suite |
make check |
Run tests and all static checks |
make clean |
Delete all build output |
make dist |
Build distribution archives (.tar and .zip) |
make cli-jar |
Build the CLI fat JAR (build/libs/attractor-cli-devel.jar) |
make release |
Build versioned server + CLI JARs for distribution |
make dev |
Dev mode: watch src/, rebuild and restart on change (requires entr) |
make docker-build-base |
Build the base image locally (attractor-base:local) from Dockerfile.base |
make docker-build |
Build the server image locally (attractor:local); builds base image first if not present |
make docker-run |
Run attractor:local; auto-loads .env if present |
make install-runtime-deps |
Interactively install Java 21, git, and graphviz using your OS package manager |
make install-dev-deps |
Interactively install Java 21, git, and entr using your OS package manager |
Pass these on the command line to override defaults:
| Option | Default | Description |
|---|---|---|
WEB_PORT=<n> |
7070 |
Web UI port |
JAVA_HOME=<path> |
/opt/homebrew/opt/openjdk@21/… |
Path to JDK 21 |
make run WEB_PORT=8080
make run-jar JAVA_HOME=/usr/lib/jvm/java-21-openjdkmake buildOr manually (Java 21 must be active):
export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home
./gradlew jarThe output jar is written to build/libs/attractor-server-*.jar.
make run # via Gradle (auto-reloads classpath changes)
make run-jar # via the pre-built fat JAR (faster startup)
make dev # dev mode: watch src/, rebuild + restart on change (requires entr)Or directly:
java -jar build/libs/attractor-server-1.0.0.jar [options]
| Flag | Description |
|---|---|
--projects-root <dir> |
Directory for logs and artifacts (default: logs/<name>-<timestamp>) |
--web-port <n> |
Web interface port (default: 7070) |
| Variable | Description |
|---|---|
ANTHROPIC_API_KEY |
API key for Anthropic Claude (Direct API mode) |
OPENAI_API_KEY |
API key for OpenAI GPT (Direct API mode) |
GEMINI_API_KEY or GOOGLE_API_KEY |
API key for Google Gemini (Direct API mode) |
ATTRACTOR_DEBUG |
Set to any value to enable debug output and stack traces |
ATTRACTOR_HOST |
Default server URL for the CLI (overridden by --host; e.g. http://attractor.example.com:7070) |
Attractor supports two execution modes, selectable in Settings → Execution Mode:
- Direct API — makes HTTP calls directly to provider APIs using environment variable keys
- CLI subprocess — shells out to installed CLI tools (
claude,codex,gemini,gh copilot)
| Provider | Toggle key | Credential |
|---|---|---|
| Anthropic Claude | provider_anthropic_enabled |
ANTHROPIC_API_KEY env var |
| OpenAI GPT | provider_openai_enabled |
OPENAI_API_KEY env var |
| Google Gemini | provider_gemini_enabled |
GEMINI_API_KEY or GOOGLE_API_KEY env var |
| Custom (OpenAI-compatible) | provider_custom_enabled |
Configurable API key (optional) |
The Custom provider works with any endpoint that implements the OpenAI /v1/chat/completions format — including Ollama, LM Studio, vLLM, and similar local or self-hosted inference servers. Configure it in Settings with a host URL, port, optional API key, and model name. The badge shows whether the endpoint is reachable rather than whether a key is set.
Ollama quick start:
ollama serve # starts on http://localhost:11434 by default
ollama pull llama3.2
# In Attractor Settings: enable Custom, set host=http://localhost, port=11434, model=llama3.2| Provider | Binary | Install |
|---|---|---|
| Anthropic Claude | claude |
Claude Code |
| OpenAI Codex | codex |
npm install -g @openai/codex |
| Google Gemini | gemini |
Gemini CLI |
| GitHub Copilot | gh copilot |
gh extension install github/gh-copilot |
CLI mode does not require environment variable API keys — authentication is handled by the installed tool. Command templates are configurable per provider in Settings and support {prompt} substitution.
The Settings page shows a Required and Optional tool grid. Missing required tools (java, git, dot) trigger a warning banner at the top of the page. Use make install-runtime-deps to install all three interactively.
Attractor stores pipeline run history in a database. By default it uses a local SQLite file (attractor.db). Set ATTRACTOR_DB_* environment variables to switch to MySQL or PostgreSQL.
The active backend is shown in the startup log:
[attractor] Database: SQLite (attractor.db)
[attractor] Database: PostgreSQL at pg.internal:5432/attractor
Set ATTRACTOR_DB_URL to a full JDBC URL. Simplified URLs without the jdbc: prefix are also accepted:
# PostgreSQL
export ATTRACTOR_DB_URL="jdbc:postgresql://localhost:5432/attractor?user=app&password=secret"
# also accepted:
export ATTRACTOR_DB_URL="postgres://app:secret@localhost:5432/attractor"
# MySQL
export ATTRACTOR_DB_URL="jdbc:mysql://localhost:3306/attractor?user=app&password=secret"
# also accepted:
export ATTRACTOR_DB_URL="mysql://app:secret@localhost:3306/attractor"Alternatively, set individual variables:
| Variable | Default | Description |
|---|---|---|
ATTRACTOR_DB_TYPE |
sqlite |
Backend: sqlite, mysql, or postgresql (or postgres) |
ATTRACTOR_DB_HOST |
localhost |
Database server hostname |
ATTRACTOR_DB_PORT |
3306 / 5432 |
Port (default depends on type) |
ATTRACTOR_DB_NAME |
attractor.db / attractor |
Database name or SQLite file path |
ATTRACTOR_DB_USER |
— | Database username |
ATTRACTOR_DB_PASSWORD |
— | Database password |
ATTRACTOR_DB_PARAMS |
— | Extra JDBC query params, e.g. sslmode=require |
# PostgreSQL via individual params
export ATTRACTOR_DB_TYPE=postgresql
export ATTRACTOR_DB_HOST=pg.internal
export ATTRACTOR_DB_NAME=attractor
export ATTRACTOR_DB_USER=app
export ATTRACTOR_DB_PASSWORD=secret
export ATTRACTOR_DB_PARAMS=sslmode=require
make runAttractor creates the database schema automatically on first start. A misconfigured ATTRACTOR_DB_TYPE causes a clear startup error and clean exit.
Once running, open http://localhost:7070 (or your chosen port) in a browser to start creating and executing pipelines. From the web interface you can describe a pipeline goal in natural language, review the generated DOT graph, and run it — all without touching the command line.
Click Docs in the navigation bar to open the built-in documentation window. It provides comprehensive, self-contained reference material organized into four tabs — Web App, REST API, CLI, and DOT Format — served directly by the Attractor server with no external dependencies.
Pipelines are written in Graphviz DOT. Attractor interprets node shapes and attributes to decide how each node is executed.
| Shape / Attribute | Behavior |
|---|---|
shape=Mdiamond |
Start node |
shape=Msquare |
Exit node |
shape=box (default) |
LLM prompt node — prompt= attribute is sent to the configured model |
shape=diamond |
Conditional gate — evaluates outgoing edge condition= attributes |
shape=hexagon or type="wait.human" |
Human review gate — pauses for interactive input |
| Parallel / fan-out nodes | Multiple outgoing edges from a single node run concurrently |
| Attribute | Description |
|---|---|
label |
Display label shown in the dashboard |
condition |
Boolean expression evaluated against the upstream node's outcome (e.g. outcome=success, outcome!=success) |
| Attribute | Description |
|---|---|
goal |
A natural-language goal string interpolated into prompts via $goal |
label |
Display name for the pipeline |
Linear pipeline (examples/simple.dot):
digraph Simple {
graph [goal="Run tests and report results", label="Simple Pipeline"]
start [shape=Mdiamond]
exit [shape=Msquare]
plan [label="Plan", prompt="Plan the implementation for: $goal"]
implement [label="Implement", prompt="Implement the plan for: $goal"]
test [label="Test", prompt="Run tests and verify the implementation"]
start -> plan -> implement -> test -> exit
}Conditional retry loop (examples/branching.dot):
digraph Branch {
graph [goal="Implement and validate a feature"]
start [shape=Mdiamond]
exit [shape=Msquare]
implement [label="Implement", prompt="Implement: $goal"]
validate [label="Validate", prompt="Run tests"]
gate [shape=diamond, label="Tests passing?"]
start -> implement -> validate -> gate
gate -> exit [label="Yes", condition="outcome=success"]
gate -> implement [label="No", condition="outcome!=success"]
}Human review gate (examples/human-review.dot):
digraph Review {
graph [goal="Review and ship a change"]
start [shape=Mdiamond]
exit [shape=Msquare]
generate [shape=box, prompt="Generate code changes for: $goal"]
review [shape=hexagon, label="Review Changes", type="wait.human"]
fix [shape=box, label="Fix Issues", prompt="Fix the issues found in review"]
start -> generate -> review
review -> exit [label="[A] Approve"]
review -> fix [label="[F] Fix Issues"]
fix -> review
}The server exposes a versioned REST API at /api/v1/ with 37 endpoints covering pipelines, artifacts, DOT operations, settings, models, and SSE event streams. All request and response bodies use JSON.
Selected endpoints:
| Method | Path | Description |
|---|---|---|
GET |
/ |
Dashboard UI |
GET |
/docs |
Built-in documentation window (four tabs) |
GET |
/api/v1/pipelines |
List all pipeline states |
POST |
/api/v1/pipelines |
Submit a new pipeline |
GET |
/api/v1/pipelines/{id} |
Get a single pipeline (includes dotSource) |
PATCH |
/api/v1/pipelines/{id} |
Update pipeline metadata |
DELETE |
/api/v1/pipelines/{id} |
Delete a pipeline |
POST |
/api/v1/pipelines/{id}/rerun |
Re-run a completed/failed pipeline |
POST |
/api/v1/pipelines/{id}/pause |
Pause a running pipeline |
POST |
/api/v1/pipelines/{id}/resume |
Resume a paused pipeline |
POST |
/api/v1/pipelines/{id}/cancel |
Cancel a running pipeline |
POST |
/api/v1/pipelines/{id}/archive |
Archive a pipeline |
POST |
/api/v1/pipelines/{id}/iterations |
Create a new pipeline version (iterate) |
GET |
/api/v1/pipelines/{id}/family |
List all versions in a pipeline family |
GET |
/api/v1/pipelines/{id}/artifacts |
List artifacts for a pipeline |
GET |
/api/v1/pipelines/{id}/artifacts.zip |
Download all artifacts as a ZIP |
GET |
/api/v1/pipelines/{id}/export |
Export pipeline + artifacts as a ZIP |
POST |
/api/v1/pipelines/import |
Import a previously exported pipeline ZIP |
GET |
/api/v1/pipelines/{id}/dot |
Download pipeline DOT source as a .dot file |
POST |
/api/v1/pipelines/dot |
Upload raw DOT file to create and run a pipeline |
POST |
/api/v1/dot/generate |
Generate a DOT pipeline from a text prompt |
POST |
/api/v1/dot/validate |
Validate a DOT pipeline |
GET |
/api/v1/settings |
List all settings |
PUT |
/api/v1/settings/{key} |
Update a setting |
GET |
/api/v1/models |
List available LLM models |
GET |
/events |
SSE stream of all pipeline state updates |
GET |
/events/{id} |
SSE stream for a single pipeline |
For the complete endpoint listing with request/response shapes and curl examples, see docs/api/rest-v1.md or open the built-in Docs window from the web UI.
The attractor CLI lets you manage pipelines, artifacts, DOT graphs, settings, and models from the command line.
make cli-jarThis produces build/libs/attractor-cli-devel.jar. For a versioned release artifact:
make release# Via the built JAR directly
java -jar build/libs/attractor-cli-devel.jar --help
# Via the bin/ wrapper (auto-locates the latest CLI JAR)
bin/attractor --helpAdd bin/ to your $PATH to use attractor as a bare command anywhere.
| Flag | Description |
|---|---|
--host <url> |
Server base URL. Overrides ATTRACTOR_HOST; defaults to http://localhost:8080 |
--output json |
Output raw JSON instead of formatted tables |
--help, -h |
Show usage information |
--version |
Print version and exit |
Set ATTRACTOR_HOST in your shell profile to avoid passing --host on every invocation:
export ATTRACTOR_HOST=http://attractor.example.com:7070attractor pipeline list
attractor pipeline get <id>
attractor pipeline create --file pipeline.dot [--name "My Pipeline"] [--simulate] [--no-auto-approve] [--prompt "text"]
attractor pipeline update <id> [--file pipeline.dot] [--prompt "text"]
attractor pipeline delete <id>
# Lifecycle
attractor pipeline rerun <id>
attractor pipeline pause <id>
attractor pipeline resume <id>
attractor pipeline cancel <id>
attractor pipeline archive <id>
attractor pipeline unarchive <id>
# Inspection
attractor pipeline stages <id>
attractor pipeline family <id>
# Polling
attractor pipeline watch <id> [--interval-ms 2000] [--timeout-ms 60000]
# Versioning
attractor pipeline iterate <id> --file updated.dot [--prompt "text"]attractor artifact list <pipeline-id>
attractor artifact get <pipeline-id> <node-id>
attractor artifact download-zip <pipeline-id> [--output out.zip]
attractor artifact stage-log <pipeline-id> <node-id> [--output node.log]
attractor artifact failure-report <pipeline-id> [--output report.json]
attractor artifact export <pipeline-id> [--output export.zip]
attractor artifact import --file export.zipattractor dot generate --prompt "Build and test a Go app" [--model <id>] [--output pipeline.dot]
attractor dot generate-stream --prompt "..." [--model <id>] # streaming output
attractor dot validate --file pipeline.dot
attractor dot render --file pipeline.dot [--output pipeline.png]
attractor dot fix --file pipeline.dot [--error "message"] [--model <id>] [--output fixed.dot]
attractor dot fix-stream --file pipeline.dot [--error "message"] [--model <id>]
attractor dot iterate --file pipeline.dot --prompt "Add a test stage" [--model <id>] [--output next.dot]
attractor dot iterate-stream --file pipeline.dot --prompt "..." [--model <id>]attractor settings list
attractor settings get <key>
attractor settings set <key> <value>attractor models listattractor events # stream all pipeline events
attractor events <pipeline-id> # stream events for one pipeline until terminal state# Create and watch a pipeline
attractor pipeline create --file my.dot --name "My Pipeline"
attractor pipeline watch <id>
# Generate a DOT file from a prompt, then run it
attractor dot generate --prompt "Deploy a container image" --output deploy.dot
attractor pipeline create --file deploy.dot
attractor pipeline watch <id>
# Export all artifacts from a completed pipeline
attractor artifact export <id> --output artifacts.zip
# Use JSON output for scripting
attractor pipeline list --output json | jq '.[0].id'For the full REST API reference, see docs/api/rest-v1.md.
src/main/kotlin/attractor/
├── Main.kt # CLI entrypoint
├── cli/ # CLI command implementations (Kotlin client for REST API v1)
├── condition/ # Edge condition evaluator
├── db/ # SQLite persistence (RunStore)
├── dot/ # DOT parser and graph model
├── engine/ # Execution loop, retry policy
├── events/ # Pipeline event types and event bus
├── handlers/ # Node handlers (LLM, human, parallel, conditional, …)
├── human/ # Human review gate logic
├── lint/ # Pipeline linting
├── llm/ # LLM provider clients (Anthropic, OpenAI, Gemini)
├── state/ # Run state model
├── style/ # Terminal/output style helpers
├── transform/ # Pipeline graph transformations
└── web/ # HTTP server, SSE, dashboard, REST API
├── WebMonitorServer.kt # HTTP server, dashboard SPA, /docs endpoint
├── RestApiRouter.kt # Versioned REST API (/api/v1/)
└── …
examples/ # Sample .dot pipelines
docs/
├── api/rest-v1.md # Full REST API reference (35 endpoints)
└── sprints/ # Sprint planning and history
make test # run the test suite
make check # run tests and all static checksOr directly:
export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home
./gradlew testLicensed under the Apache License, Version 2.0.
This entire codebase — including this README — was generated by AI.
Use it at your own risk. No guarantees are made about correctness, security, stability, or fitness for any purpose. This is a personal learning project and should not be used in production without thorough review and testing.