Skip to content

Repository files navigation

Factory

the-gang-invents-graph-engineering-readme.mp4

Factory is an intentionally unsafe trusted-environment demonstrator for three mechanisms:

  1. an append-only event wire,
  2. resource and task intake,
  3. one capacity-limited workflow coordinator.

Projects, tasks, comments, artifacts, media metadata, triggers, and workflow metadata are transactional projections of one event table in Postgres. The Solid web app and factory CLI use the same HTTP API. No authentication, permissions, policy engine, migration framework, or deployment engine lives in the application. Factory records the process and workflow-admission deployment boundaries it can observe. Nags still owns builds, process replacement, health verification, rollback, and deployment receipts.

Immutable media bytes and validated workflow source live in S3. Task descriptions and task comments refer to them through /api/media/{id}.

Monorepo

api/    Go API, event wire, projections, workflow adapter, run coordinator
cli/    Go resource client
web/    SolidJS application built with Bun and Vite

The root holds repository orchestration, module metadata, and this document.

Documentation

  • Usage covers installation, first run, everyday operation, configuration, and troubleshooting.
  • Concepts explains the event wire, projections, task intake, and the workflow coordinator.
  • Resource reference lists resource fields, HTTP routes, payloads, and matching CLI commands.
  • Workflow reference covers discovery, agent collaboration, workflow files, triggers, and cron behavior.
  • GitHub App access covers short-lived, app-identity authentication for gh and Git without placing a private key in Factory.

Run locally

Build the web bundle into the API's ignored embed staging directory:

bun install --cwd web --frozen-lockfile
bun run --cwd web typecheck
bun run --cwd web build
rm -rf api/dist
mkdir -p api/dist
touch api/dist/.keep
cp -R web/dist/. api/dist/

Then run the API:

go build -o factory ./cli
export DATABASE_URL='postgres://...'
export S3_BUCKET='...'
export S3_PREFIX='factory/local'
export S3_REGION='us-west-2'
export FACTORY_CREDENTIALS_KEY='<stable base64-encoded 32-byte key>'
go run ./api

Factory listens on 127.0.0.1:8092 by default.

For a container build with the frozen web bundle, both Go binaries, the workflow runner, Codex, Claude Code, GitHub CLI, Cloudflare Tunnel, and Git installed:

docker build -t factory .
docker run --rm -p 8092:8092 \
  -e DATABASE_URL -e S3_BUCKET -e S3_PREFIX -e S3_REGION \
  -e FACTORY_CREDENTIALS_KEY factory

The image listens on 0.0.0.0:8092. Postgres stores the event wire, projections, and encrypted harness credentials. S3 stores media and validated Factory-authored workflow source. The container filesystem is scratch space for checked-out projects, workflow execution, and agent caches.

On a container platform, run the immutable image with Postgres and S3-compatible object storage, add FACTORY_CREDENTIALS_KEY as a secret, expose port 8092, use /api/health as the health path, and allow egress for agent and workflow network calls. Supply DATABASE_URL, S3_BUCKET, S3_PREFIX, and S3_REGION through the platform's normal secret and configuration system.

The image can transparently authenticate GitHub CLI and HTTPS Git as a GitHub App through the separate github-token-broker image target. The broker alone holds the app private key; Factory receives short-lived installation tokens. See GitHub App access for portable Docker and hosted platform configuration.

Set the optional TUNNEL_TOKEN secret to run a remotely managed Cloudflare Tunnel beside Factory. Configure its public hostname to send only POST /api/ingest/external to http://127.0.0.1:8092, with a 404 fallback. Internal producers use /api/ingest/internal through the service's private address and never traverse Cloudflare.

Usage: factory-api [options]

  -addr string
        HTTP listen address
  -claude string
        Claude Code executable
  -codex string
        Codex executable
  -factory string
        Factory CLI exposed to the authoring harness
  -workflow string
        workflow CLI executable
  -workflow-workspace string
        untracked dynamic workflow workspace

DATABASE_URL, FACTORY_CREDENTIALS_KEY, S3_BUCKET, S3_PREFIX, and S3_REGION are required. Keep FACTORY_CREDENTIALS_KEY stable: changing it after saving API keys prevents Factory from decrypting them.

Web routes

/                                      overview
/projects                              project list
/projects/new                          create project
/projects/:project                     view and edit project
/tasks                                 filterable, sortable, and groupable task list
/tasks/new                             create task
/tasks/:task                           view and edit task, comments, artifacts
/tasks/:task/comments/:comment         directly address a comment
/events                                filterable live event wire
/events/:event                         directly address an event
/triggers                              filterable trigger list with enabled state
/triggers/new                          create trigger
/triggers/:trigger                     view and edit trigger
/workflows                             discovered workflow list
/workflows/new                         create through agent chat
/workflows/:workflow                   chat beside the live workflow source
/history                               running, waiting, failed, and completed run overview
/history/running                       running runs, loaded 25 at a time
/history/waiting                       waiting runs, loaded 25 at a time
/history/failed                        failed runs, loaded 25 at a time
/history/completed                     completed runs, loaded 25 at a time
/history/:item                         phase-grouped semantic event timeline
/settings                              select agent defaults, API credentials, run capacity, and canned reactions

