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
27 changes: 17 additions & 10 deletions plugins/brains/skills/brains-agenda/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ description: How to answer schedule / plan / agenda asks — "what's my day / we
1. `list_calendar_events start=<ISO> end=<ISO> limit=100` — **use this, NOT
`list_pages type=calendar_event`**, which filters by ingest time not event
time. Returns events sorted by start with attendees/location/RSVP parsed.
2. `get_overnight_digest` (no args = latest personal digest) — optional context.
2. The latest daily brief — optional context. There is no digest tool; the brief
is a board row written by the My Day recipe. `list_boards` → find the board
named **"Daily digests"** → `get_board board_id=<id> dataset="daily"` and take

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 get_board gives no ordering guarantee and takes no sort argument; it defaults to offset: 0, limit: 100. "take the newest row" therefore reads a stale row once the daily dataset passes 100 rows, which it will — the My Day recipe appends one per day. Page to the end via offset using the returned page.total, or pass filter on the dataset's date field for the window.

the newest row. The dataset name matters: that board also carries `weather`,
`important_emails`, `telegram_log` and `unanswered_emails` from the other
starter-pack automations, and only `daily` holds the brief. Skip silently if
the user doesn't have that board.

If `list_calendar_events` returns 0 for the window, the ingestor may be behind:
`fetch_from_integration source=calendar request="events from <start> through
Expand All @@ -40,17 +46,18 @@ with ⚠️ and note declined RSVPs, but never silently drop.
<events grouped by day: time · title · location/attendees · RSVP status>

### Last night's read on things
<the 'tomorrow' lens for daily asks; the 'thoughts' lens for weekly asks>
_(from overnight digest dated <local day>)_
<the brief's narrative bullets>
_(from the daily brief dated <local day>)_

### Decisions / open loops
<daily asks only: the '→ Action:' lines from the digest's 'connections' lens,
one bullet each, verbatim. Omit the rest of the connections lens — the action
lines are the load-bearing part.>
<daily asks only: the brief's action / follow-up lines, one bullet each,
verbatim. Omit the surrounding narrative — the action lines are the
load-bearing part.>
```

If `get_overnight_digest` returns `{found:false}`, omit **both** "Last night's read"
and "Decisions / open loops" silently — don't say "no digest available."
If there's no "Daily digests" board or no row for the window, omit **both** "Last
night's read" and "Decisions / open loops" silently — don't say "no digest
available."

If the connections lens is `(no connections worth surfacing this window)` or has no
`→ Action:` lines, omit "Decisions / open loops" silently — same rule.
If the brief has no action / follow-up lines, omit "Decisions / open loops"
silently — same rule.
21 changes: 17 additions & 4 deletions plugins/brains/skills/brains-build/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: brains-build
description: The catalog of higher-level brains features beyond search — boards, board skills, dataset recipes, dashboards, pages, automations, workflows, mini-sites, the Telegram bot, and the overnight digest. Use when the user wants to track a list, build a tracker/CRM, automate a recurring task, coordinate a goal with a team, publish a deck/one-pager, or asks "what can brains do." Names the right feature and the flow tool to start it.
description: The catalog of higher-level brains features beyond search — boards, board skills, dataset recipes, dashboards, pages, automations, workflows, mini-sites, board forms, the Telegram bot, and the daily digest. Use when the user wants to track a list, build a tracker/CRM, automate a recurring task, coordinate a goal with a team, publish a deck/one-pager, or asks "what can brains do." Names the right feature and the flow tool to start it.
---

