Recipes
A recipe is a portable snapshot of a guild's configuration: roster, prompts, collection columns and grants, routines, scripts, seed notes, intended grants, the repositories its coding roles work in, and the operator setup those grants require. It is not a clone of a live guild, and it is not runnable.
Three surfaces share guilds.recipe v2: the guild builder authors one from a conversation; capture takes one from a live guild; apply turns one into a live guild and a setup checklist. This page is the format, capture, and apply. Prompt Studio improves guilds that already exist and does not deal in recipes.
Contract
- Format is
guilds.recipev2. Unknown keys are rejected. Caps: 4 MiB body, 20 roles, 256 KiB task/script, 64 notes, 8 KiB setup, 50 list items. - Model-written prompt/setup fields carry
{ by: operator | builder, reviewed }. That is provenance, not a publish gate. Edit changesbyto operator; file import resets destination review state. - Routine keys are
^[a-z][a-z0-9-]{0,62}$. Design metadata never uses array indexes. - Collections are tables:
{ name, purpose, columns, scope, guild, writers }.columnsis the typed column list (name, type, description, required, unique, values), parsed by the same rules as the Databases page (Notes and collections); every collection declares one.scopeisguildfor a table the applied guild owns, where every member reads and writes, ororg(the default) for an organisation table. Aguildscope takesguild: "write"and nowriters, and both are rejected otherwise. On anorgtable,guild(readorwrite, defaultread) is the grant the new guild gets andwriterslists the roles whose agents get their ownwritegrant. Apply creates the table, or reuses a live organisation table whose columns accept the recipe's. - Capture never includes credentials, memory, collection records, browser profiles, repository URLs, or local ids. Export and share are refused while the recipe is draft. Structural validation (then
status=ready) is the gate.
Access is resolved, not stored as a matrix. The document stores effective per-role state (servers, tools, grants, secret names) plus recommended scopes. Binding row ids and connection_ids stay out.
Document shape
Stored in PostgreSQL; downloadable as JSON.
{
format: "guilds.recipe",
version: 2,
meta: { name, description, created_at, source },
guild: { name_suggestion, about, objective, group_agent_message_limit },
roles: [{
role, name_suggestion, focus, wake_triggers?, avatar_*, config,
task: { body, embeds[] },
scripts, schedules,
grants, native_tools,
browser: { required, instructions, author }
}],
collections: [{ name, purpose, columns, scope?: "guild" | "org", guild?: "read" | "write", writers?: [role] }],
notes: [{ id, level, role?, key, body, kind: "seed" }],
connections: [{
server, auth, recommended_scope, recommended_role, hint,
setup, resources?, author,
roles: { [role]: { enabled, tools: { [namespaced]: boolean } } }
}],
secrets: [{ name, description, recommended_scope, recommended_role, roles[] }],
external: [{ kind, title, setup, author }],
repositories?: [{ name, purpose }],
design?: { ... } // builder metadata; optional
}Rules:
- No
connection_id,secret_id, ciphertext, tokens, or workspace paths. config.coding(boolean) makes a role a coding agent.repositorieslists the git repositories the guild's coding roles work in, byname(^[a-z][a-z0-9_-]{0,62}$, unique, the folder under/home/agent/repos) and a one-linepurpose(at most 400 characters); a URL, an owner, or any other key is rejected. Validation errors withcapacity.agents,capacity.browser, orcapacity.codingwhen the roles, the browser roles, or the coding roles exceed what the organisation may still create under its limits (a limit that is not set never fires; core sets none), withcoding.flagwhenconfig.codingis not a boolean, and warns withrepositories.unusedwhen repositories are listed and no role setsconfig.coding.- Optional
connections[].resourcesnames confirmed long-lived external tables (kind,id,title,url?,use?). Not a live row dump. - Notes are flat:
notes[].keyis a.mdfile name with no/, and a note carryingpathis rejected on import and write. task.bodyembed syntax after capture is the destination file (![[guild/<key>]],![[org/<key>]],![[agent/<role>/<key>]]). A leftover![[recipe:<note-id>]]still applies whennotes[].idmatches. Apply rewrites those markers to live display paths and fails if any stay unresolved.- Agent tags in
task, scheduleinstruction, notes, and script source are@{role}until apply.@useris never rewritten. The rewrite uses the group-chat tag grammar, not a string replace. authoris{ by: "operator" | "builder", reviewed }. Provenance only;reviewed: falsedoes not block export, share, or apply.versionmust be 2; any other value is rejected (unsupported recipe version). A collection carryinglevel,role,schema, orkeyis rejected with a hint namingscope,guild,writers,columns, andname.
Always captured: the guild description (stored as guild.objective), notes a task embeds, empty memory on agent create (then overwritten by recipe task). Lived agent notes, org notes, memory bodies, and collection records stay out unless the operator includes them.
Schedules capture cron or window, description, and instruction. Apply inserts them paused. config.browser plus login instructions travel; Chromium profiles do not. config.coding travels; the guild's repositories are captured by name with an empty purpose, and their URLs, branches, and GitHub connections stay with the guild. The GitHub connection is not captured under connections: repositories stands for it.
Capture
POST /api/guilds/:guild_id/recipes writes a recipes row with status=draft and source_kind=guild. Capture rewrites display-path embeds to recipe-local refs and source agent names to @{role}. It writes the guild's own tables as scope: "guild", and every organisation table the guild reaches, through its own grant or through a member agent's own grant, as scope: "org" with guild as the guild's grant (read when only members hold grants) and writers as the roles whose agents override it to write. An agent override of read or none is not carried into a recipe. Secrets come from each role's environment: one entry per name with its description and the roles whose agents get it. recommended_scope is org when the secret is organisation-wide, guild when it is granted to the source guild, and otherwise agent, with the first role holding it as recommended_role. Values are never captured.
The blueprint is a preview. Approve, in the builder or on the recipe's page, moves the row to status=ready when structural validation passes. Any later body write (builder chat, edit, PUT) returns it to draft so apply cannot run mid-construction. POST /api/recipes/:id/review can still edit provenance. Expanding a connector lists catalog tools. The operator can uncheck tools or remove the connector, then save the access (Save access on the recipe's page, Save and tell the coach in the builder). That writes connections[].roles[role].tools[name]=false or drops the connection (recording design.systems as operator-refused). Validation returns access.tool_removed / access.connector_removed so the builder can update prompts or ask why.
Storage
recipes (
id, org_id, created_by_user_id,
name, description, visibility, -- private | org
status, -- draft | ready
body jsonb, body_version int,
source_kind, source_guild_id, source_builder_session_id,
created_at, updated_at
)visibility=org covers restore and intra-org share. File import is the cross-org path (POST /api/recipes/import). Cross-org reads stay 404.
Recipe HTTP
GET /api/recipes
POST /api/guilds/:guild_id/recipes
GET /api/recipes/:id
PUT /api/recipes/:id
POST /api/recipes/:id/review
POST /api/recipes/:id/access
POST /api/recipes/:id/validate
POST /api/recipes/:id/publish
DELETE /api/recipes/:id
GET /api/recipes/:id/export
POST /api/recipes/importExport is 409 while status=draft. PUT sends the expected body_version and takes 409 on a stale head.
Library and recipe page
/recipes is the library, with From a guild to capture one and Import JSON. The guild menu's Save as recipe captures that guild and opens the draft. /recipes/<id> shows the apply plan (everything the recipe creates and needs), each Task with a copy button, Task rewrites, access cuts, Approve, Export on a ready recipe, and Apply, or Approve and apply on a draft. Apply starts at /recipes/<id>/apply.
Apply
Apply turns a guilds.recipe v2 document into a live guild: roster, prompts, collections, scripts, and paused routines, then walks the operator through logins, secret values, browser sessions, and external systems. Builder Deploy uses this same flow. There is no builder-specific credential path.
Contract
- Apply may reuse existing organisation or platform connections, and the organisation's secrets. It creates new connections only at guild or agent scope, and writes overlays only at guild or agent scope after tool listing.
- Apply never writes a secret value and never makes a secret organisation-wide. The operator adds each secret the apply plan creates from the guild's Secrets view.
- Preview resolves names, models, current capacity, and note/secret collisions before writes.
- Skeleton creates the guild, agents, notes, tasks, scripts, schedules (paused at create), and collections with their grants, with compensating cleanup.
- A recipe collection is created when its name is free in the organisation, reused when the live table of that name accepts every recipe column, and a blocking
collection.conflictotherwise; a reused table keeps its records and its other grants.
Apply never writes an org-scope or platform-scope binding. It may reuse an org login; the grant lands on the new guild or one of its agents. Overlays are upserted after Connect, at the resolved connection's owner scope. A namespaced tool the destination does not list is a warning, not a failure.
Destination is a guild apply creates, or an empty guild (no agents). In-place restore onto a live roster is out of scope; live task edits stay in Prompt Studio.
Flow
recipe_applies.status: preview, then skeleton, then checklist, then ready, or aborted.
Preview
POST /api/recipes/:id/preview resolves, without writing:
- guild name in this organisation
- each role's
name_suggestionagainst charset, reserveduser, and names visible here (a global agent-name 409 is handled at insert) - each
config.modelagainstGET /api/models - the organisation's remaining capacity for the whole batch: agents, browser agents, and coding agents (roles with
config.coding: true), each against the organisation's limit,nullwhere nothing caps it; the preview'sremainingcarriesagents,browser_agents, andcoding_agents - catalog presence of each
connections.server - existing destination org connections that can satisfy a server
- each secret name against the organisation's secrets: a name the organisation holds is a collision and defaults to
reuse(the operator mayrenameorskip), a free one iscreate; a recommendedorgscope becomesguild - org-level seed note collisions
- each recipe collection against the organisation's live tables:
createwhen the name is free,reusewhen every recipe column exists there with the same type and, for a closed value list, every recipe value is accepted (extra live columns are fine), otherwiseconflict, a blockingcollection.conflictfinding listing the differences that the operator resolves by renaming the recipe collection - provenance of builder-authored fields (informational; not a gate)
Skeleton
POST /api/recipes/:id/apply requires status=ready and no blocking validation errors. Ordered so no intermediate state is broken:
- assert the whole batch against the organisation's limits once
- create guild (or attach an empty one)
- create guild- and org-level seed notes
- create agents
- create agent-level seed notes, then rewrite destination-file embeds (
guild/…,org/…,agent/<role>/…, leftoverrecipe:<id>) to live display paths. Unresolved links fail the apply. - write each
taskonce, with embeds and@{role}tags already final - insert scripts
- insert schedules paused
- write grant and native-tool bindings at guild or agent scope
- create each
createcollection with the recipe's columns (a table that appeared under that name since preview is a conflict: preview again). Ascope: "guild"collection is created for the new guild, takes no grant, and conflicts when the organisation already holds that name. Anorgcollection is granted the recipe'sguildmode, and eachwritersrole's agentwrite, on every created or reused table - open the checklist
Notes precede tasks because embed hydration throws on a missing target. A retried apply is idempotent per destination guild. Failure deletes a guild apply created (created_guild) and the collections this apply created; reused tables stay. An empty guild the operator already made stays standing.
Once the apply is saved, and when the applying user is an org owner or admin, each reuse secret that is not organisation-wide and does not reach the guild yet is granted to it. Secret steps that then pass close.
Checklist
Each step has a derived id (conn:notion, secret:TAVILY_KEY, repo:api, overlays:wallet, browser:scout, external:…).
A recipe with repositories adds a conn:github step (Connect GitHub) when its connections do not already name github, and one repo:<name> step per repository, whose setup is the repository's purpose and where to add it (the guild's Settings, under Repositories, with that name). The Connect GitHub setup also tells the operator to set Commit author email in the guild's Settings to a verified email on the GitHub account that owns any deploy project. Recipes do not store that email. Agents from roles with config.coding: true are created with coding on.
| Step | Closes when |
|---|---|
| Reuse or Connect server S | connection status=connected (verified), or operator skips with a warning |
Secret NAME | a secret of the destination name reaches every agent of the roles that need it, the recipe secret's roles or else every planned role (verified), or skipped |
| Tool overlays for S | planned mcp_tool bindings match (verified); skipped with the Connect step |
Repository name | the guild has a repository of that name (verified), or skipped |
| Browser for role R | deferred until after activate; the live agent asks the operator to log in |
| External item | operator attests (confirmed) |
Connect, secret, repository, and overlay steps can be skipped (POST …/confirm with skip: true). Skip records a warning: agents that need that login will fail those tasks until it is connected later from Tools. Browser login is not an apply checklist task. After activate, a browser-enabled agent's inspector shows a Browser login needed strip with Open Browser (the agent's Browser tab, where the operator takes control and logs in) and Mark as done; the strip stays hidden while the guild's checklist is open.
POST /api/applies/:id/steps/:step_id/verify re-derives destination state; a secret step that does not pass names the agents the secret does not reach yet, and a repository step says the guild has no repository of that name yet. …/confirm is for browser and external steps, and for skipping a connection, secret, or repository. POST /api/applies/:id/overlays writes planned tool overlays after listing.
Activate
POST /api/applies/:id/activate unpauses the schedules the operator kept, defers any remaining browser logins, and moves the row to ready. Abort deletes a guild apply created and the collections the apply created (reused tables and their records stay); org-level logins created during the checklist stay.
Apply HTTP
POST /api/recipes/:id/preview
POST /api/recipes/:id/apply
GET /api/applies/:id
GET /api/guilds/:guild_id/apply
POST /api/applies/:id/overlays
POST /api/applies/:id/steps/:step_id/verify
POST /api/applies/:id/steps/:step_id/confirm
POST /api/applies/:id/activate
POST /api/applies/:id/abortApply screens
/recipes/<recipe_id>/apply previews the apply: the new guild's name, each agent's name, and for each connector a live login to reuse or one to connect afterwards. /applies/<apply_id> is the checklist of an apply: a connect step opens the same Connect dialog as the Tools view, and connect and secret steps link to the guild's Tools and Secrets; steps are checked, confirmed, or skipped there, then Activate routines or abort. A guild whose checklist is still open shows a strip under its tabs that says the routines stay paused until the checklist is done and links to it.