How the “Immich” plan was built

A cold agent — given only our public guide and the Immich source — reconstructed the team’s product plan through the API, single-agent, in one clean run. Shown as it ran; the only touch is cosmetic: we standardized the project names.

The finished plan, live: productbrain.com/shared/5xx_muyblQa07PRyR5bgU (the transcript below ends on the staging link the run minted; this is the same plan on the public host).

Your ProductBrain API key is in $PB_API_KEY and the base URL in $PB_API_URL. Use them via those env vars; never print, echo, or hard-code the key. If $PB_API_KEY is NOT set, STOP and tell me — do NOT search the filesystem, env files, or credential stores for credentials. 1. Read the guide at https://productbrain.com/docs/llm-guide.md — it is the full contract for reading and writing a ProductBrain plan. 2. Clone https://github.com/immich-app/immich (a shallow clone is fine) and study it enough to understand what the product does and what the team is actually building and shipping. 3. Create a project named "Immich" and build a ProductBrain plan for it through the API. Build the plan the product genuinely warrants — don't hold back to keep it small, and don't pad it to look thorough. 4. When the plan is in, mint a share link (POST /share) and give me the URL as the deliverable.
bash test -n "$PB_API_KEY" && echo "PB_API_KEY is set" || echo "PB_API_KEY is NOT set"; test -n "$PB_API_URL" && echo "PB_API_URL is set: $PB_API_URL" || echo "PB_AP …

Both env vars are set. Let me read the guide and start the clone in parallel.

bash git clone --depth 1 https://github.com/immich-app/immich.git 2>&1 | tail -5

Now let me fetch the guide:

fetch https://staging.productbrain.com/docs/llm-guide.md

The guide is a summary. It references a fuller API reference and mentions `addProject`, which isn't detailed here. Let me get the concrete schemas for `mutate` and `addProject`, and check the clone.

fetch https://docs.productbrain.com
bash ls /work/immich; echo "---README head---"; head -50 /work/immich/README.md 2>/dev/null

Let me fetch the docs index to find concrete schemas.

fetch https://docs.productbrain.com/llms.txt

Let me fetch the key API reference pages: authentication, mutations, and nodes.

fetch https://docs.productbrain.com/api-reference/mutations.md
fetch https://docs.productbrain.com/api-reference/authentication.md

I have enough of the contract. The docs omit `addProject` and `/share` specifics, so let me probe the live API directly. First, verify auth with GET /projects.

api curl -s -w "\nHTTP %{http_code}\n" -H "Authorization: Bearer $PB_API_KEY" "$PB_API_URL/projects"

Auth works. Now create the project. Let me try the `addProject` action via mutate.