# brains feature catalog
Expand All @@ -13,15 +13,16 @@ tools carry it.
| Feature | What it is | Reach for it when… | Start with |
|---|---|---|---|
| **Board** | Spreadsheet-like dataset on a brain; rows are JSON, shared & queryable | "track a list of…", "keep a table/CRM/pipeline of…", "log every X" | `create_board_flow` |
| **Board skill** | Named saved LLM action over a board's rows | "every row, do X", "summarize/score/enrich each row", repeated lookup | `create_board_skill` |
| **Board skill** | Named saved LLM action over a board's rows | "every row, do X", "summarize/score/enrich each row", repeated lookup | `create_board_skill`; run a saved one with `run_board_skill` |
| **Automation** (feed board) | An automation that fetches from email/cal/drive on a cron and appends rows | "run this nightly and feed a board", "keep this list fresh from email/cal/drive" | `create_automation_flow` |
| **Dashboard** | Live HTML view over a board (stats / charts / table / kanban / tabs …) | "make me a dashboard", "show this as cards/kanban/chart", "change the dashboard" | `get_dashboard` → follow the returned `skill` → `set_dashboard` (`engine_version: "v2"`) |
| **Page** | First-class note in a brain | "save this as a note", "keep this writeup findable" | `create_page` |
| **Automation** | Sandboxed TS program on a cron schedule with a scoped token | "every morning do X", "auto-draft Y when Z", anything re-prompted on a timer | `create_automation_flow` |
| **Workflow** | Goal container: charter + KPIs + deadlines + roster + owned board + paused template automations; one status flip pauses/un-pauses all | "ship X by Q3", "coordinate this initiative with a team & deadline" | `create_workflow_flow` |
| **Mini-site** | Static sandboxed HTML on a brain — deck, one-pager, dashboard | "build me a deck/one-pager", "render this board visually", "share a link" | `create_mini_site` |
| **Board form** | Chat-with-agent intake page that appends a row to a board dataset on submit | "collect responses", "give people a form/signup link" | `create_board_form` (then `list_board_forms` / `update_board_form` / `list_board_form_responses` / `delete_board_form`) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 create_board_form defaults to status: 'draft', which is owner-only preview and accepts no submissions — so following this row for "give people a form/signup link" hands out a form_url nobody can submit to. State that the form must be created or updated with status: 'open' before the link goes out.

| **Telegram bot** | The brain over Telegram | "text it from my phone", "ping me without the laptop" | (point to the bot) |
| **Overnight digest** | Auto daily/weekly read on what's coming up | "morning brief", "what should I be thinking about" | `get_overnight_digest` |
| **Daily digest** | Not a tool — a recipe-installed automation that writes a "Daily digests" board + dashboard | "morning brief", "what should I be thinking about" | `install_recipe slug="starter-pack-my-day"` (browse with `list_recipes`) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 install_recipe requires brain_id alongside slug; the call shown here omits it, so an agent whose user has more than one brain either errors out or picks one at random. Show install_recipe slug="starter-pack-my-day" brain_id=<uuid> and resolve the target with list_brains first.


## Picking among the three that overlap

Expand All @@ -33,6 +34,18 @@ tools carry it.
goal-shape is present.)

Always use the **`*_flow`** tool for create/setup/scaffold asks — using
`create_board` / `create_automation` / `create_workflow` directly skips the
`create_board` / `save_automation_draft` / `create_workflow` directly skips the
playbook and yields a structure that doesn't fit. Use the bare tools only when
reproducing an existing entity verbatim or when the playbook tells you to.
(`update_automation` edits an existing automation in place — that one is fine
without a flow.)

## Once it exists — the tools around a board

| Need | Tools |
|---|---|
| **Share it** | `share_board` / `share_mini_site` / `share_folder` — `scope:'user'` (default, one email) or `scope:'org'` (whole workspace, needs `confirm:true`). Name a reusable audience once with `create_share_circle`. |
| **Per-dataset access** | `enable_board_dataset_acl` flips one board to deny-by-default (irreversible, owner-only), then `grant_board_dataset_access` / `revoke_board_dataset_access` / `list_board_dataset_access`; `set_board_dataset_subject` marks the column that identifies a row's subject. |
| **Files & external data** | `upload_board_file` attaches a file (`get_board_file_url` for the link); `create_board_link` binds the board to an adapter-backed external resource (http_json / ics / github / monday). |
| **Undo a bad edit** | Dashboards, mini-sites and board column-schemas are versioned: `list_history` → `get_history_entry` → `revert` (entity: `dashboard` \| `mini_site` \| `board_schema`). The revert is itself snapshotted, so it's reversible too — say so before anyone panics. |
| **Automation credentials** | `automation_secret_set` / `automation_secret_get` / `automation_secret_list` / `automation_secret_delete` — never inline an API key into automation code. |
76 changes: 62 additions & 14 deletions plugins/brains/skills/brains-integrations/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: brains-integrations
description: Managing which integrations the user has — listing what's connected (Gmail, Calendar, Drive, Monday, GitHub, …), and installing / upgrading / uninstalling them via codex recipes and the starter-pack bundle. Use when the user asks "what's connected", "can brains read from X", or wants to set up / add / update / remove an integration.
description: Managing which integrations the user has — listing what's connected (Gmail, Calendar, Drive, Monday, GitHub, …), and installing / upgrading / uninstalling them via codex recipes and the starter-pack bundle. Also covers authoring: publishing your own recipe to the catalog and managing it as a publisher. Use when the user asks "what's connected", "can brains read from X", wants to set up / add / update / remove an integration, or wants to build / publish a recipe of their own.
---

