Skip to content

bromanko/attractor

Repository files navigation

Attractor

A KDL-based workflow runner that orchestrates multi-stage AI workflows. Each stage is an AI/tool/human task and transitions define execution flow.

Built from the StrongDM Software Factory NLSpecs:

Architecture

┌─────────────────────────────────────────┐
│  CLI                                    │  Standalone CLI entry point
│  (src/cli.ts)                           │  (run, validate, show)
├─────────────────────────────────────────┤
│  Pi Extension                           │  /attractor slash command,
│  (src/extensions/)                      │  TUI panel, interactive interviewer
├─────────────────────────────────────────┤
│  PiBackend                              │  CodergenBackend powered by pi SDK
│  (src/pi-backend.ts)                    │  (AgentSession, tool modes, usage)
├─────────────────────────────────────────┤
│  Pipeline Engine                        │  KDL parser, execution engine,
│  (src/pipeline/)                        │  handlers, conditions, validators
└─────────────────────────────────────────┘

Quick Start

Define a workflow in KDL syntax

workflow "FeaturePipeline" {
  version 2
  goal "Implement and validate a feature"
  start "plan"

  stage "plan" kind="llm" prompt="Plan the implementation for: $goal"
  stage "implement" kind="llm" prompt="Implement the plan" goal_gate=true
  stage "validate" kind="llm" prompt="Run tests and verify correctness"

  stage "gate" kind="decision" {
    route when="outcome(\"validate\") == \"success\"" to="exit"
    route when="true" to="implement"
  }

  stage "exit" kind="exit"

  transition from="plan" to="implement"
  transition from="implement" to="validate"
  transition from="validate" to="gate"
}

Run the pipeline

import { parseWorkflowKdl, workflowToGraph, validateWorkflowOrRaise, runPipeline } from "attractor";

const kdl = fs.readFileSync("pipeline.awf.kdl", "utf-8");
const workflow = parseWorkflowKdl(kdl);
validateWorkflowOrRaise(workflow);
const graph = workflowToGraph(workflow);

const result = await runPipeline({
  graph,
  logsRoot: "./logs",
  backend: myCodergenBackend,  // Your LLM integration
  onEvent: (event) => console.log(event.kind, event.data),
});

Pipeline Engine Features

Stage Kinds

Kind Handler Description
llm codergen LLM task with $goal variable expansion
exit exit Pipeline exit point (no-op, goal gate check)
human wait.human Human-in-the-loop gate
decision conditional Routing based on edge conditions
tool tool External tool execution
workspace.create workspace.create Create an isolated jj workspace
workspace.merge workspace.merge Merge a workspace back
workspace.cleanup workspace.cleanup Clean up a workspace

Edge Selection

5-step deterministic priority: condition match → preferred label → suggested IDs → weight → lexical tiebreak.

Condition Expressions

stage "gate" kind="decision" {
  route when="outcome(\"validate\") == \"success\"" to="deploy"
  route when="outcome(\"validate\") == \"fail\"" to="fix"
  route when="outcome(\"validate\") == \"success\" && context(\"tests_passed\") == \"true\"" to="deploy"
}

Goal Gates

Stages with goal_gate=true must succeed before the pipeline can exit.

Model Profiles

KDL workflows configure per-stage models via named profiles:

workflow "example" {
  version 2
  start "plan"

  models {
    default "fast"
    profile "fast" model="claude-sonnet-4-5"
    profile "heavy" model="claude-opus-4-6" reasoning_effort="high"
  }

  stage "plan" kind="llm" prompt="Plan it" model_profile="fast"
  stage "implement" kind="llm" prompt="Build it" model_profile="heavy"
  stage "done" kind="exit"

  transition from="plan" to="implement"
  transition from="implement" to="done"
}

Checkpoint & Resume

Execution state saved after each node. Resume from any checkpoint.

Human-in-the-Loop

Built-in interviewers: AutoApproveInterviewer, QueueInterviewer, CallbackInterviewer, RecordingInterviewer.

Project Structure