api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"addProject","project":{"i …

Project created: `immich` / "Immich". Now let me study the codebase to build a genuine plan. Let me gather what the product does and what's actively being built.

bash cd /work/immich echo "=== recent commits ==="; git log --oneline -30 2>/dev/null echo "=== README features section ==="; sed -n '/Features/,/reference/p' README …

Shallow clone has one commit. Let me look at the roadmap, docs structure, and actual recent releases/activity via GitHub to understand what's being actively shipped.

bash cd /work/immich echo "=== docs structure ==="; find docs -maxdepth 3 -name "*.md" -o -name "*.mdx" 2>/dev/null | grep -iE "roadmap|feature|release" | head echo …

Rich signal here — I see newer areas: `ocr`, `workflow`/`workflow-execution`, `plugin`, `integrity`, `database-backup`, `hls`, `sync`, `locked` folder. Let me confirm what's genuinely recent vs. mature by checking real releases and the roadmap.

bash cd /work/immich echo "=== GitHub recent releases ==="; curl -s "https://api.github.com/repos/immich-app/immich/releases?per_page=6" | grep -E '"tag_name"|"name" …

v3.0.0 just shipped (July 2, 2026) — a major release. Let me read its release notes to see what actually shipped, plus the recently-added feature docs.

bash cd /work/immich echo "=== v3.0.0 release notes ==="; curl -s "https://api.github.com/repos/immich-app/immich/releases/tags/v3.0.0" | python3 -c "import sys,json …

This is the goldmine. Let me get the rest of the v3.0.0 notes for detail on Workflows, HLS, Integrity, OCR.

bash cd /work/immich curl -s "https://api.github.com/repos/immich-app/immich/releases/tags/v3.0.0" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['bo …
bash cd /work/immich curl -s "https://api.github.com/repos/immich-app/immich/releases/tags/v3.0.0" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['bo …

I now have an accurate, detailed picture of Immich and the team's active work (v3.0.0, July 2026). Let me learn the exact mutate mechanics before building: test a single Goal add to see the response shape and which fields stick.

api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"add","projectId":"immich" …

Good. Now let me test batch semantics and whether children can reference parents created in the same batch (temp refs).

api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"batch","projectId":"immic …

Batch temp-refs aren't supported. Let me confirm batch returns IDs in order (so I can build level-by-level with real parent IDs).

api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"batch","projectId":"immic …

Same-batch parents aren't visible. So I'll build level-by-level. Let me confirm a sibling batch works and see the ID array shape.

api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"batch","projectId":"immic …

Batch works and returns ordered `results` with IDs. Let me clean up the three probe nodes, then build the real plan.

api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"batch","projectId":"immic …

Batch add works cleanly. Now let me learn the phases/iterations API before writing the full builder.

api echo "=== GET iterations ==="; curl -s -H "Authorization: Bearer $PB_API_KEY" "$PB_API_URL/iterations?projectId=immich" echo ""; echo "=== POST iteration ===" c …

Iterations are managed via `mutate` with `addIteration`. Let me probe that format.

api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"addIteration","projectId" …
api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"addIteration","projectId" …
api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"deleteIteration","project …
api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"deleteIteration","project …

I now fully understand the API. Let me write the plan builder script encoding the genuine Immich plan (grounded in the v3.0.0 release and repo structure), then run it.

write /work/build_plan.py
bash cd /work && python3 build_plan.py

The approach `status` field is the completion status (done/delivered), not the lifecycle. The lifecycle status (development/validation/resolved/retired) must use a different field name. Goals and needs were created though — I have partial state. Let me check the approaches doc for the correct field name, and list current nodes to plan cleanup.

fetch https://docs.productbrain.com/framework/approaches.md
api curl -s -H "Authorization: Bearer $PB_API_KEY" "$PB_API_URL/nodes?projectId=immich" | python3 -c "import sys,json; d=json.load(sys.stdin); ns=d.get('nodes',d); …

Goals + 3 needs got created. Let me empirically find the approach lifecycle field name by creating a probe approach with several candidate keys and reading back which one persists.

api curl -s -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"action":"add","projectId":"immich","node":{"type":"approach", …

The store echoes arbitrary keys, so I need the canonical field name the UI actually reads.

Canonical field is `approachStatus`. Let me fix the script: use `approachStatus` for approach lifecycle (not `status`), and add an idempotent wipe-first step so partial state is cleaned and re-runs are safe.

edit /work/build_plan.py
edit /work/build_plan.py
edit /work/build_plan.py

Now wire `wipe()` into main and make iteration creation tolerant of already-existing phases.

edit /work/build_plan.py
edit /work/build_plan.py
bash cd /work && python3 build_plan.py

Plan built: **6 goals, 16 needs, 31 approaches, 47 jobs**. Let me verify the tree structure and field integrity before sharing.

api curl -s -H "Authorization: Bearer $PB_API_KEY" "$PB_API_URL/nodes?projectId=immich" | python3 -c " import sys,json,collections d=json.load(sys.stdin); ns=d.get( …

All fields validate — 37/47 jobs marked done (the v3.0-shipped gates), 10 in progress across the two forward-looking phases. Now let me mint the share link.

api curl -s -w "\nHTTP %{http_code}\n" -X POST -H "Authorization: Bearer $PB_API_KEY" -H "Content-Type: application/json" \ -d '{"projectId":"immich"}' "$PB_API_URL …

The plan is in and the share link is live. ## Deliverable — ProductBrain share link **https://staging.productbrain.com/shared/Km_PdSLys0kNJaWYazbdN** (Public link, no expiry — treat the token as a secret; it can be revoked via `DELETE /share`.) ## What I built Project `immich` ("Immich") now holds a plan grounded in the actual codebase and the **v3.0.0 release** (shipped July 2, 2026), not a generic photo-app template. I shallow-cloned the repo, read the server service map (`ocr`, `workflow`, `plugin`, `integrity`, `database-backup`, `hls`, `sync`…) and the full v3.0.0 release notes to separate what's *shipped* from what's *in progress*. **Shape:** 6 Goals → 16 Needs → 31 Approaches → 47 Jobs, across 3 delivery phases. | Goal (business outcome) | Notable bets | |---|---| | Trusted, durable home for a lifetime of media | Integrity reporting, auto DB backups, reliable Android/iOS background backup, RC channel + VectorChord migration | | Make leaving cloud photo services easy | Docker Compose install/upgrade, external libraries, CLI import | | Find any photo in seconds | CLIP search, face clustering, **OCR** (in dev), duplicate detection, Recently Added | | First-class viewing & editing on every device | **HLS real-time transcoding** (validation), unified web player, non-destructive editing web↔mobile, timeline perf, mobile slideshow | | Effortless, safe sharing | Public links, mobile share-size picker, Android gallery integration, partner/shared albums, upload-to-album | | Grow into an automation platform | **Workflows** engine (validation), plugin system (dev), public API + CLI | **Honesty calibration:** - **37/47 jobs marked `done`** — exactly the gates v3.0 actually landed (integrity checks, workflows builder, HLS web playback, mobile editor, recently-added, etc.). - **10 jobs left open** in the two forward phases — precisely the things the release notes flagged as *not yet implemented*: mobile HLS playback, remux/HDR passthrough, library-wide OCR search, re-adding dropped mobile edit features, Android "already-in-library" detection, expanded workflow triggers, and the plugin API. - Approaches previewed in v3 (Workflows, HLS) are `validation`; nascent ones (OCR, plugins, Android gallery) are `development`; mature shipped ones are `resolved`. - Mature, long-stable areas (CLIP search, partner sharing, API) are recorded as Approaches **without** padding them with Jobs — per the guide, Jobs are only for actively-worked bets. All approaches carry a concrete `measure` and Kano class; every field validated clean (no orphans, no bad enums). One note on the API vs. the guide: the linked `llm-guide.md` is a summary, so I pulled concrete schemas from `docs.productbrain.com` and probed the live API for the bits it omitted — `addProject` (takes a `project` object), `addIteration`, and the approach lifecycle field, which is `approachStatus` (the `status` field is reserved for `done`/`delivered` completion).

Same prompt each time — only the repo and the project name change. ← all three · back to the demo