# Integration lifecycle
Expand All @@ -24,23 +24,32 @@ bounce the user to the browser for the common case:
slugs: `gmail-inbox`, `gcalendar`, `gdrive-files`, `github-issues`,
`monday-items`. Idempotent — re-calling for an already-installed recipe
returns the existing install.
- **Web UI — browse catalog:** for "what's available?" send the user to
`/recipes` (integrations, boards, automations, workflows, bundles). There's
no MCP tool to enumerate the catalog, so the web UI is the way to discover
slugs the user doesn't already know.
- **Browse the catalog over MCP:** `list_recipes` enumerates installable
recipes — `tier` is `'catalog'` (curated, default), `'yours'` (the caller's
own), or `'community'` (other users' published ones), with optional
`kind` / `category` / `q` filters. Use it to discover slugs the user doesn't
already know; `recommend_recipes` picks a few with an evidenced reason when
the user has no specific target in mind. `/recipes` in the web UI is the same
catalog with pictures.
- **Web UI — starter pack:** for "set me up" / "install the basics", send the
user to `/recipes/starter-pack` — one click bootstraps the three Google
integrations (gmail-inbox, gcalendar, gdrive-files) atomically. Bundle

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 "Bundle cascades only run through the web UI" is stale in the same way as the drift this branch fixes: install_recipe accepts kind: 'bundle' recipes and auto-installs missing dependencies children-first, so install_recipe slug="starter-pack" brain_id=<uuid> bootstraps the pack in one MCP call. Name that for "set me up" instead of bouncing the user to /recipes/starter-pack.

cascades only run through the web UI.

## Upgrade

Upgrades run through the web UI (there is no MCP upgrade tool). Each
integration page (`/integrations/<install_id>`) shows a "Newer version
available" banner when an upgrade exists. If the install came via the
starter-pack bundle (`source_bundle_slug` is set), the Upgrade button
redirects to upgrading the **bundle** — don't upgrade bundle-sourced
integrations in isolation.
Read version drift from here: `list_installed_versions` returns one row per
install (`slug`, `name`, `kind`, `version`, `state`) — the caller's *actual*
pinned versions, so you never have to guess which My Day / starter-pack they're
on. Compare against the catalog's current version from `list_recipes`.

The upgrade itself still runs through the web UI — there's no MCP upgrade tool.
`install_recipe` is idempotent and dedups rather than re-pinning an existing
install, so it is not an upgrade path. Each integration page
(`/integrations/<install_id>`) shows a "Newer version available" banner when an
upgrade exists. If the install came via the starter-pack bundle
(`source_bundle_slug` is set), the Upgrade button redirects to upgrading the
**bundle** — don't upgrade bundle-sourced integrations in isolation.

## Uninstall / manage

Expand All @@ -50,8 +59,47 @@ user's secrets for that install), `pause_integration` / `resume_integration`
installs with state + cost), and `get_integration_status` (one install's
health). Get the `install_id` from `list_my_integrations`.

