a kickoff prompt for extracting atelier, written for an unattended run
differs from EXTRACT-A-PLUGIN-PROMPT.md in the two ways that matter when nobody is awake. it does not say "ask and wait", because there is no one to ask. the five decisions that would normally be questions are pre-made with reasoning, and it says to follow a decision even after finding evidence against it — recording the evidence instead. a half-applied decision is worse than a wrong one applied consistently. and it assumes the agent will be restarted mid-run, so it says commit after every task: a clean tree and a commit message are how the next attempt, quite possibly the same agent an hour later with no memory of it, knows where it is. the substantive call is the UI. atelier ships htmx AND a react spa with its own build, and porting 84 files into panels is not an overnight job. gitea already solved this exact problem — sidecar serves its own ui, plugin proxies it same-origin, one panel renders it in an iframe — so the prompt points there and says to read sidecar/rewrite.ts first, which is where the traps are recorded. the database decision is deliberately conditional rather than fixed: count the rows first, fold two empty tables into officer_db, keep the separate database if either has data. losing someone's generated images to a schema decision at 4am is not a trade worth making unattended. it opens with a worktree, and says no second agent in this tree. today two agents with bypassed permissions ended up in one checkout and nothing was lost only because one of them noticed and stopped.
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
# Kickoff prompt: extract Atelier into a plugin
|
||||||
|
|
||||||
|
Written 2026-08-20 for an **unattended overnight run**. That changes two things from
|
||||||
|
`EXTRACT-A-PLUGIN-PROMPT.md`, which assumes the owner is awake:
|
||||||
|
|
||||||
|
- **It does not say "ask and wait".** Nobody is there. Decisions that would normally be questions are
|
||||||
|
pre-made below, with reasoning, so the run does not stall on them.
|
||||||
|
- **It assumes you may be restarted.** Commit after every task, so whoever picks this up — including you,
|
||||||
|
an hour later, with no memory of it — finds a clean tree and a readable history.
|
||||||
|
|
||||||
|
Paste everything below the line into a fresh agent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
You are extracting **Atelier** out of `~/officer-dev/dockers/officer-atelier` and into an Officer plugin,
|
||||||
|
the way `music`, `offscale` and `gitea` were extracted before it.
|
||||||
|
|
||||||
|
**Work in a git worktree, not the main checkout.** Another agent may be working in
|
||||||
|
`/home/pastilhas/officer-dev/platform` tonight. On 2026-08-20 two agents with bypassed permissions ended
|
||||||
|
up in one working tree and only avoided losing work because one of them noticed and stopped. Do not
|
||||||
|
arrange that again:
|
||||||
|
|
||||||
|
```
|
||||||
|
git -C /home/pastilhas/officer-dev/platform worktree add /home/pastilhas/officer-dev/atelier-work -b atelier-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything below happens in `~/officer-dev/atelier-work`.
|
||||||
|
|
||||||
|
## Read first, in this order
|
||||||
|
|
||||||
|
1. **`plugins/EXTRACTING-A-PLUGIN.md`** — the runbook and the authority. Read "What changed on
|
||||||
|
2026-08-16" carefully; it contradicts an earlier instruction in the same file, deliberately.
|
||||||
|
2. **`plugins/example/README.md`** — what a plugin is, as conventions.
|
||||||
|
3. **`src/servers/plugins/`** — `discover.ts`, `manifest.ts`, `mount.ts`, `install.ts`, `ecosystem.ts`.
|
||||||
|
Where this prompt and that code disagree, **the code is right**.
|
||||||
|
4. **`CLAUDE.md`** — house rules, the permission model, and why `assertPermissionTotality` refuses to boot.
|
||||||
|
5. **`plugins/gitea/`** — because of the UI decision below. It solves the same problem.
|
||||||
|
6. **`docs/secret-store.md`** — Atelier holds an OpenAI key.
|
||||||
|
|
||||||
|
## What Atelier is
|
||||||
|
|
||||||
|
Image generation as a service: a provider-shaped API over OpenAI's image endpoints, storing what it makes
|
||||||
|
so every image has a permanent URL. Bun · Hono · Postgres/Drizzle · htmx with Hono JSX · a React SPA.
|
||||||
|
|
||||||
|
```
|
||||||
|
84 ts/tsx files, ~6,400 lines
|
||||||
|
tables collections, provider_settings (its own `atelier` database)
|
||||||
|
routes /generations, /generations/:id, /edits, /adapters,
|
||||||
|
/:id/items, /:id/items/:generationId, mask
|
||||||
|
env BUN_PORT, PUBLIC_URL, POSTGRES_URL, ATELIER_DATA_DIR,
|
||||||
|
OPENAI_API_KEY, ATELIER_MAX_CONCURRENCY
|
||||||
|
runs as pm2 `officer-atelier`, cwd ~/officer-dev/dockers/officer-atelier
|
||||||
|
```
|
||||||
|
|
||||||
|
It is a **sidecar** plugin, not an API-only one, for the two reasons that always decide this: it does
|
||||||
|
long-running work with a concurrency cap, and it holds a credential the platform must not.
|
||||||
|
|
||||||
|
## Decisions already made — do not re-open these
|
||||||
|
|
||||||
|
Each of these would normally be a question. They are answered so you can keep moving. If you find
|
||||||
|
evidence that one is wrong, **write it in `BLOCKERS.md`, follow the decision anyway, and carry on** — a
|
||||||
|
half-applied decision is worse than a wrong one consistently applied.
|
||||||
|
|
||||||
|
**1. The UI: follow gitea, do not port it.** Atelier ships htmx _and_ a React SPA with its own build
|
||||||
|
(`build-web.ts`, `gen-index.ts`). Rewriting 84 files into panels is not an overnight job and is not the
|
||||||
|
point of this extraction. Instead: the sidecar keeps serving its own UI, the plugin proxies it
|
||||||
|
same-origin with `createSidecarProxy`, and a single panel renders it in an iframe. `plugins/gitea` does
|
||||||
|
exactly this and its `sidecar/rewrite.ts` records the traps. **Read that file before writing any of it.**
|
||||||
|
|
||||||
|
**2. The database: check the rows, then decide.** Atelier uses a separate `atelier` Postgres database,
|
||||||
|
deliberately — `src/databases/atelier_db/drizzle.config.ts` says why. Plugin tables normally live in
|
||||||
|
`officer_db` with a prefix.
|
||||||
|
|
||||||
|
- Count the rows in `collections` and `provider_settings` first.
|
||||||
|
- **If both are empty**, fold them into `officer_db` as `atelier_collections` and
|
||||||
|
`atelier_provider_settings`, following `plugins/*/db/schema.ts`. Two tables do not earn a second
|
||||||
|
database.
|
||||||
|
- **If either has rows**, keep the separate database for now and record the migration as a follow-up in
|
||||||
|
`BLOCKERS.md`. Losing someone's generated images to a schema decision at 4am is not a trade worth making.
|
||||||
|
|
||||||
|
**3. Artefacts: `$OFFICER_ROOT/data/atelier/`.** Generated images are files, and `ATELIER_DATA_DIR`
|
||||||
|
currently points wherever its `.env` says. A plugin directory is a git clone that an update replaces, so
|
||||||
|
artefacts kept there vanish on upgrade with nothing logging it. `plugins/kiosk/artefacts.ts` has the same
|
||||||
|
problem solved and the comment explaining it.
|
||||||
|
|
||||||
|
**4. `OPENAI_API_KEY` goes in the secret store**, not `.env`. `putSecret`/`getSecret` landed 2026-08-19;
|
||||||
|
`bun run secret:set atelier-openai-key` puts it there. Read with `getSecret`, **never `getKey`** — `getKey`
|
||||||
|
mints a random value for an absent purpose, which for a foreign credential means silently authenticating
|
||||||
|
with a fake. Fall back to env when the store has nothing, so an install that predates this keeps working.
|
||||||
|
|
||||||
|
**5. One permission, `atelier`, `kind: 'app'`.** Image generation is not `execution` — it does not run as
|
||||||
|
the owner's OS user in the owner's home.
|
||||||
|
|
||||||
|
## How to work
|
||||||
|
|
||||||
|
**Map before you move.** The first task is not a file move. Find every line in the platform that names
|
||||||
|
atelier, and every import pointing INTO it. A plugin may import platform code (`@@/…`); the reverse cannot
|
||||||
|
happen. Report what you find before moving anything.
|
||||||
|
|
||||||
|
**Commit after every task, and push.** You may be restarted at any point by a scheduler that has no idea
|
||||||
|
what you were doing. A clean tree and a commit message are how the next attempt — possibly you — knows
|
||||||
|
where it is. Commit messages: simple lowercase, no prefixes.
|
||||||
|
|
||||||
|
**Do not restart the platform.** `pm2 restart officer` breaks the owner's chat session. Write the command
|
||||||
|
down instead; they will run it.
|
||||||
|
|
||||||
|
**Do not `git add -A`.** Add the paths you touched. This has caused two commits to swallow another
|
||||||
|
agent's work in one day.
|
||||||
|
|
||||||
|
**Format only what you touched** — `bunx prettier --write <paths>`, never `bun format`.
|
||||||
|
|
||||||
|
**`bunx tsgo` must be clean** before every commit. Not `tsc`.
|
||||||
|
|
||||||
|
## When you get stuck
|
||||||
|
|
||||||
|
Write it in `BLOCKERS.md` at the worktree root — what you were doing, what stopped you, what you did
|
||||||
|
instead, and whether it is reversible. Then **move to the next task**. Do not wait, do not invent a
|
||||||
|
workaround that changes behaviour without saying so, and do not stop the run.
|
||||||
|
|
||||||
|
If invented content ends up anywhere durable — copy, defaults, seed data — mark it `[placeholder]` so it
|
||||||
|
can be found later with a grep.
|
||||||
|
|
||||||
|
## Done means
|
||||||
|
|
||||||
|
- `plugins/atelier/` with `manifest.ts`, `api/router.ts`, `sidecar/index.ts`, `web/panels.ts`,
|
||||||
|
`web/layout.ts`, `db/`, `assets/icon.png`, `assets/splash.png`, and its own git repository.
|
||||||
|
- It installs from the Marketplace onto a machine that has never had it, and the panel renders.
|
||||||
|
- `bunx tsgo` clean, `bun test` passing.
|
||||||
|
- A `PLUGIN.md` in the plugin recording what moved, what did not, and why — the way `music` and `offscale`
|
||||||
|
did. This is the part that is worth most in three months.
|
||||||
|
- `BLOCKERS.md` listing every decision you made without being able to ask.
|
||||||
|
|
||||||
|
## What you may NOT do
|
||||||
|
|
||||||
|
- No unauthenticated routes. A plugin cannot serve one; `UNPROTECTED_API_PREFIXES` is hardcoded in the
|
||||||
|
platform. If Atelier needs one, that is a blocker, not something to work around. (`plugins/kiosk`
|
||||||
|
is stuck on exactly this — read its README before concluding you have found a way.)
|
||||||
|
- No changes to `src/servers/plugins/` to make Atelier fit. If the contract does not accommodate it, that
|
||||||
|
is a finding worth more than the workaround.
|
||||||
|
- No second agent, no second worktree, no background job that writes to this tree.
|
||||||
Reference in New Issue
Block a user