src/
├── cli.ts                  Standalone CLI entry point
├── cli-renderer.ts         CLI output rendering
├── pi-backend.ts           CodergenBackend powered by pi SDK
├── interactive-interviewer.ts  Interactive human gate prompts
├── index.ts                Public API re-exports
├── pipeline/               Pipeline Engine
│   ├── types.ts            Graph model, context, handlers, interviewers
│   ├── workflow-types.ts   KDL workflow definition types
│   ├── workflow-kdl-parser.ts  KDL workflow parser
│   ├── workflow-loader.ts  Workflow-to-graph conversion
│   ├── workflow-resolution.ts  Shared workflow discovery & resolution
│   ├── workflow-validator.ts   Workflow-level validation
│   ├── workflow-expr.ts    Workflow expression evaluator
│   ├── validator.ts        Graph-level lint rules
│   ├── conditions.ts       Edge condition expression language
│   ├── engine.ts           Core execution loop, edge selection, checkpoints
│   ├── handlers.ts         Node handlers + registry
│   ├── interviewers.ts     Human-in-the-loop implementations
│   ├── workspace.ts        Jujutsu workspace handlers
│   ├── tool-failure.ts     Structured tool failure details
│   ├── status-markers.ts   Stage status file utilities
│   └── graph-to-dot.ts     Graph → DOT export
└── extensions/             Pi extension integration
    ├── attractor.ts        Main extension entry point
    ├── attractor-command.ts   Pipeline CLI commands
    ├── attractor-interviewer.ts  Interactive interviewer
    └── attractor-panel.ts  TUI panel rendering

CLI

Attractor ships a standalone CLI for running, validating, and visualizing workflows.

npm install -g attractor   # or use npx attractor

Commands

# Run a pipeline (path or bare name)
attractor run pipeline.awf.kdl [options]
attractor run deploy              # resolves to .attractor/workflows/deploy.awf.kdl

# Validate a workflow graph
attractor validate pipeline.awf.kdl

# Visualize a workflow graph (ASCII/boxart via graph-easy, or raw DOT)
attractor show pipeline.awf.kdl [--format ascii|boxart|dot]

# List available LLM models
attractor list-models [--provider anthropic]

Run Options

Flag Description
--goal <text> Override the graph's goal attribute
--model <model> LLM model to use (default: claude-opus-4-6)
--provider <name> Provider name (default: anthropic)
--logs <dir> Logs directory (default: .attractor/logs)
--system <prompt> System prompt for codergen stages
--tools <mode> Tool mode: none, read-only, coding (default: coding)
--approve-all Auto-approve all human gates (no interactive prompts)
--resume [checkpoint] Resume from checkpoint (default: <logs>/checkpoint.json)
--dry-run Validate and print graph without executing
--verbose Show detailed event output

Authentication

The CLI uses pi's AuthStorage for credentials. Either:

  • Set ANTHROPIC_API_KEY in your environment, or
  • Run pi /login to authenticate with a Claude subscription

Example

attractor run feature.awf.kdl --goal "Add user authentication" --tools coding --verbose

The CLI renders a live spinner per stage, shows per-stage model overrides, and prints a usage/cost summary at completion.

Pi Extension

Attractor integrates with the pi coding agent as a /attractor slash command, providing an interactive TUI experience for pipeline execution.

Setup

Place the extension entry point in your project:

.pi/extensions/attractor.ts
// Re-exports the built attractor extension for pi auto-discovery
export { default } from "../../dist/extensions/attractor.js";

Then reload pi (/reload) to pick up the extension.

Commands

/attractor run <workflow> --goal "..." [options]
/attractor validate <workflow>
/attractor show <workflow> [--format ascii|boxart|dot]

Workflow Resolution

Both the CLI and extension use a shared resolution module. Workflow references are resolved in order:

  1. Direct file path — absolute or relative to cwd
  2. Bare name — resolved by filename stem from known Attractor locations:
    • <cwd>/.attractor/workflows/<name>.awf.kdl (project-local)
    • <repo-root>/.attractor/workflows/<name>.awf.kdl (repo-level, when cwd is a subdir)
    • ~/.attractor/workflows/<name>.awf.kdl (global user-level)

Project-local workflows take precedence over repo-root, which takes precedence over global. When duplicate filenames exist across locations, the higher-precedence copy wins and a warning is emitted.

So /attractor run deploy or attractor run deploy will search these locations for deploy.awf.kdl.

Run Options

Flag Description
--goal <text> Pipeline goal (required unless the graph has one)
--resume Resume from last checkpoint
--approve-all Auto-approve all human gates
--logs <dir> Logs directory (default: .attractor/logs)
--tools <mode> Tool mode: none, read-only, coding
--dry-run Validate and print graph without executing

Features

  • Interactive interviewer — human gate prompts appear inline in the pi TUI
  • Live panel — stage progress, agent tool calls, and text streaming rendered via custom message types
  • Stage result rendering — success/failure cards with elapsed time and error details in the conversation area
  • Tab completion — subcommand suggestions when typing /attractor

Development

This repo is managed with Nix flakes. Enter the dev shell first:

nix develop        # or use direnv (auto-loads .envrc)

Then:

npm install
npm run build
npm run lint       # type-check
npm test

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors