Quick start¶
This guide runs Factoryal on your machine, then walks through the three basics: a workflow with an approval, an automation that starts from a catalog change, and your own workflow. It takes about 15 minutes. GitHub, Slack and AWS aren't needed for any of it.
- 1. Start the stack
- 2. Load the examples
- 3. Run a workflow and approve it
- 4. Start a run from a catalog change
- 5. Write your own workflow
- Running without Docker
- Next steps
Prerequisites¶
- Docker with Compose
- An Anthropic API key (
llmandagentsteps call Claude) - Optional:
uvto run thefactoryalCLI from your machine. Without it, run the CLI inside the API container as shown below.
1. Start the stack¶
Edit .env:
| Setting | Set it to |
|---|---|
ANTHROPIC_API_KEY |
your Anthropic key |
FACTORYAL_API_TOKEN |
any secret string. It's the admin token for the API and the web UI |
FACTORYAL_WORKER_TOKEN |
another secret string: the workers' own token. Workers don't start without it |
FACTORYAL_TOKEN_SECRET |
a random string of 32+ characters (openssl rand -hex 32) |
FACTORYAL_RUN_TOKEN_SECRET |
another random string of 32+ characters: it signs only the workers' run tokens |
.env.example's values for these are public: anyone can read them in the repo. That's fine on your
own machine. On a host others can reach, replace every one of them, and set FACTORYAL_INSTALL_ID
to anything but dev: the API then refuses to start with .env.example's value for any of these,
or without a worker token or run-token secret (Worker credentials).
Then start everything:
docker compose up -d --build
docker compose ps # postgres, temporal, api, dispatcher, worker-{workflows,light,llm,coding}
| URL | What |
|---|---|
| http://localhost:8000 | Factoryal landing page, web UI (/app/) and API (/v1/...) |
| http://localhost:8233 | Temporal web UI, for the underlying executions |
The API migrates the database on startup. curl localhost:8000/healthz answers once it's up.
2. Load the examples¶
Configuration (blueprints, agents, skills, workflows, ...) only comes from
factory repos: folders in git that the dispatcher applies whenever their branch
moves. The demo factory, examples/demo, holds a small catalog (a team, a
service, a repo), three agents and several workflows, split into three
projects (support, delivery and code review). .env.example already points
FACTORYAL_FACTORIES at it in this checkout, which Docker Compose mounts into the dispatcher:
If your checkout is on another branch, use that branch's name. Within a minute of starting, the dispatcher applies the demo:
# the CLI and the curl commands below read these
export FACTORYAL_API_URL=http://localhost:8000 FACTORYAL_API_TOKEN=<your token>
uv run factoryal factory status demo # or: docker compose exec api factoryal factory status demo
factory demo @ 4b077f859df5 /factoryal-src@main:examples/demo (37 resources)
projects: code-review, delivery, support
2026-10-26T18:02:11 poll apply 4b077f859df5 applied 37 create
In the web UI, Catalog now shows the team, service, repo and issue blueprints,
and Workflows lists release, agent-triage and the others, each with its project.
The examples include
linear-agent-fix, which opens pull requests and needs the GitHub App. Without it, those runs stop at their approval step or fail. They don't affect the rest of this guide.
3. Run a workflow and approve it¶
examples/demo/projects/delivery/workflows/release.yaml writes release notes
with Claude, waits for an approval, then calls a deploy webhook if you gave one.
From the web UI: Workflows → release → fill in service and version → start. The
run page shows the workflow graph live. When the run reaches approve, its status becomes
WAITING_APPROVAL and the notes appear in the approval message. The UI shows the approval
but doesn't decide it: approvals are made in the workflow's channels, so approve with the
API call below (in a real setup, from Slack or Jira).
From the API:
H=(-H "Authorization: Bearer $FACTORYAL_API_TOKEN" -H 'content-type: application/json')
# start a run; the response holds its id ("r_...")
curl -s "${H[@]}" localhost:8000/v1/workflows/release/runs \
-d '{"inputs": {"service": "billing", "version": "1.4.0", "changes": "fix rounding in invoices"}}'
# watch it: status, steps, cost (or: curl -s "${H[@]}" localhost:8000/v1/runs/<run-id>)
uv run factoryal runs show <run-id>
# approve the pending step
curl -s "${H[@]}" localhost:8000/v1/runs/<run-id>/approval \
-d '{"step": "approve", "approved": true, "by": "me"}'
The run finishes with status SUCCESS. Its output holds the notes and who approved them.
While it waited for approval, the run held no worker. The wait is a durable Temporal timer with a 72h expiry, so you could restart every container and the run would still be waiting afterwards.
4. Start a run from a catalog change¶
examples/demo/projects/support/workflows/agent-triage.yaml has an
entity_event trigger: whenever an issue entity is created, the triager agent reads it,
looks up the affected service in the catalog, and writes a severity back on the issue. The
agent's scopes (examples/demo/agents/triager.yaml) only allow it to write issues.
Create an issue:
curl -s "${H[@]}" localhost:8000/v1/blueprints/issue/entities -d '{
"identifier": "INC-1",
"properties": {"summary": "Invoices show the wrong total",
"description": "Since this morning totals are off by the tax amount."},
"relations": {"service": "billing"}
}'
Within a few seconds the dispatcher starts an agent-triage run (Runs in the UI).
When it finishes, the issue has severity, labels and triage_rationale set:
If the agent rates it high or critical, the run also asks for an approval to page the
owners. That approval auto-approves after 30 minutes.
5. Write your own workflow¶
Save this as examples/demo/workflows/summarize-service.yaml (the file name is the
workflow's identifier):
title: Summarize a service
inputs:
service: { type: string, required: true }
steps:
- id: facts
type: agent # an agent can search the catalog
agent: planner
prompt: Describe service {{ .inputs.service }}: owner, tier, repos and open issues.
- id: tldr
type: llm
effort: low
prompt: "Summarize in one sentence: {{ .steps.facts.output }}"
output:
summary: "{{ .steps.tldr.output }}"
Commit it. The dispatcher applies the commit within a minute (factoryal factory sync demo
does it now). Only committed changes apply:
uv run factoryal factory check examples/demo # validate before committing
git add examples/demo && git commit -m "Add summarize-service"
uv run factoryal factory sync demo
curl -s "${H[@]}" localhost:8000/v1/workflows/summarize-service/runs -d '{"inputs": {"service": "billing"}}'
Every commit that changes the workflow publishes a new version of it. Runs already started keep the version they began with. For your own factories, use their own repos on GitHub (see factory.md): connect them on the Factories page, and pull requests get the plan as a comment. Workflows covers every step type and trigger.
Running without Docker¶
You need Postgres 16 and a Temporal dev server (temporal server start-dev).
uv sync
cp .env.example .env # the defaults point at localhost:5432 and localhost:7233
uv run factoryal api & # control plane on :8000
uv run factoryal dispatcher & # automations, schedules, reconcile
uv run factoryal worker --queues workflows,light,llm &
Outside Docker, point FACTORYAL_FACTORIES at your checkout's path instead of /factoryal-src.
To serve the web UI, build it once: cd web && npm ci && npm run build.
Next steps¶
- Scale a worker pool:
docker compose up -d --scale worker-llm=4. Pools are stateless. - Connect GitHub so
codesteps open pull requests: GitHub App. - Start runs from Slack, Linear or Jira: Slack and Linear, Factories as code → Jira.
- Understand how it works: Architecture.
- Deploy it: Deploying to AWS.
Troubleshooting¶
| Symptom | Fix |
|---|---|
api restarts with FileNotFoundError: /secrets/github-app.pem |
Comment out FACTORYAL_GITHUB_* in .env until the GitHub App exists, or put the key at ./secrets/github-app.pem |
401 from the API |
The Authorization: Bearer token doesn't match FACTORYAL_API_TOKEN |
A run stays PENDING |
No worker polls its queue: check docker compose ps and docker compose logs worker-workflows |
An llm or agent step fails with an authentication error |
ANTHROPIC_API_KEY is missing in .env. Run docker compose up -d again after you set it |
An entity_event run never starts |
Check docker compose logs dispatcher, and the trigger's blueprint, events and condition |