Skip to content
Back to skills

Factory

ASecurity

deploy and manage a per-project AI software factory (init/up/down/status/teardown) — FTS ticket pipeline + ftown loops. Trigger on "factory init", "deploy a factory", "factory status", "pause the factory".

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 1, 2026
ai-agentspythonshellbashsqlgitdatabase

Works with

  • cursor

Security analysis

A100/100

Pro scans all 12 files and shows the line behind each finding

Scanned October 1, 2026

npx -y skills add fmktech/ftown --skill factory --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Factory?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Factory
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/fmktech-factory/badge)](https://www.skillsdirectory.com/skills/fmktech-factory)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: factory
description: deploy and manage a per-project AI software factory (init/up/down/status/teardown) — FTS ticket pipeline + ftown loops. Trigger on "factory init", "deploy a factory", "factory status", "pause the factory".
---

<!-- Vendored from the ffactory repo (skills/factory + factory-template); sync changes there first. -->

# Factory

You are a capable model helping a human deploy and operate a per-project AI software
factory: a `fticket` (`fts`) ticket pipeline driven by three ftown loops (a dispatcher
that spawns stage workers, a preflight-guarded interval triage sweep, and a daily digest
report). This skill is procedures, not narrative — follow the exact commands. It runs
inside the target project repo (`$REPO` = repo root, resolved via
`git rev-parse --show-toplevel`).

## Layout

- `factory/` — checked into the project repo, the factory's *definition*:
  `factory.yaml` (stages, limits, triage config), `skills/*.md` (stage worker briefings +
  `_protocol.md`), `bin/dispatch.py`.
- `.ffactory/` — gitignored, the factory's *runtime state*: `factory.db` (fts sqlite),
  `tickets/<n>/` (per-ticket artifact folders), `worktrees/` (implement-stage checkouts),
  `dispatch.cursor` (dispatcher bookmark). Disposable — deleting it loses history but not
  the factory's definition.

## init

Idempotent. If `factory/` already exists in `$REPO`, stop and tell the user the factory is
already initialized (point them at `status`/`up`/`down` instead) — do not overwrite.

1. **Preconditions.** Confirm `$REPO` is a git repo. Run `~/.ftown/ftown-sessions list` —
   if it errors, the bridge is down; stop and tell the user to start it first. Confirm
   `uv` is on PATH.
2. **Install fticket.** `uv tool install fticket` (or `uv tool upgrade fticket` if already
   installed). Verify with `fts --help`.
3. **Resolve routing and copy the template.** If the deployment prompt names the
   initiating harness/model, use those values. Otherwise, when `FTOWN_SESSION_ID` is
   set, run `~/.ftown/ftown-sessions get "$FTOWN_SESSION_ID"` and read this session's
   `shellType` and optional `model`. If neither source identifies the harness, ask the
   user instead of assuming one.
   ```bash
   cp -r ~/.ftown/skills/factory/factory-template "$REPO/factory"
   ```
   `factory/bin` moves in as-is (no edits). Set every stage plus `triage` and `digest` to
   the initiating harness/model. When the initiator has no explicit model, remove each
   template `model` value so the harness default is used. Only retain or add a different
   route when the user explicitly requested that override. Also apply the project name
   (default: the repo directory's basename), operator ftown session id (optional —
   default `-`, no operator mail), and shared resources (default `staging`,
   exclusive-only). Edit `factory/factory.yaml` with these values.
4. **Gitignore + state dirs.** Append `.ffactory/` to `$REPO/.gitignore` (create the file
   if absent). `mkdir -p "$REPO/.ffactory/tickets"`.
5. **Init the ticket db**, reading stage order and limits back out of the edited
   `factory.yaml`:
   ```bash
   fts init --db .ffactory/factory.db \
     --stages groom,design,implement,review,qa,pr \
     --bounce-limit 3 --claim-ttl-ms 1800000
   ```
   (substitute the actual stage names in yaml order, `limits.bounce_limit`, and
   `limits.claim_ttl_ms`).
6. **Register shared resources.** For every entry under `resources` in `factory.yaml`,
   register its stable name and policy. For the default template:
   ```bash
   fts register-resource --db .ffactory/factory.db \
     --name staging --policy exclusive_only
   ```
   Repeat once per configured resource. Workers acquire these leases FIFO; do not invent
   resource names at runtime that were not declared in the factory definition.
7. **Register the loops, grouped under one factory label.** The `--group` flag folds all
   loops together in the ftown dashboard and requires ftown bridge PR #29. If
   `loop create` rejects `--group` as an unknown flag, tell the user their bridge is out
   of date and needs updating — do not silently drop the flag and register ungrouped.

   Dispatcher (interval loop, runs the Python dispatcher every tick):
   ```bash
   ~/.ftown/ftown-sessions loop create \
     --name "<project>-dispatch" --every 30s --shell shell --workdir "$REPO" \
     --group "Factory: <project>" --max-runtime 10m --retention 10 \
     --preflight "~/.local/bin/fts queues --db $REPO/.ffactory/factory.db --json | python3 -c 'import json,sys; qs=json.load(sys.stdin); sys.exit(0 if any(q.get(\"queued\",0)+q.get(\"claimed\",0)+q.get(\"in_progress\",0)+q.get(\"blocked\",0) for q in qs) else 1)'" \
     --task "uv run --python 3.13 --with fticket,pyyaml python factory/bin/dispatch.py"
   ```
   (`--every` from `limits.dispatch_every`. The preflight skips the tick — no shell
   session — while no ticket is queued/claimed/in_progress/blocked, so an idle factory
   costs nothing. A resting `rejected`/`dead_letter` ticket doesn't wake it: those belong
   to explicit chaining and triage respectively.)

   Triage (interval loop with a preflight skip guard — only spawns a session when there
   is dead-letter/orphan work; requires ftown bridge >= 0.18.0 for cheap preflight skips —
   on 0.17 the preflight still works but bloats run history since a skip still logs a
   run):
   ```bash
   ~/.ftown/ftown-sessions loop create \
     --name "<project>-triage" --every 10m --shell claude --model sonnet \
     --workdir "$REPO" --group "Factory: <project>" --retention 10 \
     --preflight "~/.local/bin/fts triage --db $REPO/.ffactory/factory.db --json | python3 -c 'import json,sys; d=json.load(sys.stdin); sys.exit(0 if d[\"dead_letter\"] or d[\"orphans\"] else 1)'" \
     --task "FTS_DB=<abs path to .ffactory/factory.db> REPO_ROOT=<abs $REPO> OPERATOR_SESSION=<value or -> — Read and follow factory/skills/triage.md."
   ```
   (`--every`/`--shell`/`--model` from `triage.every`/`triage.harness`/`triage.model`.)
   The preflight exits 0 (work exists → run the session) when `fts triage --json` reports
   any dead_letter or orphan tickets, and exits 1 (healthy → skip, no session spawned)
   otherwise. The triage briefing must set the three task variables literally;
   `triage.md` treats a missing variable as fatal.

   Digest (daily cron loop, shift report mailed to the operator) — register this loop
   ONLY when `operator_session` in `factory.yaml` is not `-`; skip it entirely otherwise,
   since there is nowhere to mail the digest:
   ```bash
   ~/.ftown/ftown-sessions loop create \
     --name "<project>-digest" --cron "0 9 * * *" --shell claude --model sonnet \
     --workdir "$REPO" --group "Factory: <project>" --retention 5 \
     --task "FTS_DB=<abs path to .ffactory/factory.db> REPO_ROOT=<abs $REPO> OPERATOR_SESSION=<operator> — Read and follow factory/skills/digest.md."
   ```
   (`--cron`/`--shell`/`--model` from `digest.cron`/`digest.harness`/`digest.model`.)
8. **Wrap up.** Suggest the user commit `factory/` (not `.ffactory/`). Show them how to
   create the first ticket:
   ```bash
   mkdir -p .ffactory/tickets/1
   # write .ffactory/tickets/1/request.md with the ask
   fts create --db .ffactory/factory.db --title "<title>" --stage groom \
     --folder .ffactory/tickets/1
   ```
   and how to watch it: `fts serve --db .ffactory/factory.db --port 8377` (dashboard).

## resource leases for an existing factory

Existing factories are never overwritten by `factory init`. To introduce a new shared
resource safely, add its stable name/policy under `resources` in `factory/factory.yaml`,
then register it idempotently in the existing database:

```bash
fts register-resource --db .ffactory/factory.db \
  --name staging --policy exclusive_only
fts resources --db .ffactory/factory.db --json
```

Do not rename a resource while leases/waiters exist; register the new name, migrate users,
then retire the old name only after `fts resources` shows it idle.

## up / down

Resume or pause every registered loop by name. Loops don't expose lookup-by-name
directly, so filter `loops` output for the project's `--group` label (`Factory:
<project>`) to get ids first — this naturally covers whichever loops are actually
registered (dispatch + triage always; digest only if an operator is configured):

```bash
~/.ftown/ftown-sessions loops                      # find ids for all loops in group "Factory: <project>"
~/.ftown/ftown-sessions loop resume <loop-id>      # up — repeat for each loop id found
~/.ftown/ftown-sessions loop pause  <loop-id>      # down — repeat for each loop id found
```

## status

Report all of:

1. `fts board --db .ffactory/factory.db` — ticket counts by stage.
2. `fts queues --db .ffactory/factory.db` — per-stage claim queues.
3. `fts resources --db .ffactory/factory.db` — active leases and FIFO waitlists.
4. `fts doctor --db .ffactory/factory.db` — health checks (flag any failure first).
5. `~/.ftown/ftown-sessions loop runs <loop-id>` for every loop id registered under
   `Factory: <project>` (dispatch, triage, and digest if present) — last few runs of
   each, success/error. For triage specifically, also check `loop get <triage-id>` for
   `lastSkipAt`/`lastSkipReason` — frequent skips are healthy (the preflight guard found
   no dead-letter/orphan work).
6. Active workers: `~/.ftown/ftown-sessions list` filtered to names starting with
   `<project>-t` (worker sessions the dispatcher spawned, distinct from the loop names
   themselves which start with `<project>-dispatch`/`<project>-triage`/`<project>-digest`).

## run-now

Force an immediate dispatcher tick without waiting for the interval:
```bash
~/.ftown/ftown-sessions loop run <dispatch-loop-id>
```

## teardown

Confirm with the user first — this is destructive to scheduling, distinct from deleting
data.

1. Pause then delete every loop registered under `Factory: <project>`: `loop pause <id>`
   then `loop delete <id>` for dispatch, triage, and digest (if it was registered).
2. Remind the user: `.ffactory/` is disposable runtime state (db, tickets, worktrees) —
   only delete it if they explicitly confirm, since it's the ticket history. `factory/`
   is the checked-in definition — keep it (removing it is a separate, deliberate repo
   change, not part of teardown).

## Troubleshooting

| Symptom | Cause / fix |
| --- | --- |
| `ftown-sessions list` errors at any step | Bridge is down — start the ftown bridge before retrying. |
| `loop create --group` rejected as unknown flag | Bridge predates ftown PR #29 — tell the user to update the bridge; do not drop `--group` and register loops ungrouped. |
| Workers never spawn even though tickets are queued | Check `loop runs <dispatch-id>` for run status first, then the dispatcher's own stderr (surfaced in the run's session output) for `dispatch.py` errors — e.g. `max_sessions` cap reached, or a claim that keeps expiring. |
| A ticket looks stuck in one stage | `fts why --db .ffactory/factory.db <id>` — gives a stuck diagnosis (expired claim, bounce-limit hit, missing resource lease, etc.) without needing to read the raw db. |
| Triage never fires | Check `loop get <triage-id>` for `lastSkipAt`/`lastSkipReason` — preflight guard skipping is healthy when there are no dead-letter/orphan tickets; only worry if `fts triage --json` shows work but the loop still skips. |

Files in this skill

  • SKILL.md11.2 KB
  • factory-template/bin/dispatch.py17.7 KB
  • factory-template/factory.yaml2.3 KB
  • factory-template/skills/_protocol.md6.3 KB
  • factory-template/skills/design.md15.8 KB
  • factory-template/skills/digest.md4 KB
  • factory-template/skills/groom.md13.1 KB
  • factory-template/skills/implement.md15.2 KB
  • factory-template/skills/pr.md12 KB
  • factory-template/skills/qa.md13.7 KB
  • factory-template/skills/review.md12.5 KB
  • factory-template/skills/triage.md9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…