Back to Docs

Skill authoring guide

The technical reference for writing skills. What tools the agent has, what triggers exist today, and how to write bodies that map cleanly to both.

New to skills? Start with the skills overview. This guide is the authoritative inventory of what's supported — when in doubt about what a skill can do, this is the source of truth.

How skills work, in one paragraph

A skill is a doc with type = skill. Its body is plain English. When you click Activate, a “setup” agent reads the body and derives a wiring (which event to listen for, what filter to apply). When a matching trigger fires — or you click Run— an “execution” agent runs the body with access to a toolbelt of named tools it can call to read data and take action. Both setup and execution share the same engine; the only difference is which toolbelt they get.

The lifecycle

StateMeaningPrimary action
InertDoc exists. No setup has been run.Activate
DraftSetup produced wirings, but they aren't firing yet.Activate (flip wirings on)
LiveAt least one wiring is active. Triggers fire the skill.Run (ad-hoc)

There's a fourth quasi-state: Live · stale. This appears when you've edited the body since the last Activate. The old wiring keeps firing, but the body has drifted. Click Re-derive wiring to refresh.

Editing the body never automatically re-runs setup. That's deliberate — auto-fire on save thrashed during testing (every keystroke was an LLM call). Re-derive is explicit.

Triggers — what fires a skill today

Every skill needs to know when to fire. The setup agent reads your body and registers one or more triggers. Today, exactly one trigger source is supported.

card.moved

Fires when any card on any board in your workspace changes stage.

Filter fields— the setup agent picks based on your body's wording:

  • toStageName — the name of the stage the card moved TO
  • fromStageName — the name of the stage the card moved FROM
  • boardId — restrict to one board
  • toStageId, fromStageId — restrict to one specific stage by ID

Filters are shallow equality. “When a card moves to Done” → likely { toStageName: "Done" }. “When a card moves from Backlog to In Progress” → likely { fromStageName: "Backlog", toStageName: "In Progress" }.

Not supported as triggers (yet)

  • Cron — “every Monday at 9am” doesn't work yet
  • Webhooks from external systems — no inbound URL minting
  • Other SP events — feedback created, person added, doc edited, comment posted don't emit yet
  • Multi-condition logic across triggers beyond simple shallow-equality filters

If you write a body that relies on an unsupported trigger, the wiring will be empty or partial. You can still use Run Now to fire the skill manually.

Execution toolbelt

When the skill fires, the agent has access to these tools. Names use underscores (not dots) because Anthropic's tool-name regex requires it. All current tools are SP-internal — they read and write data in your workspace.

sp_cards_get

Get a card's full details (title, description, stage, board, linked-docs preview).

Input{ cardId: string }
ReturnsThe card object including stageName, boardName, workspaceId, and a linkedDocs array of { _id, title, type }.
When to useFirst call when the skill is triggered by a card event — gives you everything about what shipped.

sp_cards_list_linked_docs

List docs explicitly linked to a card (via the linking UI). Optional docType narrows to one category.

Input{ cardId: string, docType?: string }
ReturnsArray of { _id, title, type, permission, locked }.
When to useWhen you specifically want LINKED feedback / docs, not search-discovered ones. Common: { cardId, docType: "feedback" }.

sp_docs_list_linked_people

For a given doc, return the people linked to it (e.g. the customers behind a feedback doc).

Input{ docId: string }
ReturnsArray of { _id, name, email, orgId }.
When to useCommon after sp_cards_list_linked_docs to find customers behind each feedback doc.

sp_docs_search

Full-text search across docs in the workspace. Useful when you want docs ABOUT a topic, not just docs explicitly linked.

Input{ query: string, docType?: string, limit?: number }
ReturnsArray of { _id, title, type, snippet (200 chars), createdAt }.
When to useSearch feedback for themes related to a card → iterate hits → call sp_docs_list_linked_people on each. Note: results drift as content changes.

sp_cards_search

Full-text search across all cards in all boards in the workspace.

Input{ query: string, limit?: number }
ReturnsArray of { _id, title, description (truncated), boardId, stageId }.
When to useFollow up with sp_cards_get or sp_cards_list_linked_docs for details.

sp_releases_create

Create a release doc in the workspace. Returns the new release's docId.

Input{ title: string, content: string (markdown) }
Returns{ docId: string }
When to useLead with what the user can now do; brief note on why if linked feedback makes it clear. Keep titles short and human — don't write v1.2.0; write '@ mentions in comments'.

sp_emails_draft

