An AI agent factory that processes open GitHub issues through two connected pipelines:
- Planning Pipeline:
Triage -> Spec -> Plan -> Tasks - Execution Pipeline:
Implement -> Review -> Ship
The factory separates discovery and planning work from implementation work. The planning pipeline runs well as a deployed Cloudflare Worker service. The execution pipeline is local-first and designed to run with real access to git, npm, and gh — now with an optional Docker sandbox for isolated AI execution.
- What It Does
- Architecture Overview
- End-To-End Flow
- Planning Pipeline
- Execution Pipeline
- Sandboxed Execution (Docker)
- Repository Structure
- Requirements
- Installation
- Configuration
- Main Commands
- Testing Guide
- Deployment Guide
- GitHub Actions Integration
- Managed Labels & Markers
- Troubleshooting
- Detects open issues in configured GitHub repositories.
- Generates structured artifacts per stage.
- Writes comments and managed sections inside the issue.
- Adds labels to move the issue between states.
- Executes implementation in an isolated workspace per issue.
- Runs baseline checks on the target repository.
- Creates branch, push, and draft PR when execution reaches
Ship.
flowchart TB
subgraph Host["Host Machine"]
direction TB
Flue["Flue Agent Orchestrator"]
GH_CLI["gh CLI"]
Git["git"]
NPM["npm"]
end
subgraph Cloud["Cloudflare Workers"]
direction TB
Worker["Factory Worker<br/>(Planning Pipeline)"]
end
subgraph Docker["Docker Sandbox (Optional)"]
direction TB
Container["sandcastle:openrouter-agent<br/>Container"]
Agent["openrouter-agent CLI"]
OR_API["OpenRouter API"]
end
subgraph GitHub["GitHub"]
direction TB
Issues["Issues API"]
PRs["Pull Requests API"]
end
Issues --> Worker
Worker --> Issues
Worker --> Flue
Flue --> GH_CLI
Flue --> Git
Flue --> NPM
Flue -. "sandboxed mode" .-> Container
Container --> Agent
Agent --> OR_API
GH_CLI --> PRs
Git --> PRs
| Component | Role |
|---|---|
| Flue | Orchestrates agents and stages |
| OpenRouter | Provides the main LLM (planning + execution) |
| GitHub REST | Universal fallback for reading/writing issues |
| GitHub MCP | Used in planning when available |
| Cloudflare Workers | Hosts the HTTP entrypoint for planning |
| gh, git, npm | Used during local execution |
| Docker + Sandcastle | Optional isolated sandbox for execution |
flowchart LR
A["Open GitHub Issue"] --> B["Workflow: issue-opened.yml"]
B --> C["Factory on Cloudflare"]
C --> D["Planning Pipeline"]
D --> E["Comments + Checklist + Labels"]
E --> F["Label: factory:ready-for-implementation"]
F --> G["Workflow: issue-ready-for-implementation.yml"]
G --> H{"Execution Mode"}
H -->|local| I["Local Execution"]
H -->|sandboxed| J["Docker Sandbox Execution"]
I --> K["Branch + Changes + Verification"]
J --> K
K --> L["Draft PR"]
The planning pipeline processes an issue and publishes incremental artifacts:
triagespecplantasks
When tasks completes, the factory:
- Updates the issue with a managed
Factory Taskssection - Ensures the label
factory:ready-for-implementation - Leaves the issue ready for the execution pipeline
flowchart TD
A["Open Issue or Manual Payload"] --> B["Triage"]
B --> C["Spec"]
C --> D["Plan"]
D --> E["Tasks"]
E --> F["Write Comments by Marker"]
E --> G["Update Checklist in Issue Body"]
E --> H["Add Label: factory:ready-for-implementation"]
The execution pipeline expects an issue with generated tasks and the label factory:ready-for-implementation.
- Clones the target repo into
.factory-workspaces/. - Creates a branch with configurable prefix.
- Runs baseline project checks.
- Executes
implement. - Executes
review. - Executes
ship. - Pushes branch and opens draft PR.
flowchart TD
A["Issue with Tasks + Ready Label"] --> B["Prepare Isolated Workspace"]
B --> C["Install Target Repo Dependencies"]
C --> D["Implement"]
D --> E["Verification"]
E --> F["Review"]
F --> G["Ship"]
G --> H["Commit + Push + Draft PR"]
In sandboxed mode, the AI agent runs inside a Docker container while git/npm operations remain on the host.
flowchart TB
subgraph Host["Host Machine"]
direction TB
A["Factory Agent"]
B["git / npm / gh"]
C["Workspace<br/>.factory-workspaces/"]
end
subgraph Sandbox["Docker Sandbox"]
direction TB
D["sandcastle:openrouter-agent"]
E["openrouter-agent CLI"]
F["OpenRouter API"]
end
A -->|"1. clone / checkout"| B
B --> C
A -->|"2. prompt + context"| D
D --> E
E -->|"3. chat completion"| F
F -->|"4. tool calls"| E
E -->|"5. write files"| C
A -->|"6. verify / commit / push"| B
The sandboxed execution mode runs the AI coding agent inside an isolated Docker container. This provides:
- Isolation: The agent cannot accidentally modify files outside the workspace.
- Reproducibility: Same environment every time, regardless of host machine state.
- Security: Unknown or experimental code runs in a container, not on your host.
sequenceDiagram
participant Host as Host Factory
participant Docker as Docker Daemon
participant Container as sandcastle Container
participant OR as OpenRouter API
Host->>Docker: docker build sandcastle:openrouter-agent
Docker-->>Host: Image ready
Host->>Docker: docker run (detached)
Docker->>Container: Start container with tail -f /dev/null
Host->>Docker: docker exec openrouter-agent --model <model>
Docker->>Container: Execute agent CLI
Container->>OR: POST /chat/completions
OR-->>Container: Response with tool calls
Container->>Container: Execute Bash/ReadFile/WriteFile
Container-->>Host: Final JSON output
Host->>Docker: docker stop & remove
flowchart LR
subgraph HostOps["Host Operations"]
GitClone["git clone / checkout"]
NpmInstall["npm install"]
GitCommit["git commit / push"]
end
subgraph SandboxOps["Sandbox Operations (Docker)"]
AIAgent["AI Agent"]
FileEdit["File Read / Write"]
BashCmd["Bash Commands"]
end
GitClone --> Workspace["Workspace Directory<br/>(bind-mounted)"]
Workspace --> FileEdit
FileEdit --> Workspace
AIAgent --> BashCmd
BashCmd --> Workspace
Workspace --> GitCommit
| File | Description |
|---|---|
.flue/agents/factory.ts |
Main agent and stage orchestration |
.flue/lib/config.ts |
Configuration loader from factory.config.json, .env, and payload |
.flue/lib/execution.ts |
Local execution backend: workspaces, git, and verification |
.flue/lib/sandboxed-execution.ts |
NEW Sandboxed execution backend using Docker + Sandcastle |
.flue/lib/execution-common.ts |
Shared utilities for both execution backends |
.flue/lib/sandcastle/openrouter.ts |
NEW Custom Sandcastle AgentProvider for OpenRouter |
.flue/lib/github.ts |
REST integration with GitHub |
scripts/openrouter-agent.mjs |
NEW CLI agent that runs inside the Docker sandbox |
scripts/run-local-test.mjs |
Local runner for planning and execution |
scripts/run-local-http-test.mjs |
HTTP test against local worker |
scripts/run-sandboxed-test.mjs |
NEW Runner for sandboxed execution |
.sandcastle/Dockerfile |
NEW Docker image for the sandbox |
docs/local-testing.md |
Short guide for local testing |
- Node.js 22 (recommended)
npmgitghCLI (authenticated if testing full execution pipeline)- Docker (only if using sandboxed execution mode)
- OpenRouter account and API key
- GitHub token with permissions for
issues,contents, andpull-requests - Cloudflare access if deploying the worker
npm installcp .env.example .envEdit .env and provide at least:
GITHUB_TOKEN=ghp_xxx
OPENROUTER_API_KEY=sk-or-v1-xxxOptional but useful variables:
FACTORY_REPOS=owner/repo-one,owner/repo-two
FACTORY_MODEL=openrouter/openai/gpt-5.1
FACTORY_WRITE_BACK=true
FACTORY_MAX_ISSUES_PER_REPO=1
FACTORY_EXECUTION_MODE=local
FACTORY_EXECUTION_ROOT=.factory-workspacesThe factory supports two execution modes. Set FACTORY_EXECUTION_MODE in .env:
| Mode | Value | Description |
|---|---|---|
| Local | local |
AI agent runs directly on your host machine (default) |
| Sandboxed | sandboxed |
AI agent runs inside a Docker container |
You can also override per-command:
npm run local:execute -- --repo owner/repo --issue 123 --execution-mode sandboxednpm run local:validateThis checks:
.envexistsGITHUB_TOKENis presentOPENROUTER_API_KEYis present- Cloudflare build can be generated
- TypeScript has no errors
If you plan to use sandboxed execution, build the Docker image once:
npm run sandbox:build-imageOr let the factory auto-build it on first run.
{
"repos": [],
"maxIssuesPerRepo": 3,
"model": "openrouter/openai/gpt-5.1",
"writeBack": true,
"execution": {
"defaultMode": "local",
"sandboxed": false,
"branchPrefix": "factory",
"workspaceRoot": ".factory-workspaces",
"prDraft": true
}
}| Key | Description |
|---|---|
execution.defaultMode |
Default execution mode (local or sandboxed) |
execution.sandboxed |
Whether sandboxed mode is enabled in config |
execution.branchPrefix |
Prefix for generated branches |
execution.workspaceRoot |
Directory for isolated workspaces |
execution.prDraft |
Whether to create draft PRs |
# Type check and build
npm run check
# Development server (Cloudflare worker)
npm run dev:cloudflare
# Build for Cloudflare deployment
npm run build:cloudflare
# --- Planning Commands ---
npm run local:dry-run -- --repo owner/repo --issue 123 --stage full
npm run local:write-test -- --repo owner/repo --issue 123 --stage full
# --- Execution Commands ---
# Local execution
npm run local:execute -- --repo owner/repo --issue 123 --stage execute
# Sandboxed execution
npm run sandbox:execute -- --repo owner/repo --issue 123
# --- HTTP Testing ---
npm run local:http-test -- --repo owner/repo --issue 123 --stage fullConfirm the project compiles and minimum credentials exist:
npm run local:validate
npm run checkUse this to validate prompts, payloads, and repo access without modifying the issue:
npm run local:dry-run -- --repo owner/repo --issue 123 --stage full- Forces
writeBack=false - Runs
triage,spec,plan, andtasks - Safest way to test prompt or configuration changes
If you want to verify comments, markers, and labels:
npm run local:write-test -- --repo owner/repo --issue 123 --stage full- Uses
writeBack=true - The issue will be modified
Requires an issue with a Factory Tasks checklist and the label factory:ready-for-implementation.
npm run local:execute -- --repo owner/repo --issue 123 --stage executeDuring this test the factory:
- Clones the target repo into
.factory-workspaces/ - Creates a branch with format
factory/<owner-repo>/issue-<n> - Runs
npm install - Runs
npm test(with--runInBandfor Jest, without for Vitest) - Runs
npm run buildif available
npm run sandbox:execute -- --repo owner/repo --issue 123What happens:
- Builds Docker image
sandcastle:openrouter-agentif missing - Clones repo to
.factory-workspaces/ - Starts Docker container with bind-mounted workspace
- Runs
openrouter-agentinside container with OpenRouter API - Agent reads/writes files via bind-mount
- Host runs
npm testandgit commit/pushafter agent finishes
sequenceDiagram
participant User
participant Factory as Factory Agent
participant Docker as Docker
participant Container as Sandbox Container
participant OR as OpenRouter
participant GitHub
User->>Factory: npm run sandbox:execute
Factory->>Docker: Build image (if missing)
Docker-->>Factory: Image ready
Factory->>Factory: git clone / checkout
Factory->>Docker: Start container
Docker->>Container: Run tail -f /dev/null
Factory->>Docker: docker exec openrouter-agent
Docker->>Container: Execute agent
Container->>OR: API call with prompt
OR-->>Container: Tool calls requested
Container->>Container: Read/Write files
Container->>OR: Results
OR-->>Container: Final response
Container-->>Factory: JSON output
Factory->>Factory: npm test
Factory->>GitHub: git push + draft PR
Start the server:
npm run dev:cloudflareIn another terminal:
npm run local:http-test -- --repo owner/repo --issue 123 --stage fullDefault URL: http://localhost:3583
| What Changed | Test Command |
|---|---|
| Prompts or roles | npm run local:dry-run |
| Markers, labels, or writing | npm run local:write-test |
| Cloning, git, verification, or PR flow | npm run local:execute |
| HTTP entrypoint or Cloudflare wiring | npm run dev:cloudflare + npm run local:http-test |
| Sandbox configuration | npm run sandbox:execute |
This repository builds a Cloudflare artifact using Flue. The build generates dist/ and sets up Wrangler redirection in .wrangler/deploy/config.json, so deployment can be launched from the repository root after building.
Before deploying, ensure you have:
wrangleravailable- Logged into Cloudflare with
npx wrangler loginor equivalent environment variables - Required secrets and variables for the worker
npm run build:cloudflareThis produces:
dist/_entry.tsdist/wrangler.jsoncdist/manifest.json
npx wrangler deployThe workflow .github/workflows/issue-opened.yml needs this secret:
FACTORY_BASE_URL
It must point to the public URL of the deployed worker. That workflow makes a POST to:
${FACTORY_BASE_URL}/agents/factory/issue-<issue-number>
The workflow .github/workflows/issue-ready-for-implementation.yml requires:
OPENROUTER_API_KEYGITHUB_TOKEN
This job:
- Checks out the repository
- Installs dependencies with
npm ci - Writes
.envat runtime - Executes
npm run local:execute
Triggered when:
- An issue is opened
- Manually via
workflow_dispatch
Responsibility:
- Calls the deployed factory
- Initiates the planning pipeline with
writeBack=trueandstage=full
Triggered when:
- An issue receives the label
factory:ready-for-implementation - Manually via
workflow_dispatch
Responsibility:
- Executes the
Implement -> Review -> Shippipeline - Pushes changes and opens draft PR
| Label | Stage |
|---|---|
factory:triaged |
Triage complete |
factory:specified |
Spec complete |
factory:planned |
Plan complete |
factory:tasked |
Tasks complete |
factory:ready-for-implementation |
Ready for execution |
factory:implementing |
Implementation in progress |
factory:reviewed-code |
Code review complete |
factory:pr-opened |
Draft PR opened |
factory:execution-failed |
Execution failed |
<!-- rob-agent-factory:triage -->
<!-- rob-agent-factory:spec -->
<!-- rob-agent-factory:plan -->
<!-- rob-agent-factory:tasks -->
<!-- rob-agent-factory:implement -->
<!-- rob-agent-factory:review -->
<!-- rob-agent-factory:ship -->
<!-- rob-agent-factory:failure -->
<!-- rob-agent-factory:tasks-body:start -->
<!-- rob-agent-factory:tasks-body:end -->cp .env.example .envnpm run local:validate- Confirm
npm run dev:cloudflareis running - Confirm the expected port is
3583 - Re-run
npm run local:http-test -- --repo owner/repo --issue 123
- Verify
ghworks with the current token - Verify permissions:
contents,pull-requests, andissues - Confirm the target repo allows push from the runner or local environment
This usually comes from the cloned project inside .factory-workspaces/, not this repo. Check there:
npm installnpm testnpm run build
Ensure the Docker image is built correctly:
npm run sandbox:build-imageThe container runs with your host UID. Ensure the image has a writable home:
RUN mkdir -p /home/agent && chmod 777 /home/agentThe factory auto-builds the image on first run. To manually build:
npm run sandbox:build-imageIf you see 429 errors, the model is rate-limited upstream. Options:
- Wait a few minutes and retry
- Use a different model in
.env:
FACTORY_MODEL=openrouter/openai/gpt-4o