Publish a template
Version: v2 (templates flow). This versions the publish/adopt flow and the
manifest format. v2 publishes ONE slug-free template.md + template.toml
template.svgper repo, overriding any previous manifest rather than accumulating beside it, with the machine-readable half (recipe, requirements, and the environment an adopter must install) in the TOML. v1 -- slug-namedinspiration-<slug>.mdwith a YAML recipe block inside it and no TOML -- is still READ by the adopt paths, but nothing writes it any more.
An "template" is a clean, shareable, bootable snapshot of something this
mind built -- an app or feature, but equally a chat customization or behavior, a
skill, a workflow, a service, config, or seed data: anything committable that
lives in the repo tree and can be snapshotted -- published to a new GitHub repo
so another mind can be created FROM it (not just read its source). A repo holds
EXACTLY ONE template -- template.md, template.toml, and
template.svg at the repo root -- and publishing overrides whatever was
there before, recording what it replaced as a [[lineage]] entry. This skill delegates the assembly to a launch-task sub-agent
worker (which builds the snapshot, finishes the manifest, and designs the
thumbnail in its own git worktree), confirms the publish with the user in
chat, obtains GitHub access via latchkey permissioning (never the gh CLI),
and then creates the repo and pushes -- directly from the worker's worktree.
CWD INVARIANT -- read this before running anything in §§6-8. From the moment §3's worker reports
done, the live mind's checkout at/home/user/workspaceis DONE being touched for the rest of this skill. Every command in §6 (chat confirmation and any confirmed manifest/thumbnail edits), §7 (GitHub auth), and §8 (create repo + push) runs with cwd =$WT(the worker's worktree), NEVER/home/user/workspace. There is no merge-back step:$WT's tree, built bybuild_template.shon top ofBASE_REFand finished by the worker, IS the tree that gets pushed, as-is, by you, from$WT. In particular, IGNORElead-proxy.md's defaultdone -> merge the worker's branchhandling for this flow --/home/user/workspace's branch and working tree are never modified, merged into, or pushed from. This is the single most important invariant in this skill: a prior version of this skill merged the assembly branch into/home/user/workspace's current branch before pushing, which one time silently reset/home/user/workspace's entire live tree to an old base (a normal 3-way merge diffs from the merge-base, and the assembled tree looks nothing like/home/user/workspace's HEAD, so git read everything present in HEAD but absent from the old base as an intentional deletion -- 1400+ files gone from a live mind). Do not reintroduce a merge, agit checkout mngr/<slug>in/home/user/workspace, or any other step that runs from/home/user/workspaceafter assembly.The ONE sanctioned exception: §8 step 4, the version-history entry. After the push has SUCCEEDED,
/home/user/workspacegets exactly one write -- appending this publish todocs/VERSION_HISTORY.mdand committing that single file on the branch/home/user/workspaceis already on. That is a normal one-file commit, not a tree operation:git add docs/VERSION_HISTORY.md+git commit, and NEVER a merge, a checkout, a reset, agit add -A, or anything that touches another path. It is what makes a publish knowable afterwards (slug, repo, version, and the source commit the snapshot was cut from). Do not mistake it for the tree-clobbering pattern above, and do not generalize it: nothing else in §§6-10 runs from/home/user/workspace, and if the push fails it does not run at all.
A TEMPLATE MUST BE BOOTABLE -- NEVER PUBLISH A PARTIAL SNAPSHOT. A valid template is always the FULL tree
build_template.shassembles onmngr/<slug>: the clean DEFAULT_WORKSPACE_TEMPLATE base (pyproject.toml,system/supervisord.conf,.mngr/,.agents/skills/including the generated template/welcome,system/config/parent.toml, etc.) plus the selected app/feature paths -- never just the app code plus a README. That full tree is what makes/use-template's template path work: another mind must be creatable FROM the published repo, not merely able to read its source. If assembly (§3), the chat confirmation or GitHub auth (§6-§7), or the push (§8) fails for ANY reason, do NOT invent an alternate publish mechanism -- do not push a hand-assembled subset of files viagh api, a plaingit initof just the app directory, or any other ad-hoc path outside this skill's documented flow. A non-bootable "template" silently defeats the whole feature and is strictly worse than no publish at all. Instead: diagnose and fix the actual blocker (e.g. re-resolveBASE_REFper §2, relaunch the worker, pick a different repo name) and retry the documented flow from the failed step, or STOP and clearly tell the user what failed, why, and that you did not publish -- never silently redefine what "publishing a template" means.
Shared conventions
- Slug derivation.
slug= the user's title lowercased, with each run of characters outside[A-Za-z0-9._-]collapsed to a single-, and leading / trailing-stripped. The result MUST match^[A-Za-z0-9._-]+$and MUST NOT start with-(build_template.shre-validates).repo_namedefaults toslug; the user may override it in the chat confirmation (§6) -- validate any override against the same pattern yourself. The same slug names the manifest (template.md), the thumbnail (template.svg), the assembly worker, and the worker's branch (mngr/<slug>). $WT-- the worker's worktree.mngr createplaces worker worktrees under/home/user/worktrees/<name>-<uuid>/(theworktree_base_folderin.mngr/settings.toml; the<uuid>suffix is random), so the path cannot be guessed -- resolve it after the worker'sdonereport per §3. Everything after assembly runs with cwd =$WT(see the callout above).BASE_REF(provenance + clean base). The workspace's template base -- the template state this mind started from (or last updated itself to). Resolve it in-repo, with no network access (see step 2); do NOTgit fetch/git pullupstream. Pass it tobuild_template.shas--base-ref.
0. Somewhere to publish TO (ask first, before anything else)
Settle this before the scope questions in §1. Publishing puts the code on an account the USER owns, so if they have not got one, everything after this -- the interview, a full assembly, a bespoke thumbnail -- is time spent on a publish with nowhere to go. Ask before spending it.
Open with what publishing actually means, in their terms:
To publish, we'll post the code for your template on your own account on a code-hosting platform such as GitHub. Do you have a GitHub account?
Then, by their answer:
They have one. Ask them to connect it, and initiate the GitHub permission request now (§7 has the exact latchkey calls; it re-probes before the push, so requesting early is safe and never duplicates a grant). Doing it here means the approval is waiting for them in minds while assembly runs, instead of stalling the publish at the end.
They do not. Point them at https://github.com/signup -- a free account is enough for this -- and wait for them to tell you it exists before going on to §1. Do not start assembling on the assumption that they will get around to it.
They would rather use something else (GitLab, Codeberg, a self-hosted forge). That is a legitimate answer, not a problem -- but say plainly that you will need to work out how to authenticate that platform with git first, and that you have not done it yet. Do the research BEFORE promising a publish, because three things in this flow are GitHub-shaped and each needs a replacement:
- auth -- §7 requests the latchkey
github-rest-apiandgithub-gitscopes, and the push goes through the gateway's GitHub proxy; - repo creation -- §8 step 1 POSTs to
api.github.com/user/repos, and step 1b/3 PATCH settings and set theminds-templatetopic; - the README -- the generated "Open in Minds" button and its copyable
/use-templateline both hardcode ahttps://github.com/prefix around the repo placeholder, so both need rewriting for another host. The trampoline itself takes any git URL, so only the prefix is wrong.
If you cannot find a way to authenticate their platform, say so and offer GitHub as the path that works today, rather than half-publishing.
- auth -- §7 requests the latchkey
Everything from §1 on assumes this is settled.
1. Setup Q&A and the scope gate (live in chat)
Ask the user, in plain language. Never enumerate files at them:
- what they want to include -- an app or feature, but equally a skill, a chat
customization or behavior, a workflow, a service, config, or seed data:
anything committable that lives in the repo tree. You translate this into a set
of repo-root-relative include paths (e.g.
system/apps/slack_inboxplus their service wiring, or.agents/skills/<name>for a skill) -- you reason about the backing paths, the user does not; - what data should be included -- and this is NOT an all-or-nothing default. Judge each candidate data path by whether it is personal: information about the user or specific real people (names, emails, accounts, messages, contacts, private notes -- anything identifying, or that they'd reasonably consider theirs). Personal data is kept private by default -- excluded from the snapshot. Non-personal data -- generic seed/sample/reference data, fixtures, config defaults, and public or synthetic datasets with no tie to a real person -- is included by default, since shipping it is what makes the template bootable and genuinely useful to an adopter. For anything remotely close to the boundary -- arguably personal, a mix of personal and non-personal, or simply data you are not sure how to classify -- do NOT silently pick a side: ask the user what they want and let their answer decide. When in doubt, treat it as near-the-boundary and ask rather than guessing;
- whether anything should be changed, removed, or generalized in the published version only -- hardcoded personal preferences, account or channel names, anything they'd rather not ship. Their live files stay untouched; the edits land only in the snapshot (see the modifications step in §3);
- a name for the template (propose one yourself; it becomes the title, and the slug derives from it -- naming is cheap to change later, so it never needs to hold up the gate below).
If what they want to snapshot is not committed to git -- an ephemeral chat
behavior, the current conversation's history, runtime-only state, anything that
lives only in memory or outside the repo tree -- it cannot go into a template
as-is: a template must be reconstructable from the committed tree, so a
snapshot that omits it would boot without the very thing that made it worth
sharing. Recognize this and, before going further, suggest turning it into
something committable first -- most often by crystallizing it into a skill (this
repo's crystallize-creation skill promotes just-finished work into a committed,
tested skill), or otherwise capturing it as config, seed data, or a service that
does live in the tree. Once it is committed, include it like any other path
above.
Derive slug and repo_name from the title. Resolve the concrete set of
include paths yourself.
The scope gate: confirm BEFORE any assembly work -- before treating the include set as final, and before dispatching the worker (§3). This is a hard gate. Send ONE message that lays out, in plain language:
- what WILL be included (apps/features, plus any non-personal data that ships with them -- not file lists);
- what will NOT be included that they might expect (their personal data, other apps this mind has, secrets/config) -- so surprises surface now;
- any data near the personal/non-personal boundary you flagged in the data question above, restated so they can settle it before assembly begins;
- the published-version modifications you will apply (or "none");
- the proposed title and repo name, marked as adjustable later;
- the default private visibility.
Then STOP your turn and WAIT for the user's reply. The go-ahead must be an explicit answer to THIS message -- the user's original "publish this" request is NOT it, however specific it was. Never announce anything as "confirmed" that the user has not themselves replied to: confirmation is something the user gives, not something you declare. (A real publish run declared "include set confirmed" and dispatched the worker in the same turn as its own proposal; this gate exists to prevent exactly that.)
A rename NEVER requires tearing down or relaunching the worker. The worker's own name and its branch name are internal plumbing -- they appear nowhere in the published repo (§8 mints a single snapshot commit from the final tree and pushes that commit, never the branch), so a stale name there is irrelevant. If the user renames after dispatch anyway, handle it in place:
- Renamed before the worker has run the script: just pass the new
--slug/--titletobuild_template.sh. - Renamed after the script has run (worker mid-run or done): rename in
place --
git mvthe manifest and thumbnail to the new slug names, update the front-mattertitle:and the generated welcome's slug references, commit (in the worker's worktree). This preserves any FILL-IN prose and bespoke SVG already done. Do NOT re-run the script under a new slug in an already-assembled worktree: it would regenerate the manifest from scratch and lose the FILL-IN prose and thumbnail already done. A display-title-only change is just the front-matter edit.
2. Resolve BASE_REF and SOURCE_SHA (in-repo, no network)
BASE_REF is this workspace's template base -- the template state the
mind started from (or last updated itself to). Resolve it deterministically as
the NEWEST first-parent commit that is a template-state marker:
BASE_REF=$(git log --first-parent --format='%H %s' HEAD \
| awk '{h=$1; sub(/^[^ ]+ /,""); if ($0 ~ /^update-self:/ || $0 == "Initial workspace commit") {print h; exit}}')
Two marker kinds; the newest one on the first-parent chain wins:
update-self: ...-- the mind pulled a newer template version after creation (the same subject conventionupdate-self/assistrely on).Initial workspace commit-- written by bootstrap on the mind's very first boot (always present -- it is created--allow-emptybysystem/libs/bootstrap-- and it snapshots exactly what the workspace started from, including any uncommitted source state a dev-flow clone carried). This is the normal answer for a mind that never ranupdate-self.
This is NOT a judgment call -- do not go hunting for an older "clean template" commit past the marker. A full-history clone's first-parent ancestry reaches ancient template commits that have nothing to do with this mind; the marker is the mind's actual base.
Fallback (only if NO marker exists -- a hand-made or pre-bootstrap repo): the first-parent root:
git rev-list --first-parent HEAD | tail -1
The fallback MUST be the first-parent root, never a bare root-commit lookup
(git rev-list --max-parents=0 HEAD): subtree merges add parallel root commits
that are NOT the seed (a mind repo can have several near-empty roots), while
the first-parent chain from HEAD always ends at the true template seed. Do NOT
fetch or pull from upstream to obtain BASE_REF in any case -- system/config/parent.toml
is a provenance link only.
Mandatory pre-check (before ANY assembly). Verify the resolved base is a
bootable template -- its tree must name both pyproject.toml and
system/supervisord.conf:
git ls-tree --name-only "<BASE_REF>^{tree}" | grep -qx pyproject.toml \
&& git ls-tree --name-only "<BASE_REF>^{tree}" | grep -qx system/supervisord.conf
If the check fails, STOP and reconsider the base (e.g. walk forward along the
first-parent chain to the earliest commit that passes both checks, or ask
the user) rather than launching the worker -- this catches the wrong-root and
too-old-base problems in seconds instead of a full worker round-trip.
build_template.sh re-validates both conditions itself and exits 5 with
a clear message (see §5), but that is a backstop, not a substitute for the
pre-check.
(The same marker walk seeds the version ledger's ## Workspace origin line in
§8 step 4 below (and in the update apply's _origin_line, in update-self's
scripts/update_self.py) -- with one deliberate difference: the
origin-line walk takes the OLDEST marker (where the mind started) where this
section takes the NEWEST (the base the mind is on now). This BASE_REF bash is
the primary; keep the two in step if either ever changes.)
Also capture SOURCE_SHA -- the source commit the snapshot is cut from.
The worker's worktree branches off /home/user/workspace's current HEAD, so that commit is
the provenance anchor recorded in §8 step 4's version-history entry (and what a
later reader diffs against to see what changed since). Capture it now, in
/home/user/workspace, BEFORE dispatching -- not after the push, when /home/user/workspace's HEAD may
have moved on:
SOURCE_SHA=$(git rev-parse HEAD)
3. Delegate assembly to a launch-task worker
Do NOT dispatch until the user has explicitly replied to §1's scope-gate message confirming what goes in, what stays out, and the published-version modifications. If a rename arrives after dispatch anyway, fix it in place per §1 -- never tear down or relaunch the worker for a rename (its name and branch are internal and appear nowhere in the published repo).
Assembly runs in a launch-task sub-agent worker. The worker gets a fresh git
worktree on branch mngr/<slug>, runs build_template.sh there, then --
in the SAME run, no second round-trip -- fleshes out every manifest FILL-IN
block and designs the bespoke thumbnail. /home/user/workspace is never modified.
The worker name is <slug>. Names must be unique: if a previous attempt left
a worker or branch with this name, clean it up first
(uv run .agents/skills/launch-task/scripts/create_worker.py destroy --name <slug>,
then git branch -D "mngr/<slug>" once no worktree holds it).
Per launch-task, the whole delegation is ONE step in your timeline:
tk create --step "Delegate assembling the shareable template snapshot to a sub-agent"
# -> Created cod-step-XXXX: ...
tk start cod-step-XXXX
Write the task file. Substitute the real <slug>, <title>,
<description>, <BASE_REF>, the include paths, and the user-confirmed
published-version modifications list (from §1's scope gate; write "None
requested." if there are none) into the body -- the worker must be able to
run the script verbatim, with zero back-and-forth:
mkdir -p data/.tasks/launch-task/<slug>
{
cat << FRONTMATTER_EOF
---
finish_report_path: data/.tasks/launch-task/<slug>/reports/report.md
---
FRONTMATTER_EOF
cat << 'BODY_EOF'
# Task: Assemble the "<title>" template snapshot
## What to do
Assemble a clean, bootable "template" snapshot on your worktree's branch,
then finish its manifest and thumbnail. Do ALL of it in this one run.
**Before anything else**, extract `LEAD_AGENT` / `FINISH_REPORT_PATH` per
`.agents/shared/references/worker-reporting.md`: step 1's script resets your
worktree to a clean template base and deletes gitignored state -- including
`data/` and this task file -- so parse the frontmatter FIRST.
1. **Run the assembly script** from your worktree root, verbatim (every value
below was already resolved by the lead):
```bash
bash .agents/skills/publish-template/scripts/build_template.sh \
--base-ref <BASE_REF> \
--slug <slug> \
--title "<title>" \
--description "<description>" \
--include <path> [--include <path> ...] \
[--data-include <path> ...]
```
On ANY non-zero exit: nothing was committed; go straight to "Reporting
back" with a `stuck` report that quotes the script's stderr verbatim (the
exit code maps to a specific guard rail the lead knows how to handle). Do
NOT retry with different arguments and do NOT assemble anything by hand.
2. **Apply the published-version modifications** (skip if the list below
says none). These are user-confirmed edits that belong ONLY in the
published snapshot -- the live mind keeps its own versions, and nothing
you do here touches it:
<one line per modification: file + the change to make, e.g.
"system/apps/slack_zen_garden/config.py: replace the hardcoded '#team-garden'
channel with a neutral default the adopter sets" -- or the single line
"None requested.">
After applying them, re-run the secret scan over every file you modified,
with the same shared script the assembly's scan gate uses. It runs both
scanners (betterleaks, kingfisher) and exits non-zero on any finding, any
scanner error, or any missing scanner -- there is no fallback scanner:
```bash
bash .agents/skills/publish-template/scripts/scan_secrets.sh <each modified file>
```
A finding means a modification did not fully remove a credential -- fix
it or report `stuck`; never leave it in. If the script reports a missing
scanner, or the script itself is absent from your assembled tree (a
BASE_REF that predates it), report `stuck` -- do not substitute a weaker
ad-hoc scan.
3. **Flesh out the manifest.** `template.md` at the repo root has
`<!-- FILL-IN (publishing agent): ... -->` comment blocks in "What it is,"
"How it works," "Requirements," "Environment," and "Publication
history" -- generated placeholders, not real content. Replace EVERY block
with real, specific content. "Publication history" is this template's
changelog: replace its FILL-IN with the first entry `### v1 (YYYY-MM-DD) --
<one line: what this first version publishes>` using today's date; later
updates append `### v2 (date) -- what changed`. It is the PUBLISHER's log --
never write into "Adaptation history", which is the adopters' log.
"Requirements" is the strictest, and it holds two kinds of entry. The
ACTIVATION half is one machine-readable line per requirement, in the exact
`requires_permission:` / `requires_secret:` forms
the template shows, derived from the included code (inspect every service
the app reaches through `latchkey curl` and name the real latchkey scope
and permission schema, e.g. `slack-api / slack-read-all`). These lines are
what the ADOPTING agent acts on during setup -- it initiates each one via
a latchkey permission request before asking how to adapt -- so a vague or
missing line silently breaks adoption (a real incident: an adopter never
prompted for a Slack permission the app needed). This list IS "what the
template's user would need to give for the app to work"; it must be
complete and accurate, because the lead surfaces it back to the publishing
user for confirmation in §6 and a gap you leave here is exactly what that
confirmation is checking for. The ADAPTATION half is prose bullets in the
same section -- design gaps, stubbed integrations, hardcoded accounts --
worked through interactively rather than acted on automatically. Both kinds
live under one heading, and each entry's kind decides how it is handled, so
there is no longer a wrong heading to file something under -- but you DO
have to use the `requires_` line form for anything that must be activated,
or it will not be. If a section genuinely has nothing to add, say so
explicitly in prose; never leave a placeholder comment in place and never
leave a section blank.
**LLM access is a first-class activation requirement.** If any included code calls an
LLM (Claude) -- an AI-driven service, an AI integration, a scripted model
step -- record that dependency explicitly, because HOW a mind reaches Claude
is per-environment and differs between the publisher and the adopter. This
repo's `use-ai-integration` skill routes through a KEYED path
(`ANTHROPIC_API_KEY` set -> `litellm`, pay-per-token API) or a KEYLESS path
(`claude -p` -> the subscription credit pool), chosen by whether
`ANTHROPIC_API_KEY` is present. The adopter's mind may use the OTHER method
than the one this code was written against. So add a `requires_llm:` line naming
the LLM dependency and the method it was built for, e.g. `requires_llm: calls
Claude via the keyed litellm path (ANTHROPIC_API_KEY); an adopter on the
keyless subscription path must switch the model calls per use-ai-integration`.
If the code hardcodes one path (a key, an endpoint, a specific model), ALSO
list switching it to the adopter's method as a Requirement. Never leave an LLM
dependency implicit: the adopter must know the app needs LLM access and be
able to wire in their own method (subscription or litellm).
If you edit the manifest's front matter, remember it is YAML: quote any
value containing a `"`, a `: `, or a leading `#`/`&`/`*`/`%`, escaping inner
quotes (`title: "The \"Daily\" Digest: v2"`). The generator already does;
a hand-edit that does not will fail validation.
**The machine-readable half lives in `template.toml`.** Fill it in at the
same time, from the same knowledge -- the validator compares the two files and
the publish fails if they disagree:
- `[recipe]`'s `exclude` -- every deliberate exclusion: paths NOT included
that a reader might expect, and features stripped out of an included path.
This is what keeps an exclusion excluded when a later update re-runs the
recipe against a source workspace that still has the thing.
- `[recipe]`'s `modification_rules` -- one entry per published-version
modification from step 2, written as a RULE and NEVER as the removed value
(`replace the hardcoded team Slack channel with a neutral default`, never
the channel name itself). The whole point of a modification is that the
value does not ship; restating it here would publish it.
- `[requirements]` -- one `[[requirements.permission]]` per
`requires_permission:` line you wrote, one `[[requirements.secret]]` per
`requires_secret:`, and a `[requirements.llm]` table if there is a
`requires_llm:` line. One-for-one with the markdown, both directions --
that is the half the validator cross-checks. Mirror the adaptation
bullets as `[[requirements.adaptation]]` entries too; those are prose on
both sides, so they are not compared.
- `[environment]` -- what the included code needs INSTALLED beyond the stock
template. Derive it from the CODE, not from whatever happens to be
installed on this machine: every binary it shells out to, every global
npm/uv/cargo tool it invokes. `apt` takes bare package NAMES (versions are
a function of the apt snapshot timestamp, so the adopter's timestamp
supplies them); `npm_global`, `uv_tools`, and `cargo_crates` are
`name = "version"` maps, because those registries are not snapshot-pinned
and the version is the only pin available. For an install with no package
database at all -- a URL-fetched binary, a browser -- ship a
`system/scripts/env.d/<NNNN>-<slug>-<name>.sh` unit with `NNNN` >= 2000,
add it to the recipe's `include`, and list it in `env_d_units`. Leave the
tables empty if the app genuinely needs nothing extra.
Every declared apt package must resolve in the pinned snapshot mirror, and
the validator checks that -- so a package from an unmirrorable third-party
source is rejected here rather than at some adopter's first boot.
The generated `README.md` is the repo's GitHub landing page -- the thing
that decides whether a person boots this at all. It carries three FILL-IN
blocks ("Why you care", "How to use it", "Ideas for making it yours"); the
full recipe for what belongs in each, and the distinction between Ideas and
the manifest's Requirements, is in
`.agents/skills/publish-template/references/readme-recipe.md`. Read it
before writing them. The hero graphic is the thumbnail you design in step 4,
and the "Open in Minds" button carries a placeholder repo URL the LEAD
substitutes once the repo exists -- leave that alone.
Do NOT render a preview yourself. The preview tab lives in the USER's
workspace and belongs to the lead's conversation; a background worker
opening it would surface half-finished work in front of the user
unannounced. The lead renders the finished README and asks the user about
it at §6. Your checks are the validator and the greps in step 6.
4. **Design the thumbnail.** `template.svg` at the repo root is a
generic placeholder the script generated -- it must never be published.
Replace its entire contents with a bespoke SVG you design for THIS app: a
clean, simple, iconic representation of what the app actually is and shows
(derive it from the app code and the manifest you just wrote -- e.g. a
stylized miniature of its main screen or its core object). Hard rules:
mock data only, never real user data; no `<script>`; no `on*=` event
attributes; no `<foreignObject>`; no external references (no href/src
pointing outside the file) -- fully self-contained. Keep the root
`viewBox` around 240x160.
5. **Commit** the modification + manifest + thumbnail edits as a follow-up
commit on your branch (`mngr/<slug>`), in your worktree.
6. **Self-check, then report.** The validator must exit 0 and both greps must
print NOTHING before you may report `done`:
```bash
uv run --no-project --with 'pydantic>=2' python \
.agents/skills/publish-template/scripts/validate_template.py .
grep -n -- '<!-- FILL-IN (publishing agent)' template.md README.md
grep -nEi -- 'minds-placeholder-thumbnail|<script|<foreignObject|on[a-z]+[[:space:]]*=' template.svg
```
Run the validator with NO `--allow-unfinished` and NO `--skip-apt-check`:
the assembly script used both when it checked its own freshly-generated
skeleton, and this is the run that actually enforces the finished article --
the markdown and TOML agreeing, no leftover placeholders, every declared
`env.d` unit shipping, and every declared apt package resolving in the
pinned mirror. It reports every problem at once, so fix them together.
If anything fails, fix and re-commit; do not report done until the validator
is clean, both greps are silent, and `git status` is clean.
## Context
- Your worktree is a fresh checkout on branch `mngr/<slug>`. The script
resets it to the clean template base `<BASE_REF>` and overlays only the
selected paths, so the final tree looks nothing like the live mind's HEAD
-- that is correct and expected. Do not "restore" anything it removes.
- Included paths and what each one is:
<one line per include path: what it is and its role>
- <extra context the lead has: what the app does for its user, known gaps,
tokens/accounts it depends on -- everything the worker needs to write a
good manifest and a representative thumbnail>
## Success criteria
- `build_template.sh` exited 0 and its commit is on `mngr/<slug>`.
- Every published-version modification applied, its files re-scanned clean.
- Every FILL-IN block replaced with real prose (or an explicit "none") -- in
BOTH `template.md` and `README.md`.
- `template.toml` filled in to match: the recipe's exclusions and
modification rules, one structured entry per `requires_` line, and the
`[environment]` declarations the included code actually needs.
- `validate_template.py` exits 0 with no `--allow-unfinished` and no
`--skip-apt-check`.
- `README.md` describes this template (not the default-workspace-template).
- `template.svg` is a bespoke design for this app; the placeholder
marker is gone and the safety grep is clean.
- Follow-up edits committed on `mngr/<slug>`; `git status` clean.
## Reporting back
Follow `.agents/shared/references/worker-reporting.md` for the full report
procedure. Substitutions for this task:
- `<TASK_FILE_GLOB>` -> `data/.tasks/launch-task/*/task.md`
- `<RUNTIME_REPORTS_DIR>` -> `data/.tasks/launch-task/<slug>/reports/` (recreate
it with `mkdir -p` -- the assembly script deleted `data/`)
- Valid `name:` values: `question` (mid-flight gate), `done` / `stuck`
(terminal).
In a `done` report body, include your worktree's absolute path (from
`git rev-parse --show-toplevel`) and the branch `mngr/<slug>` -- the lead
publishes directly from that worktree. In a `stuck` report, quote the
assembly script's stderr verbatim.
BODY_EOF
} > data/.tasks/launch-task/<slug>/task.md
Launch (foreground, so a failed launch surfaces immediately):
uv run .agents/skills/launch-task/scripts/create_worker.py launch \
--name <slug> \
--template worker \
--runtime-dir data/.tasks/launch-task/<slug>/ \
--task-file data/.tasks/launch-task/<slug>/task.md
Background-await the report (Bash run_in_background: true -- never block
on it), then continue with whatever else you were doing:
# Run with Bash run_in_background: true
uv run .agents/skills/launch-task/scripts/create_worker.py await \
--name <slug> \
--task-file data/.tasks/launch-task/<slug>/task.md
Handle the report per .agents/shared/references/lead-proxy.md (proxy or
answer any question gate, consume reports into consumed/, diagnose
liveness on a timeout) -- with one critical override:
name: stuck-> the assembly script refused for one of §5's reasons. Surface the quoted stderr to the user plainly and stop (or fix the input -- e.g. re-resolveBASE_REFper §2 -- and relaunch). Do not publish anything.name: done-> do NOT mergemngr/<slug>(that islead-proxy.md's defaultdonehandling, and it is exactly the merge the CWD-INVARIANT callout forbids -- the assembled tree diffs against/home/user/workspaceas mass deletions). Instead, resolve$WT:WT="$(git worktree list --porcelain | awk -v b='refs/heads/mngr/<slug>' '$1 == "worktree" { wt = $2 } $1 == "branch" && $2 == b { print wt }')"Cross-check it against the worktree path in the report body (worktrees live under
/home/user/worktrees/<slug>-<uuid>/), then verify the worker's gates yourself -- both greps must print nothing andgit -C "$WT" statusmust be clean:grep -n -- '<!-- FILL-IN (publishing agent)' "$WT/template.md" "$WT/README.md" grep -nEi -- 'minds-placeholder-thumbnail|<script|<foreignObject|on[a-z]+[[:space:]]*=' "$WT/template.svg" ( cd "$WT" && uv run --no-project --with 'pydantic>=2' python \ .agents/skills/publish-template/scripts/validate_template.py . )If either grep hits, message the worker to finish the job (per
lead-proxy.md's gate mechanics) rather than finishing it yourself. Once clean, close the delegation step and proceed to §6 (chat confirmation), §7 (GitHub auth), and §8 (create repo + push), ALL running with cwd =$WT(see the callout above).
Leave the worker itself alone until §10 -- destroying it removes $WT, which
you still need for the push.
4. What the assembly does
build_template.sh (documented below) does the whole mechanical assembly
in the worker's worktree: clean base + overlay + secret scan + the manifest
pair (template.md prose skeleton and template.toml, the latter
carrying forward the lineage of whatever manifest it overrides) + placeholder
thumbnail + regenerated README + a template-specific /welcome written
into the snapshot + boot smoke-check + manifest validation + a single
commit. It communicates purely via its exit code -- 0 on success (the
assembled commit is on mngr/<slug>), non-zero otherwise (see §5). It prints
a summary of what it assembled to stderr. The worker then supplies the two
things the script cannot: the manifest prose and the bespoke thumbnail.
5. Guard rails (the script's non-zero exits)
The worker maps any non-zero exit to a stuck report quoting the script's
stderr. What each exit means, and what you do:
- Secret scan (exit 1). A credential/token rode in on an overlaid path, OR one of the two required scanners (betterleaks / kingfisher) was missing or errored -- the stderr says which. Nothing was committed; for a finding, surface the flagged path (value redacted) and stop. For a missing/broken scanner, the environment is broken (the binaries are baked into the workspace image; if one is missing, the stderr names the command to reinstall both) -- never publish around the gate.
- No-diff guard (exit 3). The resolved include set contributes nothing
beyond
BASE_REF(the assembled tree equals the base tree). Tell the user plainly and do NOT create a repo -- there are no empty template repos. - Boot smoke-check (exit 4). The clean base does not boot at all; abort BEFORE any repo creation. Selected apps having unresolved requirements is expected and does NOT fail the check.
- Manifest validation (exit 6). The generated
template.tomldid not parse, did not satisfy the schema, or disagreed withtemplate.md. On a worker's or the lead's LATER run of the same validator (without--allow-unfinished), this also means an unreplaced FILL-IN block, a still-placeholder thumbnail, a declaredenv.dunit the recipe does not ship, or -- the one that needs the container -- a declared apt package that does not resolve in the pinned snapshot mirror. The stderr lists every problem at once. Fix them and re-run; never publish around it. - Non-template base (exit 5). The
--base-refdoes not resolve to a tree in the repo, or its tree is not a bootable template: it lackspyproject.tomland/orsystem/supervisord.conf(e.g. a parallel subtree root was picked instead of the real seed). Nothing was committed; re-resolveBASE_REFper §2 (its pre-check should have caught this before launch) and relaunch.
Every one of these is a "fix the input and relaunch the worker" situation, never a "publish something smaller instead" situation -- see the "MUST BE BOOTABLE" callout at the top of this skill.
6. Confirm the publish in chat
cwd = $WT for this and every remaining section. The manifest/thumbnail
files referenced below (template.md / .svg) live at $WT's repo
root, not /home/user/workspace's.
Confirmation happens inline in chat -- there is no other confirmation mechanism. Present the proposal to the user ONCE, in plain language:
- the title and description;
- the repo name (defaults to
slug); - the visibility (default: private) -- and, if they choose public, say in the same breath that the template ships under the MIT license, so the licensing consequence is in front of them at the moment they make the choice rather than after;
- what it will install -- the
[environment]declarations fromtemplate.toml, in plain language ("adopting this also installs poppler-utils"), or that it needs nothing beyond the stock environment. An adopter's machine runs this, so the publisher should recognise the list; - a short recap of the published-version modifications that were applied (or that there were none), so the user can verify their requested removals and changes actually happened;
- the permissions and secrets an adopter must grant -- t
…(truncated)