the-gang-invents-graph-engineering-readme.mp4
Factory is an intentionally unsafe trusted-environment demonstrator for three mechanisms:
- an append-only event wire,
- resource and task intake,
- 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}.
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.
- 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
ghand Git without placing a private key in Factory.
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 ./apiFactory 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 factoryThe 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.
/ 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.
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.
Build the agent-facing client:
go build -o factory ./cliThe 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.
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.
go test ./...
go test -race ./...
go vet ./...
bun install --cwd web --frozen-lockfile
bun run --cwd web typecheck
bun run --cwd web buildThe 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.