Skip to content

coreydaley/attractor

Attractor

CI Docs License

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.

Features

  • DOT pipelines — define workflows as .dot directed 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 gatestype="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 .dot files 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/; see docs/api/rest-v1.md for the full reference

Requirements

  • 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)

Quick Start

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

Make Targets

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

Make Options

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-openjdk

Build

make build

Or manually (Java 21 must be active):

export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home
./gradlew jar

The output jar is written to build/libs/attractor-server-*.jar.

Run

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]

Options

Flag Description
--projects-root <dir> Directory for logs and artifacts (default: logs/<name>-<timestamp>)
--web-port <n> Web interface port (default: 7070)

Environment Variables

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)

LLM Providers

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)

Direct API providers

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

CLI subprocess providers

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.

System tool warnings

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.

Database Configuration

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

Connection String

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"

Individual Parameters

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 run

Attractor 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.

Pipeline Format

Pipelines are written in Graphviz DOT. Attractor interprets node shapes and attributes to decide how each node is executed.

Node types

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

Edge attributes

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)

Graph attributes

Attribute Description
goal A natural-language goal string interpolated into prompts via $goal
label Display name for the pipeline

Examples

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
}

Web API

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.

CLI

The attractor CLI lets you manage pipelines, artifacts, DOT graphs, settings, and models from the command line.

Build

make cli-jar

This produces build/libs/attractor-cli-devel.jar. For a versioned release artifact:

make release

Run

# 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 --help

Add bin/ to your $PATH to use attractor as a bare command anywhere.

Global Flags

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:7070

Commands

Pipeline management

attractor 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"]

Artifacts

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.zip

DOT graph tools

attractor 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>]

Settings

attractor settings list
attractor settings get <key>
attractor settings set <key> <value>

Models

attractor models list

Events (SSE stream)

attractor events              # stream all pipeline events
attractor events <pipeline-id> # stream events for one pipeline until terminal state

Examples

# 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.

Project Structure

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

Running Tests

make test     # run the test suite
make check    # run tests and all static checks

Or directly:

export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home
./gradlew test

License

Licensed under the Apache License, Version 2.0.


Disclaimer

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.

Sponsor this project

 

Packages

 
 
 

Contributors