Attractor Framework

repository·main·Indexed 22 days ago

https://github.com/strongdm/attractor

A specification-driven framework for building custom software factories using coding agents. Attractor acts as an orchestration layer and pipeline runner that uses a constrained subset of Graphviz DOT syntax to define multi-stage AI workflows via directed graphs. It features Natural Language Specifications (NLSpecs), pluggable handlers, checkpoint and resume capabilities, human-in-the-loop gates, and a declarative DSL for managing LLM-based tasks and tool execution.

Tokens
39.9K
Snippets
54
Records
160
Agent score
78%

What's inside Attractor

  1. Overview of the Unified LLM Client Specification

    main

    The Unified LLM Client Specification defines a language-agnostic standard for building a client library that provides a single, consistent interface across multiple LLM providers (such as OpenAI, Anthropic, and Google Gemini).

    Its primary goal is to eliminate the fragmentation caused by differing HTTP APIs, message formats, tool calling conventions, and streaming protocols. By using this specification, developers can write provider-agnostic code and switch models by simply changing a model string identifier, without rewriting request construction or response parsing logic.

  2. What is Attractor

    main
    Attractor is a framework designed to help you build your own version of an Attractor to create a custom software factory. It is defined by a set of Natural Language Specifications (NLSpecs) that allow coding agents to implement or validate the system's behavior. While not strictly required, it is highly recommended to control your own agentic loop and unified LLM SDK to ensure a strong foundation for your software factory.
  3. What is Attractor and how does it work?

    main

    Attractor is a pipeline runner that uses directed graphs defined in Graphviz DOT syntax to orchestrate multi-stage AI workflows. Instead of writing imperative scripts, you define your workflow declaratively: nodes represent tasks (such as LLM calls, human reviews, or conditional branches) and edges represent the transitions between them.

    Key characteristics include:

    • Declarative Structure: You define the graph structure in a .dot file, and the execution engine handles the traversal.
    • Pluggable Handlers: Different node types are backed by handlers that implement a common interface, allowing for easy extensibility.
    • Checkpoint and Resume: The engine saves serializable checkpoints after every node, allowing workflows to resume from the last successful stage if interrupted.
    • Human-in-the-Loop: The pipeline can pause at specific nodes to await human decisions, which is used for approval gates or manual overrides.
    • Edge-based Routing: Transitions are controlled by conditions, labels, and weights defined on the edges of the graph.
  4. What is the Coding Agent Loop and how does it work?

    main

    The Coding Agent Loop is a language-agnostic specification for building an autonomous coding agent. Unlike a CLI, it is designed as a programmable library that a host application (like an IDE, Web UI, or CLI) controls.

    An agent takes a natural language instruction, plans a solution, and executes it by interleaving LLM calls with tool execution (reading files, editing code, running commands).

    Key Architectural Concepts:

    • Session: Manages conversation history, a steering queue for mid-task intervention, and event emission.
    • Provider Profiles: Ensures the agent uses tools and system prompts optimized for specific model families (e.g., OpenAI/codex, Anthropic/Claude Code, Gemini/gemini-cli).
    • Tool Registry: Handles tool dispatch, validation, and context truncation.
    • Execution Environment: An abstraction that allows tools to run anywhere (local, Docker, Kubernetes, WASM, or SSH) without changing the tool logic.
    • Event-Driven Interface: The loop emits typed events for every action (thinking, tool calls, output), allowing host applications to observe and react in real time.
    /* Conceptual Architecture Diagram */
    +--------------------------------------------------+
    |  Host Application (CLI, IDE, Web UI)              |
    +--------------------------------------------------+
            |                            ^
            | submit(input)              | events
            v                            |
    +--------------------------------------------------+
    |  Coding Agent Loop                                |
    |  +--------------------+  +---------------------+ |
    |  | Session            |  | Provider Profiles   | |
    |  |  - history         |  |  - OpenAI (codex)   | |
    |  |  - steering queue  |  |  - Anthropic (cc)   | |
    |  |  - event emitter   |  |  - Gemini (cli)     | |
    |  +--------------------+  +---------------------+ |
    |  +--------------------+  | Execution Env       | |
    |  | Tool Registry      |  |  - local (default)  | |
    |  |  - tool dispatch   |  |  - docker           | |
    |  |  - truncation      |  |  - k8s / wasm / ssh | |
    |  |  - validation      |  +---------------------+ |
    |  +--------------------+ |
    +--------------------------------------------------+
            |
            v
    +--------------------------------------------------+
    |  Unified LLM SDK (Client.complete / stream)       |
    +--------------------------------------------------+
            |
            v
    |  LLM Provider APIs                                |
    +--------------------------------------------------+
  5. Map Graphviz shapes to Handler Types

    main

    In Attractor, the shape attribute of a node determines its Handler Type (the logic used to execute the node). Use the following mapping to define node behavior via DOT shapes:

    ShapeHandler TypeDefault Behavior
    MdiamondstartNo-op entry point
    MsquareexitNo-op exit point (goal gate check in engine)
    boxcodergenLLM task (default for all nodes)
    hexagonwait.humanBlocks for human selection
    diamondconditionalPass-through; engine evaluates edge conditions
    componentparallelConcurrent branch execution
    tripleoctagonparallel.fan_inConsolidate parallel results
    parallelogramtoolExternal tool execution
    housestack.manager_loopSupervisor polling loop
  6. Monitor Session lifecycle and states

    main

    A session moves through several states during its lifecycle. You can monitor these transitions to understand the agent's current activity:

    • IDLE: Waiting for user input.
    • PROCESSING: Running the agentic loop (executing tool calls or generating responses).
    • AWAITING_INPUT: The model has asked the user a question and is waiting for a response.
    • CLOSED: The session has terminated due to completion, error, or explicit closure.

    Common Transitions:

    • IDLE $\rightarrow$ PROCESSING: Triggered by submit().
    • PROCESSING $\rightarrow$ AWAITING_INPUT: Triggered when the model asks an open-ended question.
    • PROCESSING $\rightarrow$ IDLE: Natural completion or reaching a turn limit.
    • AWAITING_INPUT $\rightarrow$ PROCESSING: Triggered when a user provides an answer.
  7. Configure Parallel and Fan-In execution patterns

    main

    Attractor supports concurrent execution branches and subsequent consolidation of results.

    Parallel Execution

    The ParallelHandler fans out execution to all outgoing edges. Each branch receives an isolated clone of the parent context.

    Join Policies (set via join_policy attribute):

    • wait_all: The handler waits for all branches to complete. Returns SUCCESS if all succeed, or PARTIAL_SUCCESS if some fail.
    • first_success: The handler returns SUCCESS as soon as one branch succeeds; other branches may be cancelled.

    Fan-In Consolidation

    The FanInHandler consolidates results from a preceding parallel node and selects the best candidate.

    Selection Logic:

    1. LLM-based: If the node has a prompt, the handler calls an LLM to rank the candidates.
    2. Heuristic-based: If no prompt is provided, it uses heuristic_select, which ranks candidates by:
      • Outcome status (Order: SUCCESS > PARTIAL_SUCCESS > RETRY > FAIL)
      • A score attribute (descending)
      • Node ID (as a tie-breaker)
  8. Manage Prompt Caching for Cost Optimization

    main

    Prompt caching reduces input token costs by reusing computation for unchanged conversation prefixes. The SDK maps provider-specific cache statistics to Usage.cache_read_tokens and Usage.cache_write_tokens.

    Provider Behaviors:

    • OpenAI: Automatic via the Responses API. No SDK action required.
    • Gemini: Automatic for repeated content; explicit caching via cachedContent API is available via provider_options.
    • Anthropic: Not automatic. Requires explicit cache_control annotations. The Anthropic adapter is responsible for injecting these breakpoints. Users can disable automatic placement via provider_options.anthropic.auto_cache = false.
  9. Understand the Attractor Pipeline Run Lifecycle

    main

    The execution of an Attractor pipeline follows a six-phase lifecycle. Understanding these phases helps in debugging where a pipeline might be failing (e.g., during parsing, validation, or execution).

    1. Parse: Reads the .dot source and creates an in-memory Graph model.
    2. Transform: Applies stylesheet, variable expansion, and custom AST transforms.
    3. Validate: Runs lint rules and rejects invalid graphs.
    4. Initialize: Creates the run directory, initial context, and checkpoint. Graph attributes are mirrored into the context.
    5. Execute: Traverses the graph from the start node, executing handlers and selecting edges.
    6. Finalize: Writes the final checkpoint, emits completion events, and cleans up resources.
  10. Enforce critical stages with Goal Gates

    main

    You can mark nodes as critical stages using the goal_gate=true attribute. When the pipeline reaches a terminal node (shape=Msquare), it checks all visited nodes marked with goal_gate=true.

    If any goal gate node did not result in a SUCCESS or PARTIAL_SUCCESS outcome, the pipeline will not exit. Instead, it attempts to jump to a retry target in this order:

    1. The node's retry_target.
    2. The node's fallback_retry_target.
    3. The graph-level retry_target.
    4. The graph-level fallback_retry_target.

    If no retry target is found at any level, the pipeline terminates with a FAIL outcome.

  11. Understand the Provider Alignment Principle

    main

    Attractor follows a Provider Alignment Principle: instead of forcing all LLMs into a single universal tool format, it uses the native tool interfaces that specific models were trained and optimized for.

    To achieve optimal results, a provider profile should be a 1:1 copy of the provider's reference agent (e.g., OpenAI's codex-rs, Anthropic's Claude Code, or Gemini's gemini-cli). This includes using the exact same system prompt and tool definitions byte-for-byte.

    Key takeaway: Do not attempt to unify tool interfaces across providers; instead, extend the provider's native harness with additional capabilities like subagents.