The web UI also exposes Uninstall on each entity page with a 7-day recovery
window; from `/recipes/<slug>` it cascades to every entity whose
`source_recipe_slug` matches (and for a bundle, every child). Use
Tune an install without reinstalling it: `set_integration_config` (installer-side
config, e.g. which repos github-issues ingests), `set_integration_overrides`
(tighten per-run / per-month limits — you can only lower the recipe's caps),
and `run_integration_once` for an on-demand test run instead of waiting for
cron. Publishers can flip state across every install of a recipe they own with
`bulk_set_integration_state`.

The whole-recipe cascade is callable over MCP too: `preview_uninstall_recipe
slug=<slug> brain_id=<uuid>` shows the blast radius (entities stamped with the
slug plus deps that would be orphaned) without mutating anything, then
`uninstall_recipe` does it in one transaction — or pass `entity_id` to drop a
single entity. Always preview first for a bundle. The web UI exposes the same
thing per entity page. Everything lands in a 7-day recovery window; use
`list_uninstalled` when the user asks "what did I just uninstall" before they
click Recover.

## Authoring a recipe (plugin-only)

These tools are `pluginOnly` — they exist on this surface and nowhere else, so
if the user wants to *publish* something, this is the only place it can happen.

- **Start with the playbook, not a blank template.** To author an integration
recipe, call **`create_integration_flow`** first — it returns the full

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 create_integration_flow is not on the current MCP surface — it ships in the unlanded ssvlabs/brains#871. This skill's whole purpose is removing tool names that don't exist, and the authoring section reintroduces exactly that: an agent reading this file will call a phantom tool and dead-end, with no merge-order guard here (the caveat lives only in the PR description, which the agent never sees). Gate the authoring section behind #871, or add an inline note that the tool is unavailable until then.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked this against the tree rather than the PR description — the dependency is already satisfied, so no gate or caveat is needed.

origin/stage: apps/mcp/src/tools/integration-flow.ts:352  name: "create_integration_flow"
origin/main:  apps/mcp/src/tools/integration-flow.ts       (present)
registered:   apps/mcp/src/tools/index.ts:13416            for (const t of integrationFlowTools())

ssvlabs/brains#871 merged 2026-07-20T15:06Z, and it has since been promoted past stage — the tool is on main too, so it's live for users on prod, not just staging. That last part is the bit worth having on the record: the plugin ships to whatever a user's deployment is running, so 'exists on stage' wouldn't actually have been sufficient. It exists on both.

Adding an inline "unavailable until #871" note now would be actively wrong, so I've left the authoring section as-is.

While I was in here I re-verified the rest of the branch the same way — extracted every backticked identifier from the three changed SKILL.md files and diffed it against the real tool surface built from apps/mcp/src/tools/** on origin/stage. Everything resolves. The only two that didn't match a literal name: "…" are install_recipe and list_recipes, which are the canonical names produced by the rename layer (tool-aliases.ts:29-30: apply_starter_recipe → install_recipe, list_starter_recipes → list_recipes), so those are correct too.

That sweep did turn up one real gap, now fixed in a3ac903: the agenda skill pointed at the "Daily digests" board but left the dataset as <name> and said "take the newest row". That board carries five datasets (daily, weather, important_emails, telegram_log, unanswered_emails) written by four different starter-pack automations — only daily holds the My Day brief, so an unguided pick could have surfaced a weather strip under "Last night's read on things". Same class of bug as the drift this branch fixes, just in a non-tool identifier.

IntegrationTemplate shape (auth, trigger, source, outputs, allowed_origins,
tool_grants, …) as a playbook to follow. Do NOT hand-roll a template or guess
at its fields; the spec lives in the playbook, not here.
- **Publish:** `publish_recipe` (kind-agnostic — board / automation / workflow /
bundle / integration) or `publish_integration_recipe` (integration-specific).
Both validate the template server-side and write nothing if it's invalid.
`unpublish_recipe` / `unpublish_integration_recipe` pull a recipe from the
catalog (existing installs are untouched — uninstall those separately).
- **Versioning is append-only.** A brand-new slug publishes at v1; re-publishing
a slug you own auto-increments to `max(version)+1`, the old version stays as a
historical row, and existing installs keep their pinned version. There is no
in-place edit — fix a mistake by publishing the next version.
- **`private` defaults to TRUE** for a new recipe: it's visible and installable
only by the author until it's published with `private: false`. Say this out
loud after publishing, or the user will wonder why nobody can see it. A
version bump inherits current visibility unless `private` is passed.
- **Manage what you publish:** `list_publisher_recipes` (your recipes + install
counts by state), `get_publisher_recipe` (one recipe's detail — auth, limits,
recent install errors, publisher-secret *names*), and the publisher vault via
`list_publisher_secrets` / `rotate_publisher_secret` / `delete_publisher_secret`.
You cannot publish under a slug someone else owns.
Loading