Resource and detail route IDs are integers. The four history status routes use the canonical persisted status names. Deletion is soft and list routes omit deleted records.

Resource API

The API exposes resource routes under /api; media creation uses multipart form data and the other mutations use JSON:

projects     GET / POST, GET / PUT / DELETE by ID
tasks        GET / POST, GET / PUT / DELETE by ID
comments     POST under a task or workflow, GET / PUT / DELETE by ID
artifacts    GET / POST, GET / PUT / DELETE by ID
media        POST one multipart file, GET immutable bytes by ID
events       GET / POST, GET by ID, GET types, SSE stream
triggers     GET / POST, GET / PUT / DELETE by ID
workflows    GET / POST, GET / PUT / DELETE by ID
history      GET list, GET run and event detail by ID
settings     GET / PUT singleton selection and option catalog
credentials  GET status, PUT replacement OpenAI or Anthropic API keys
ingress      ANY request at /api/ingest or a path beneath it

POST /api/events accepts any event:

{
  "type": "release.ready",
  "data": {
    "version": "1.0"
  }
}

Every accepted event type appears in the trigger event selector. cron is always included. Disabled triggers remain visible through the list and detail routes. Trigger PUT bodies must include their complete definition and an explicit enabled boolean.

/api/ingest?source=<name> accepts any HTTP payload and records it as ingress.<name> without a provider adapter. The event preserves the method, URL, headers, and lossless UTF-8 or base64 body. Paths below /api/ingest support configurable OTLP/HTTP signal endpoints. Request bodies are limited to 32 MiB.

CLI

Build the agent-facing client:

go build -o factory ./cli

The client prints JSON and accepts inline JSON or @file bodies:

factory [--url URL] <resource> <action> [id] [json|@file]

Examples:

factory project create '{"name":"Factory","path":"/path/to/factory"}'
factory task create '{"title":"Review the PR","status":"todo","projectId":1}'
factory task comment 12 '{"content":"The build passed."}'
factory media create ./screen.png
factory artifact get 18
factory workflow create '{"message":"Build a review-panel workflow."}'
factory workflow update 24 '{"message":"Add a security reviewer."}'
factory event create '{"type":"release.ready","data":{"version":"1.0"}}'
factory trigger update 41 '{"eventType":"release.ready","workflowId":24,"enabled":false}'
factory history get 30
factory settings update '{"harness":"claude","model":"sonnet","reasoning":"high","workflowCapacity":6,"reactionEmojis":["👍","🎉","🤔"]}'

FACTORY_URL changes the default server from http://127.0.0.1:8092.

Workflow coordinator

Factory asks the external workflow CLI to discover and execute dynamic workflows. It does not embed that CLI's loader, DSL, or agent runtime.

Factory-created workflow files live outside git at:

~/.local/share/factory/workflow-workspace/.claude/workflows/

That directory is a local cache. Factory restores it from S3 at startup and persists each successfully validated authored workflow back to S3.

Creating or updating a workflow appends a user chat comment. The coordinator sends that conversation to the selected unrestricted harness. Factory appends each exposed reasoning, tool, output, agent, error, or unknown semantic step as an ordered live comment while the harness writes the workflow file. After validation and rediscovery, Factory appends one final reply. The authoring harness runs from the workflow workspace and can use $FACTORY_CLI against $FACTORY_URL to inspect resources or create a trigger when asked. Trigger execution uses the same selected harness, model, and reasoning:

workflow --cwd <task.project.path-or-workspace> run <workflow-source-path> \
  --backend <codex-or-claude> \
  --model <selected-model> \
  --allow-mutating \
  --no-validate \
  --<harness>-yolo \
  --args <source-event-and-trigger>

Enabled event triggers match events received after the trigger's latest update. Disabled triggers retain their definitions without admitting work; events and cron ticks missed while disabled are not replayed after re-enable. Cron resumes at the first schedule after that update. Cron triggers append a targeted cron event and follow the same execution path. Disabling does not cancel a run that already started. Task events resolve their required project and run from its configured local path, so workflow agents operate in the task's repository without copying or linking the workflow source into it. Workflow runs stream every ordered semantic journal event onto the durable wire for the live and historical views. A task-triggered human gate posts its prompt as a task comment, leaves the run waiting without a live process, and resumes that same journal from the next root comment or direct reply. Workflow conversations remain sequential. Triggered workflows run in parallel up to the configured capacity, which defaults to six and can be set from zero through ten in /settings. The same page configures the ordered canned reactions used by every task and task-comment control.

Factory records deployment.started, deployment.quiescing, deployment.quiesced, and deployment.resumed on the generic event wire. These facts correlate the current health release identity with process startup, workflow drain, and admission resumption. They do not claim that Factory built, verified, or deployed its own release.

Verify

go test ./...
go test -race ./...
go vet ./...
bun install --cwd web --frozen-lockfile
bun run --cwd web typecheck
bun run --cwd web build

The local deployment manifest builds both Go binaries and embeds the frozen Solid bundle in factory-api. Nags signs both binaries and runs them from stable paths under ~/.local/share/factory/bin, so macOS keeps one privacy identity across deployments.

About

An event-driven task and workflow coordinator

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages