agent-swarm
Install, self-host, deploy, operate, or use agent-swarm. Covers Docker Compose, Kubernetes,…
Build and iterate on schema-backed Swarm Apps with live models, named queries, custom actions, and json-render interfaces using the app-upsert, app-get, app-list, and app-patch tools.
$ npx -y skills add desplega-ai/agent-swarm --skill apps --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/appsContext preview
The summary Claude sees to decide when to auto-load this skill.
Build and iterate on schema-backed Swarm Apps with live models, named queries, custom actions, and json-render interfaces using the app-upsert, app-get, app-list, and app-patch tools.
name: apps description: Build and iterate on schema-backed Swarm Apps with live models, named queries, custom actions, and json-render interfaces using the app-upsert, app-get, app-list, and app-patch tools.
Swarm Apps are persistent, agent-authored internal applications: a schema-backed row store, named queries, custom script/task actions, and a validated json-render interface served at `/apps/<id>`. Use an app when people need to view and change live records; use `create_page` for a static snapshot.
> **Capability gate**: app tools are available when the server's `CAPABILITIES` includes `pages` (for example `CAPABILITIES=core,task-pool,pages`). There is no separate `apps` capability. If these tools are absent from your MCP list, this is why.
| Tool | Use it for | |---|---| | `app-list` | Discover apps by id, name, description, and timestamps. Definitions are omitted. | | `app-get` | Read one complete stored definition before changing it. | | `app-upsert` | Create an app, or replace an existing app's entire definition when you intentionally have the full desired state. | | `app-patch` | Make a focused change while preserving everything not mentioned in the patch. Prefer this for iteration. | | `app-query` | Run one declared named query and return its rows. Use this when an agent or saved script needs to consume app data rather than render the app UI. | | `app-history` | List definition snapshots and their compact model/column digests before choosing a restore point. | | `app-diff` | Show a unified definition diff; defaults to newest snapshot → CURRENT. | | `app-rollback` | Restore a snapshot through the normal schema-migration engine. |
For every edit: `app-get` -> modify the smallest coherent subtree -> `app-patch` -> if rejected, fix every returned `issues[]` entry (`path` + `message`) and retry. Validation happens before storage, so a rejected update leaves the saved app unchanged. Do not guess at the current definition or use `app-upsert` with a partial definition.
`app-patch` can also change `name`; omit `description` to preserve it or pass `description: null` to clear it.
One `app-get` returns everything needed to drive an app you did not write: `definition.queries` are runnable with `app-query` (supply every `$param` the query references), and `definition.actions` are invocable with `POST /api/apps/<id>/actions/<name>` (a `script` action runs its script, a `task` action spawns a task, a `sync` action refreshes sources). Read a query's `$param` placeholders and an action's kind/args from the definition before calling — do not guess parameter names.
Prefer **hiding** a column over deleting it. A hidden column's values stay on every existing row, so an unhide or rollback can make it visible again. Hiding is metadata-only: hidden columns cannot be used in queries, page bindings, `app.mutate` values, or new row writes, and `required` is ignored while hidden. Row GETs still expose the historical value for backward compatibility.
Do not reuse a hidden column name. The name remains held until you either unhide it or explicitly purge its stored values. To permanently remove a column, delete its declaration with `null` and include `{ "columnName": { "purge": true } }` in the same `migration` object. That is deliberately one-shot and irreversible for row data.
Schema-changing writes can carry this sibling `migration` object:
{
"migration": {
"priority": { "from": "flag", "map": { "urgent": "high", "watch": "low" }, "else": "none" },
"status": { "from": "flag", "map": { "watch": "watching", "done": "done" }, "else": "open" },
"oldColumn": { "purge": true },
"count": { "coerce": true, "else": 0 },
"owner": { "set": "unassigned" }
}
}Use `set` to backfill a changed column, `from` with optional `map`/`else` to derive it from one existing (including hidden) column, `coerce` for a kind change, and `purge` only for explicit destruction. Non-purge directives target a column changed in that same write. The server fails loudly before writing when a lossy change needs a directive: inspect every returned `issues[]` item, including its per-value row counts, then choose an explicit policy and retry. Never guess a migration just to get a 200.
**Source lifecycle** rides the same engine with the same all-or-nothing guarantees. Free: adding a source, adding a NEW column with a `source` binding, binding an existing column that holds zero values, changing a binding's `field`/`transform`, and changing a source's `args`/`config`/`connection`/`scriptId` (window changes churn staleness on the next pass — expect `stale` flips when narrowing). Rejected with a path-bearing issue: changing `joinKey` (immutable — remove the source and add it again), changing `connector` while the source owns rows (row count in the message), and binding an existing column that already holds values (purge it — or add a fresh column — and bind that instead; a sync pass would otherwise overwrite the data). Removing a source **detaches** its rows: values survive as ordinary columns, the `source`/`syncedAt`/`stale` envelope is stripped, and the report counts them in `detachedRows`. A patch removing a source must drop its column bindings in the same call — a dangling `source.of` is rejected before anything is written.
Sync never adopts rows it does not own: a pulled record whose join key matches an unowned row creates a second, source-owned row. Adding a source over a join-key column that already holds values therefore duplicates those records on the first pass — and remove-source → re-add duplicates for the same reason, because detached rows keep their values. To convert a hand-maintained model into a synced one, purge the old rows first or use a fresh join-key column.
For example, split a legacy `flag` into `priority` and `status` without losing data in one patch:
Repo: desplega-ai/agent-swarm
Install, self-host, deploy, operate, or use agent-swarm. Covers Docker Compose, Kubernetes,…
Rebuild the feel of a reference motion-design clip (camera, depth of field, easing, captions,…
Reverse-engineer a short motion-design / product-update video (animation technique, timing…
Make a design-led motion-graphics video (showreel, eval comparison, feature or bug-hunt…
How to generate repo-committed video content (README hero videos, case-study clips, landing…
Read the task-digest extension's output. Use when someone asks how many tasks completed or…