Save an email draft addressed to a specific person. The draft is NOT sent automatically — it goes to drafts for review.

Input{ personId: string, to: string, subject: string, body: string }
Returns{ draftId: string }
When to useCall once per recipient. Get email addresses from sp_docs_list_linked_people output.

sp_run_save_summary

Save a 1-3 sentence summary of what this run accomplished. Call exactly once at the end before ending the turn.

Input{ summary: string }
Returns{ saved: true }
When to useAppears in the skill's run history and helps with debugging.

What you can't do yet

The execution toolbelt does NOT include:

  • Sending emails — drafts only. No sp_emails_send.
  • External APIs — no Gmail, Slack, GitHub, or arbitrary HTTP. Actions stay in SP.
  • Modifying cards — no update, move, or create. (Coming.)
  • People / org CRUD — can't create or update people or orgs from inside a skill.
  • Comments — can't post comments to anything.
  • Anything cross-workspace — skills only see and modify their own workspace.

If you write a skill that asks for any of these, the agent will either ignore the step or improvise around it. The summary should mention what couldn't be done — but don't rely on that.

Example skill bodies

Real shapes that work today.

Example 1 — Release from linked feedback (explicit form)

When a card moves to Done: 1. Read the card's full details (sp_cards_get). 2. Find feedback docs linked to the card (sp_cards_list_linked_docs with docType="feedback"). 3. For each feedback doc, find the linked people (sp_docs_list_linked_people). 4. Create a release doc summarizing what shipped (sp_releases_create) — lead with what users can now do. 5. For each unique customer, draft a personal email (sp_emails_draft) — short, address them by name, reference their feedback if appropriate. 6. Save a one-sentence summary (sp_run_save_summary).

The agent follows this almost verbatim. Use it when you want predictable behavior.

Example 2 — Release + search-based feedback discovery

When a card moves to Done, write a release and notify the customers whose feedback shaped it. To find the right customers, don't just look at linked feedback — also SEARCH recent feedback for content related to the card's title and description. Combine linked-feedback customers and search-result customers, dedupe, then draft each one a personal email about the release.

Less rigid than Example 1, but explicitly tells the agent to use both link-traversal AND search.

Example 3 — On-demand customer outreach (Run-Now only)

For the most recent 5 feedback docs in this workspace: - Find the customer who left each (sp_docs_list_linked_people). - Draft each customer a short thank-you email acknowledging their feedback and asking if they'd be open to a 15-minute call this week. After drafting, save a summary listing who got emails.

No trigger — useful only via Run Now. Activate is a no-op. Good pattern for one-off outreach.

Best practices

Be explicit about which tools to use

Vague: “find feedback related to this card.” Better: “search feedback for themes related to the card's title.” The first might be interpreted as “LINKED feedback.” The second points the agent at sp_docs_search. Tool ambiguity is the most common reason skills don't do what you expected.

Order your steps

The agent generally follows the order you write things in. If you put “save summary” before “draft emails,” the agent may save the summary too early. End with sp_run_save_summary.

Keep the scope bounded

The execution loop has a max-turn safety limit (currently 12 turns). Skills that fan out to dozens of items can exceed it. Narrow the scope (“most recent 5”) or split into multiple skills.

Don't promise unsupported features

If you ask for “send emails directly” or “post to Slack,” the agent might try, fail silently, and you'll wonder why nothing happened. Stick to what's documented above.

Use specific verbs

Verbs the agent recognizes well: search, read, find linked, list, create, draft, save. Verbs that may not behave as expected: notify (draft email? post comment?), update (no update tools), publish(the agent can't publish for you).

Iterate via Run Now

The fastest dev loop is: write body → click Run Now → inspect transcript → tweak body → repeat. You don't need to Activate while iterating. Once it's right, Activate to start listening on real triggers.

How to invoke a skill

  1. Activate + trigger — set up the wiring, wait for the event (today: card.moved). The typical autonomous pattern.
  2. Run Now button — execute once immediately. Empty payload. Good for testing and on-demand skills.
  3. External agent execution — Claude Code (or any agent with the SP MCP) can read the skill body via get_doc and execute its instructions directly using SP MCP tools. Coming: a dedicated sp.skills.start_run MCP tool that runs through SP's engine with full run-history tracking.

Stuck? Confused? Hit something undocumented?

The skills system is new. If your skill does something surprising, the transcript on the run history (/app/<slug>/skills/runs) is the best debugging surface — it shows every tool call, every argument, every result. If something documented here doesn't work in practice, that's a bug we'd want to know about.