Skip to content

Skills are the vocabulary of the agent router. Each skill is a named capability that an agent declares. The router matches inbound messages to skills and dispatches accordingly.


Skills are declared in workspace/agents/<name>.yaml (in-process agents) or workspace/agents.yaml (external A2A agents). The AgentRuntimePlugin resolves skills from per-agent YAML files first; unknown skills fall through to SkillBrokerPlugin for external dispatch.


Skill routing

Skills are matched in priority order:

  1. Explicit hintpayload.skillHint bypasses keyword matching (set by GitHubPlugin, DiscordPlugin, LinearPlugin)
  2. Keyword match — content scanned against the keyword table in each agent's YAML
  3. Default — falls back to the agent declared as default: true in agents.yaml

Defining skills

Each skill entry in an agent's YAML declares:

yaml
skills:
  - name: my_skill
    description: One-line description used for keyword extraction
    keywords:         # optional — override automatic keyword extraction
      - foo
      - bar
    systemPromptOverride: |   # optional — replace agent's main prompt for this skill
      You are performing a specific structured task...
FieldRequiredPurpose
nameyesSkill identifier — must match skillHint values sent on agent.skill.request
descriptionnoHuman documentation and automatic keyword extraction source
keywordsnoOverride automatic keyword extraction with explicit match terms
systemPromptOverridenoReplace the agent's main systemPrompt for this skill invocation. Use for structured-output skills that need narrow, format-enforcing prompts (e.g. JSON-only output, YAML drafting). Skills without this field use the agent's main prompt.

Adding a new agent

Create workspace/agents/<name>.yaml:

yaml
name: my-agent
role: general                   # orchestrator | qa | devops | content | research | general
model: protolabs/reasoning      # gateway alias; e.g. claude-sonnet-4-6 / claude-opus-4-7 also work
systemPrompt: |
  You are MyAgent. Your job is...
tools:
  - publish_event
  - get_projects
maxTurns: 10
skills:
  - name: my_skill
    description: Does the thing
    keywords:
      - the thing
      - do thing

AgentRuntimePlugin picks up the file on next restart and registers my_skill for routing.

