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:
- Explicit hint —
payload.skillHintbypasses keyword matching (set by GitHubPlugin, DiscordPlugin, LinearPlugin) - Keyword match — content scanned against the keyword table in each agent's YAML
- Default — falls back to the agent declared as
default: trueinagents.yaml
Defining skills
Each skill entry in an agent's YAML declares:
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...| Field | Required | Purpose |
|---|---|---|
name | yes | Skill identifier — must match skillHint values sent on agent.skill.request |
description | no | Human documentation and automatic keyword extraction source |
keywords | no | Override automatic keyword extraction with explicit match terms |
systemPromptOverride | no | Replace 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
In-process agent (recommended)
Create workspace/agents/<name>.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 thingAgentRuntimePlugin 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 insrc/executor/executors/deep-agent-executor.tsor anyworkspace/agents/*.yamltools: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:.
| Tool | Description | Agents |
|---|---|---|
ask_human | Ask 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_detail | Fetch full detail (description, attendees, RSVPs) for a single calendar event. | ava |
calendar_list_upcoming | List upcoming calendar events for the next N days (default 7) on the primary calendar. | ava |
chat_with_agent | Multi-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_review | Run 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_issue | Close 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_issue | Post a comment on an existing GitHub issue. | quinn |
create_github_issue | File an issue on a managed GitHub repo. | ava, quinn |
delegate_task | Fire-and-forget: dispatch work to an agent without waiting. Reaches ANY registered agent (see list_agents). Returns a correlationId + pollUrl. | ava |
discord_create_channel | Create a Discord channel. Types: text, voice, category, announcement, forum. | protobot |
discord_create_webhook | Create a Discord webhook on a channel for external integrations (GitHub, CI, etc.). | protobot |
discord_delete_channel | Delete 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_channels | List all Discord channels with type and category. | protobot |
discord_list_members | List Discord server members with roles. | protobot |
discord_list_webhooks | List all Discord webhooks across guild channels. | protobot |
discord_scan_feed | Scan 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_send | Send a message to a Discord channel by name or ID. | protobot |
discord_server_stats | Discord server stats: members, channels, roles, boost level. | protobot |
get_branch_drift | Dev vs main divergence. | ava, quinn |
get_ceremonies | List ceremonies. | — |
get_ci_health | CI pass rates. | ava, quinn |
get_incidents | Open incidents. | ava, quinn |
get_pr_pipeline | Open PRs and CI status. | ava, quinn |
get_projects | List all projects. | ava, quinn |
get_recent_activity | What shipped recently across the fleet — merged PRs + published releases in a trailing window (default 24h). | ava |
github_trending | Search GitHub repositories by topic/language sorted by stars or recent activity — find trending AI/ML projects and releases. | researcher |
gmail_create_draft | Create 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_thread | Read the full message history of a Gmail thread. Returns headers and bodies for every message. | ava |
gmail_list_unread | List unread Gmail messages in a label (default INBOX). Use when the operator asks 'what's in my inbox?'. | ava |
gmail_search | Search Gmail with native Gmail query syntax (from:, subject:, after:, has:attachment, etc.). | ava |
huggingface_search | Search HuggingFace Hub for models or datasets (trending / by downloads / likes). Public — no auth. | researcher |
label_github_issue | Set (replace all) labels on an existing GitHub issue. Replaces any existing labels with the provided list. | quinn |
linear_add_comment | Post a comment to a Linear issue. | ava |
linear_create_issue | File a new Linear issue. Use when the operator asks to create one explicitly. | ava |
linear_get_issue | Read one Linear issue with full description, labels, and comment history. Accepts UUID or identifier (e.g. 'ENG-123'). | ava |
linear_list_issues | List Linear issues with optional filters. Use to answer 'how many open issues?' or 'what's in the ENG backlog?'. | ava |
linear_list_teams | List all Linear teams (id, key, name). | ava |
linear_search_issues | Full-text search across Linear issues. | ava |
list_agents | List 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_cron | CRUD 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_operator | Send 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_inspector | Inspect 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_event | Publish a bus event. | ava, quinn |
react | Add 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_incident | File an incident. | ava, quinn |
research_ingest | Store a research item (paper, finding, digest, or model_release) into the searchable knowledge base so it's recallable in future research. | researcher |
research_search | Hybrid (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_stats | Counts of stored research items per kind. | researcher |
resolve_incident | Mark 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_ceremony | Trigger a ceremony. | ava |
searxng_search | Search 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_update | Send 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
- Add the agent to
workspace/agents.yaml:
agents:
- name: my-agent
url: http://my-agent:PORT/a2a
apiKeyEnv: MY_AGENT_API_KEY
skills:
- my_skill- 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
AgentRuntimePlugin— checksworkspace/agents/*.yaml:- Explicit
targets[]in the request → first matching agent name skillHint→ first agent declaring that skill- No match → falls through
- Explicit
SkillBrokerPlugin— checksworkspace/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:
{
"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.