Skip to content

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.

Prerequisites

  • Docker with Compose
  • An Anthropic API key (llm and agent steps call Claude)
  • Optional: uv to run the factoryal CLI from your machine. Without it, run the CLI inside the API container as shown below.

1. Start the stack

cp .env.example .env

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:

FACTORYAL_FACTORIES={"demo": {"repo": "/factoryal-src", "branch": "main", "path": "examples/demo"}}

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:

curl -s "${H[@]}" localhost:8000/v1/blueprints/issue/entities/INC-1

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

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