AUTO-GENERATED by bun run docs:tools. Do not edit the table by hand — regenerate after changing the tool registry in src/executor/executors/deep-agent-executor.ts or any workspace/agents/*.yaml tools: list.

52 tools are defined in the DeepAgent tool registry. Each agent's tools: list in workspace/agents/<name>.yaml selects which of these it exposes; a skill may further narrow the set via its own tools:.

ToolDescriptionAgents
ask_humanAsk the caller who invoked you a question and WAIT for their answer. Use when you genuinely need their input or a decision to proceed — a missing detail, an approval, or a choice between options. Returns the caller's answer as a string. They see an input-required prompt and typically answer within seconds. Use sparingly: only when you cannot proceed without it, not for things you can reasonably decide yourself.ava
calendar_event_detailFetch full detail (description, attendees, RSVPs) for a single calendar event.ava
calendar_list_upcomingList upcoming calendar events for the next N days (default 7) on the primary calendar.ava
chat_with_agentMulti-turn conversation with another agent. Reaches ANY registered agent — call list_agents to see the live fleet. Pass contextId+taskId from a prior response to continue; set done=true on your final message. Long-running skills are polled to completion automatically, so the response carries the agent's real output plus taskState.ava, protobot, quinn
clawpatch_reviewRun structural code review via clawpatch. Maps the repo into semantic feature slices, reviews each, and returns structured findings (correctness bugs, security issues, race/concurrency, data-loss, resource leaks, bad error handling, API contract mismatches, missing tests, build hazards, maintainability risks). Use DURING pr_review to fold findings into the QA Audit body's Observations section with severity + file:line cites. Provider choice: - gateway (default) — stateless LLM call to the LiteLLM endpoint. Prompt has file contents pre-inlined. Fast, cheap, good for nearly every PR. - proto — spawns protoCLI as a live ACP agent during review. Agent has tool access: read additional files, run LSP queries, run typecheck/lint. Slower + more tokens but deeper structural read. Use on non-trivial PRs that warrant active investigation (large diffs, suspicious test coverage, novel patterns). Pass the repo and the pr number — the route resolves the PR's head + base SHAs from GitHub, checks out the head, and scopes findings to exactly the features the PR changed vs its base. repo must be a managed project (in the project registry); other repos return a clear error. The response's prFindings array carries the open findings scoped to this PR's changed files — each with severity, category, confidence, file:line evidence, and a recommendation. Fold the highest-impact ones into your review Observations with a CLAWPATCH source tag.quinn
close_github_issueClose an existing GitHub issue on a managed repo. Optionally posts a comment explaining the close. Use reason: 'not_planned' for stale, superseded, or resolved-differently closes. Default is 'completed'.quinn
comment_github_issuePost a comment on an existing GitHub issue.quinn
create_github_issueFile an issue on a managed GitHub repo.ava, quinn
delegate_taskFire-and-forget: dispatch work to an agent without waiting. Reaches ANY registered agent (see list_agents). Returns a correlationId + pollUrl.ava
discord_create_channelCreate a Discord channel. Types: text, voice, category, announcement, forum.protobot
discord_create_webhookCreate a Discord webhook on a channel for external integrations (GitHub, CI, etc.).protobot
discord_delete_channelDelete a Discord channel by ID or name. For categories, by default all child channels are deleted too (set recursive=false to leave them as orphans). Destructive — confirm with the user before invoking unless the user explicitly named the channel/category to delete.protobot
discord_list_channelsList all Discord channels with type and category.protobot
discord_list_membersList Discord server members with roles.protobot
discord_list_webhooksList all Discord webhooks across guild channels.protobot
discord_scan_feedScan the team's research-feed Discord channel for shared links (arxiv, huggingface, github, video, web). Omit channelId to use the configured feed (RESEARCH_FEED_CHANNEL_ID).researcher
discord_sendSend a message to a Discord channel by name or ID.protobot
discord_server_statsDiscord server stats: members, channels, roles, boost level.protobot
get_branch_driftDev vs main divergence.ava, quinn
get_ceremoniesList ceremonies.
get_ci_healthCI pass rates.ava, quinn
get_incidentsOpen incidents.ava, quinn
get_pr_pipelineOpen PRs and CI status.ava, quinn
get_projectsList all projects.ava, quinn
get_recent_activityWhat shipped recently across the fleet — merged PRs + published releases in a trailing window (default 24h).ava
github_trendingSearch GitHub repositories by topic/language sorted by stars or recent activity — find trending AI/ML projects and releases.researcher
gmail_create_draftCreate a Gmail DRAFT reply. The draft lands in the operator's Drafts folder for manual review and send — nothing is sent automatically. If threadId is provided, To/Subject/In-Reply-To are auto-resolved from the thread.ava
gmail_get_threadRead the full message history of a Gmail thread. Returns headers and bodies for every message.ava
gmail_list_unreadList unread Gmail messages in a label (default INBOX). Use when the operator asks 'what's in my inbox?'.ava
gmail_searchSearch Gmail with native Gmail query syntax (from:, subject:, after:, has:attachment, etc.).ava
huggingface_searchSearch HuggingFace Hub for models or datasets (trending / by downloads / likes). Public — no auth.researcher
label_github_issueSet (replace all) labels on an existing GitHub issue. Replaces any existing labels with the provided list.quinn
linear_add_commentPost a comment to a Linear issue.ava
linear_create_issueFile a new Linear issue. Use when the operator asks to create one explicitly.ava
linear_get_issueRead one Linear issue with full description, labels, and comment history. Accepts UUID or identifier (e.g. 'ENG-123').ava
linear_list_issuesList Linear issues with optional filters. Use to answer 'how many open issues?' or 'what's in the ENG backlog?'.ava
linear_list_teamsList all Linear teams (id, key, name).ava
linear_search_issuesFull-text search across Linear issues.ava
list_agentsList the live agent fleet you can reach via chat_with_agent / delegate_task — each registered agent with its type (deep-agent | a2a) and the skills it serves. This registry is the source of truth; call it to discover who's available rather than assuming a fixed roster. Agents with pendingDiscovery=true are configured but not currently reachable (e.g. an A2A host that's offline).ava
manage_cronCRUD scheduled ceremonies (cron jobs). Actions: list, create, update, delete, run (manual fire — ignores schedule). Naming convention: prefix ids with the agent's name (e.g. ava.weekly-rollup) so the dashboard groups by owner. Per-agent ownership is enforced server-side via X-API-Key — agent-scoped keys can only update/delete their own ceremonies; admin keys see all.ava
msg_operatorSend a direct message to the human operator (a Discord DM). Use for genuine escalation only — a dead end after you've exhausted your options, a decision that needs credentials / infrastructure / policy, or cost/risk beyond your authority. Not for routine progress (that's send_update). Match urgency to reality: low (FYI), normal (needs attention this session), high (review soon), urgent (system degraded, act now).ava, quinn
pr_inspectorInspect AND act on GitHub PRs. The repo arg (owner/name) is REQUIRED on every call. Actions: - list_open: list open PRs in a repo - check_ci: CI check states for a PR - coderabbit_threads: unresolved review threads on a PR - diff_summary: first 200 lines of the PR diff - prior_review: recall your OWN most-recent verdict on this PR + whether the head moved since (for incremental re-review) - review_comment: post a COMMENTED review (requires body) - review_approve: post an APPROVED review (body optional) - review_request_changes: post a CHANGES_REQUESTED review (requires body) - close_pr: close a PR (optionally with a leading comment explaining why) - close_pr_as_not_planned: close + mark state_reason=not_planned (use when the PR is stale, superseded, or the underlying request was resolved differently) - reopen_pr: reopen a closed (non-merged) PR Prefer closing a stale/resolved PR directly to filing a 'please close #X' issue — that's the cascade pattern from #556. Always include comment on close_pr / close_pr_as_not_planned so the close has audit context.quinn
publish_eventPublish a bus event.ava, quinn
reactAdd an emoji reaction to the user's triggering message. Use for acknowledgment before a long task (eyes for 'working on it', hourglass for 'slow one', thinking for 'considering'). Don't react on every response — only when you're about to do substantial work.ava, protobot, quinn
report_incidentFile an incident.ava, quinn
research_ingestStore a research item (paper, finding, digest, or model_release) into the searchable knowledge base so it's recallable in future research.researcher
research_searchHybrid (semantic + keyword) search over the research knowledge base — prior papers, findings, digests, and model releases. Use this BEFORE external searches to reuse what's already been gathered.researcher
research_statsCounts of stored research items per kind.researcher
resolve_incidentMark an OPEN incident resolved by id (e.g. INC-019). Only resolve when you can POSITIVELY verify from this run's own tool output that the reported condition has cleared — e.g. an incident says a data API is returning socket errors and that same API just returned successfully for you. Never resolve a domain incident (a red main, a stuck PR) you cannot confirm is fixed. Include a note citing the evidence.
run_ceremonyTrigger a ceremony.ava
searxng_searchSearch via SearXNG with category routing (general, news, science, it). Supports bang syntax (!wp, !scholar, !gh). For deep research, use chat_with_agent with the researcher agent.ava, quinn, researcher
send_updateSend a brief progress update to the user while other tools run. Throttled server-side to 1 per 5s — don't spam, use only for meaningful progress (e.g. 'Quinn is reviewing the PR now', 'Searching the board...'). The user WILL see the final response — this is just for long-running work.ava, protobot, quinn

External A2A agent

  1. Add the agent to workspace/agents.yaml:
yaml
agents:
  - name: my-agent
    url: http://my-agent:PORT/a2a
    apiKeyEnv: MY_AGENT_API_KEY
    skills:
      - my_skill
  1. Restart: docker restart workstacean

SkillBrokerPlugin dispatches via JSON-RPC 2.0. In-process agents take priority for any skill they declare — external A2A is the fallback.


Skill resolution order

  1. AgentRuntimePlugin — checks workspace/agents/*.yaml:
    • Explicit targets[] in the request → first matching agent name
    • skillHint → first agent declaring that skill
    • No match → falls through
  2. SkillBrokerPlugin — checks workspace/agents.yaml:
    • Same resolution order against external A2A agents
    • Timeout: 110s per call

A2A protocol (external agents)

External agent calls use JSON-RPC 2.0 message/send:

json
{
  "jsonrpc": "2.0",
  "id": "<uuid>",
  "method": "message/send",
  "params": {
    "message": { "role": "user", "parts": [{ "kind": "text", "text": "..." }] },
    "contextId": "workstacean-{channel}"
  }
}

Timeout: 120s per agent call. contextId is derived from the message channel so conversation context persists across turns.

protoWorkstacean — a switchboard, not an agent.