Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 37 additions & 32 deletions .claude/skills/create-squad/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ Do not invent the contract from memory — read these files.
Ask the user what they want, and don't scaffold until you have answers for all of it:

- **The squad** — its purpose, and a kebab-case `name` (globally unique, ≤ 64 chars).
- **Each agent** — `id` (kebab-case), role / `description`, `model` (`haiku`/`sonnet`/`opus`,
string enum), `heartbeat` — a curated subset of [OpenClaw's
`agents.list[].heartbeat`](https://docs.openclaw.ai/gateway/config-agents#agents-defaults-heartbeat).
Only six sub-fields are accepted: `every`, `model`, `lightContext`,
`isolatedSession`, `skipWhenBusy`, `timeoutSeconds`. `every` is an OpenClaw duration
string in units `ms`/`s`/`m`/`h` (e.g. `"30m"`, `"2h"`, `"24h"`, `"0m"` to disable);
named values like `"daily"` are invalid. `heartbeat.model` is the same `haiku`/`sonnet`/`opus`
enum. Keep each agent single-lane and focused.
- **Each agent** — `id` (kebab-case), role / `description`, `model`
(`haiku`/`sonnet`/`opus`, string enum). Keep each agent single-lane and focused.
- **Recurring wakes** — for each agent, ask what schedule(s) it needs to wake on. These
become **crons** in `crons/jobs.json` (per-agent `agent.json#/heartbeat` does **not**
fire for squad sub-agents today, so every recurring wake is cron-driven). Get: each
cron's id + cron expression + timezone + the wake procedure that goes in `payload.text`.
The common pattern is one daily clock-driven cron + one `0 */2 * * *`-style pulse cron
for self-driven check-ins.
- **Skills** — which are squad-wide (every agent gets them) vs agent-specific.
- **Required identities** — external sites the squad needs connected, each with a reason.
- **Required vault secrets** — each `{ key, label, type }`.
Expand All @@ -44,7 +44,6 @@ Ask the user what they want, and don't scaffold until you have answers for all o
`image-generation` / `image_generate` / `image`, `cron`. Anything else is rejected by
the validator. Slack and voice/TTS are intentionally excluded — those are user-facing
channels owned by the co-founder, not by a sub-agent.
- **Crons** — any scheduled jobs, and what each one does.
- **Catalog metadata** — `tags` for the marketplace card (no `token_intensity` — it is
deprecated and Pancake Cloud computes token usage automatically).

Expand All @@ -56,25 +55,30 @@ Copy [`template/`](../../../template/) to `squads/<name>/`, then fill every file
No per-agent runtime config in this file. Delete optional sections the squad doesn't use.
- **`agents/<id>/agent.json`** for every agent — the per-agent runtime config (curated
subset of OpenClaw's `agents.list[]`). Required: `id`, `description`. `model` is a
string from `haiku`/`sonnet`/`opus`. `heartbeat` is an object with up to six allowed
sub-fields: `every`, `model`, `lightContext`, `isolatedSession`, `skipWhenBusy`,
`timeoutSeconds`. `every` is an OpenClaw duration in `ms`/`s`/`m`/`h` (e.g. `"30m"`,
`"2h"`, `"24h"`); plain strings (`"daily"`) and named values are rejected. Pod-level
fields like `prompt`, `target`, `directPolicy`, `session`, `to`, `ackMaxChars` are
rejected. Top-level optional fields: `skills`, `contextInjection`, `bootstrapMaxChars`,
`params`. Unknown fields anywhere are rejected.
- **`agents/<id>/IDENTITY.md`, `SOUL.md`, and `HEARTBEAT.md`** for every agent; add
`agents/<id>/MEMORY.md` if useful. `HEARTBEAT.md` is **required** when `agent.json`
declares a heartbeat — keep it out of `SOUL.md` (behaviour) and `MEMORY.md` (pointer
index).
string from `haiku`/`sonnet`/`opus`. Top-level optional fields: `skills`,
`contextInjection`, `bootstrapMaxChars`, `params`. **`heartbeat` is rejected** —
OpenClaw's agent-level heartbeat does not fire for squad sub-agents, so every recurring
wake goes in `crons/jobs.json` instead. Unknown fields anywhere are rejected.
- **`agents/<id>/IDENTITY.md` and `SOUL.md`** for every agent; add
`agents/<id>/MEMORY.md` if useful. `HEARTBEAT.md` is a **forbidden filename** anywhere
in the bundle — move what would have gone there into the `payload.text` of a cron in
`crons/jobs.json` whose `sessionTarget` is the agent's id.
- **`crons/jobs.json`** — required whenever the agent has any recurring wake (which is
almost always). For the "heartbeat pulse" pattern, add a cron with id `heartbeat-pulse`,
`schedule.expr = "0 */2 * * *"` (or whatever cadence the user wants), `sessionTarget`
pointing at the agent, and the imperative wake procedure embedded in `payload.text`. If
the procedure is long, an alternative is to ship a `heartbeat-pulse` skill under the
agent's `agent.json#/skills` and have the cron's payload say only *"Load the
`heartbeat-pulse` skill and run it end to end."* — either shape is allowed. A cron run
that intentionally produces no output must instruct the agent to reply with the single
literal token `NO_REPLY` (OpenClaw's silent-turn sentinel — never write "do not respond").
- **Every skill file** referenced by `manifest.skills` or `agent.json#/skills`, in
SKILL.md format (frontmatter `name` + `description`, then a procedure written as steps).
- **`SQUAD.md`** — frontmatter is minimal: `tags` (recommended) and optional
`preview_image`. The body is the marketplace catalog's source of truth for per-agent
prose, so describe every agent here in user-facing language.
- **`ONBOARD.md`** — the runnable onboarding script the co-founder executes after deploy.
- Add or delete the optional `crons/jobs.json` and squad-wide `MEMORY.md` depending on
Step 2.
- Add or delete the optional squad-wide `MEMORY.md` depending on Step 2.
- **Strip every `<!-- TODO -->` comment and placeholder** the template ships with. The
validator errors on any unresolved TODO marker outside `template/`.

Expand All @@ -90,18 +94,19 @@ Copy [`template/`](../../../template/) to `squads/<name>/`, then fill every file
`vault_request`, connect identities via `browser_identity_add`, save answers to the agent's
`MEMORY.md`, and create + dispatch a first task. It must fit `estimated_setup_minutes`.
- `MEMORY.md` is a **thin index of pointers**, never a notebook.
- `HEARTBEAT.md` is the **imperative wake procedure** OpenClaw loads on every pulse —
not behaviour (that's `SOUL.md`), not pointers (that's `MEMORY.md`). It must require
the agent to **execute at least one task before closing the session** (no
orient-and-bail), and to write a **digest** to `memory/YYYY-MM-DD.md` before ending
the turn — what was done, what changed, what's still open, the next wake's first
move. `NO_REPLY` is only acceptable when nothing is actionable, with the reason
logged first.
- The **cron payload** is the imperative wake procedure the agent runs — not behaviour
(that's `SOUL.md`), not pointers (that's `MEMORY.md`). It must require the agent to
**execute at least one task before closing the session** (no orient-and-bail), and to
write a **digest** to `memory/YYYY-MM-DD.md` before ending the turn — what was done,
what changed, what's still open, the next wake's first move. `NO_REPLY` is only
acceptable when nothing is actionable, with the reason logged first.
- The **`SQUAD.md` body** is the catalog's per-agent prose surface — describe each
agent in user-facing language there (not in `manifest.json`).
- **Forbidden files**: do not create `AGENTS.md`, `USER.md`, `BOOTSTRAP.md`, or `BOOT.md`
inside the bundle — those are pod-managed by Pancake Cloud. `TOOLS.md` is allowed (it
is bundle-authored documentation).
- **Forbidden files**: do not create `AGENTS.md`, `USER.md`, `BOOTSTRAP.md`, `BOOT.md`,
or `HEARTBEAT.md` inside the bundle. The first four are pod-managed by Pancake Cloud;
`HEARTBEAT.md` is forbidden because OpenClaw's per-agent heartbeat does not fire for
squad sub-agents (move the wake procedure into a cron's `payload.text` instead).
`TOOLS.md` is allowed (it is bundle-authored documentation).
- Crons target **only this squad's own agents**.
- A cron run with nothing to report must reply with the single literal token `NO_REPLY`.

Expand Down
33 changes: 16 additions & 17 deletions .claude/skills/validate-squad/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,17 @@ The validator's checks fall into the following categories:
`manifest.agents` must have a matching `agents/<id>/agent.json` file.
- **`agent.json` schema** (e.g. `agents/<id>/agent.json#/model must be one of: haiku,
sonnet, opus`) — the per-agent config is invalid. Common causes:
- Wrong `model` value (string enum `haiku`/`sonnet`/`opus`) — applies to both
top-level `model` and `heartbeat.model`.
- `heartbeat` written as a plain string instead of the object shape (e.g.
`"heartbeat": "daily"` instead of `"heartbeat": { "every": "24h" }`).
- `heartbeat.every` written as a named value (`"daily"`) instead of an OpenClaw duration
in `ms`/`s`/`m`/`h` (`"30m"`, `"2h"`, `"24h"`, `"0m"`).
- Unknown field on the agent or inside `heartbeat`. Only six heartbeat sub-fields are
accepted (`every`, `model`, `lightContext`, `isolatedSession`, `skipWhenBusy`,
`timeoutSeconds`); pod-level fields like `prompt`, `target`, `directPolicy`,
`session`, `to`, `ackMaxChars` are rejected because they're not authorable from a
bundle.
- Wrong `model` value (string enum `haiku`/`sonnet`/`opus`).
- `heartbeat` declared on the agent — this is now an **unknown field**. OpenClaw's
per-agent heartbeat does not fire for squad sub-agents today; move the wake
procedure into a cron in `crons/jobs.json` whose `sessionTarget` is the agent's id,
and embed the procedure in `payload.text`.
- Other unknown field on the agent (only `id`, `description`, `model`, `skills`,
`contextInjection`, `bootstrapMaxChars`, `params` are accepted).
- `id` not matching the directory name.
- **Referenced-file errors** — a file the manifest or agent.json points to (`SQUAD.md`,
`ONBOARD.md`, a skill, `IDENTITY.md`, `SOUL.md`, `HEARTBEAT.md` when the agent has a
heartbeat) is missing, is a symlink, is not a regular file, or resolves outside the
bundle root.
`ONBOARD.md`, a skill, `IDENTITY.md`, `SOUL.md`) is missing, is a symlink, is not a
regular file, or resolves outside the bundle root.
- **Unknown tool permission** (e.g. `required_tool_permissions[2] "message" is not an
accepted tool key`) — an entry in `manifest.required_tool_permissions` is not in the
canonical Pancake tool list (see [`bundle-reference.md#tool-permissions`](../../../docs/bundle-reference.md#tool-permissions)).
Expand All @@ -57,9 +52,13 @@ The validator's checks fall into the following categories:
- **Targeting errors** (`crons/jobs.json`) — a cron's `sessionTarget` names an agent the
squad does not declare. Squad crons may target only the squad's own agents.
- **Forbidden file** (e.g. `agents/<id>/USER.md forbidden filename`) — the bundle
contains a file named `AGENTS.md`, `USER.md`, `BOOTSTRAP.md`, or `BOOT.md`. Those are
pod-managed by Pancake Cloud and must not appear inside a bundle. Delete the file.
`TOOLS.md` is *allowed* and is not flagged.
contains a file named `AGENTS.md`, `USER.md`, `BOOTSTRAP.md`, `BOOT.md`, or
`HEARTBEAT.md`. The first four are pod-managed by Pancake Cloud; `HEARTBEAT.md` is
forbidden because OpenClaw's per-agent heartbeat does not fire for squad sub-agents
today. **Migration:** move the wake procedure from `HEARTBEAT.md` into the
`payload.text` of a cron in `crons/jobs.json` (with `sessionTarget` set to that agent's
id), then delete the `HEARTBEAT.md` file and the `heartbeat` field from `agent.json`.
For other forbidden files, just delete them. `TOOLS.md` is *allowed* and is not flagged.
- **Deprecated field** (e.g. `SQUAD.md frontmatter has a deprecated 'token_intensity:'
line`) — `token_intensity` has been removed from the contract. Pancake Cloud computes
token usage automatically; delete the line.
Expand Down
11 changes: 6 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ A bundle is a directory with a `manifest.json` (the package descriptor: `name`,
`description`, `author`, `agents` as a string array of ids, plus optional squad-wide
skills, required identities, and vault secrets), a `SQUAD.md` catalog card, an
`ONBOARD.md` onboarding script, and per agent: an `agents/<id>/agent.json` (the per-agent
runtime config — model, heartbeat, agent-specific skills, mirroring OpenClaw's
`agents.list[]`), `agents/<id>/IDENTITY.md`, and `agents/<id>/SOUL.md`. Optionally it
carries `MEMORY.md` seed memory, `skills/` files, `crons/jobs.json`, and a per-agent
`HEARTBEAT.md` (required when `agent.json` declares a heartbeat). Full detail is in
[`docs/bundle-reference.md`](./docs/bundle-reference.md).
runtime config — model, agent-specific skills, mirroring OpenClaw's `agents.list[]`),
`agents/<id>/IDENTITY.md`, and `agents/<id>/SOUL.md`. Optionally it carries `MEMORY.md`
seed memory, `skills/` files, and `crons/jobs.json`. Recurring wakes live in
`crons/jobs.json` payloads — OpenClaw's per-agent heartbeats do not fire for squad
sub-agents today, so `HEARTBEAT.md` is a forbidden filename anywhere in a bundle. Full
detail is in [`docs/bundle-reference.md`](./docs/bundle-reference.md).

## Working in this repo

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ node scripts/validate.mjs squads/<bundle-name> # one bundle
Zero dependencies — just Node. It mirrors marketplace ingestion exactly, so a bundle that
passes here passes ingestion. CI runs it on every push and pull request, alongside
`node scripts/test-validator.mjs` which self-tests the validator against negative
fixtures (forbidden files, wrong heartbeat shape, etc.) — both must pass for a merge.
fixtures (forbidden files including `HEARTBEAT.md`, deprecated fields, unknown
`agent.json` keys, cron-target mismatches, etc.) — both must pass for a merge.

## Publish

Expand Down
36 changes: 1 addition & 35 deletions agent.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://squads.getpancake.ai/agent.schema.json",
"title": "Agent Squad — per-agent config",
"description": "Schema for agents/<id>/agent.json inside a squad bundle. Mirrors the subset of OpenClaw's agents.list[] that squad bundles may declare. See https://docs.openclaw.ai/gateway/config-agents for the canonical OpenClaw spec.",
"description": "Schema for agents/<id>/agent.json inside a squad bundle. Mirrors the subset of OpenClaw's agents.list[] that squad bundles may declare. See https://docs.openclaw.ai/gateway/config-agents for the canonical OpenClaw spec. Note: per-agent `heartbeat` is intentionally absent — OpenClaw's agent-level heartbeat does not fire for squad sub-agents today, so every recurring wake must be driven by a cron in `crons/jobs.json`.",
"type": "object",
"additionalProperties": false,
"required": ["id", "description"],
Expand All @@ -22,40 +22,6 @@
"enum": ["haiku", "sonnet", "opus"],
"description": "Strict enum. Defaults to the pod's agents.defaults when omitted."
},
"heartbeat": {
"type": "object",
"additionalProperties": false,
"description": "OpenClaw heartbeat config — the curated subset of agents.list[].heartbeat that squad authors are allowed to set. Pod-level fields (prompt, target, directPolicy, etc.) are intentionally omitted. Every sub-field below is optional; omitted values inherit from the pod's agents.defaults.heartbeat. When the heartbeat object is present, agents/<id>/HEARTBEAT.md must exist.",
"properties": {
"every": {
"type": "string",
"pattern": "^\\d+(ms|s|m|h)$",
"description": "Duration string in OpenClaw format — units ms / s / m / h. e.g. \"30m\", \"2h\", \"24h\". Set \"0m\" to disable. Named values like \"daily\" are not valid."
},
"model": {
"type": "string",
"enum": ["haiku", "sonnet", "opus"],
"description": "Override the agent-level model just for heartbeat runs. Same strict enum as the top-level model field."
},
"lightContext": {
"type": "boolean",
"description": "When true, the wake bootstrap loads only HEARTBEAT.md from the workspace — useful for cheap, narrow wakes."
},
"isolatedSession": {
"type": "boolean",
"description": "When true, each heartbeat runs in a fresh session with no prior conversation history."
},
"skipWhenBusy": {
"type": "boolean",
"description": "When true, defer the wake while this agent's other lanes (subagents, nested tasks) are still running."
},
"timeoutSeconds": {
"type": "integer",
"minimum": 1,
"description": "Maximum seconds the heartbeat run is allowed before OpenClaw aborts it."
}
}
},
"skills": {
"type": "array",
"items": { "type": "string" },
Expand Down
Loading
Loading