Agents

MCP tools#

Agents talk to Co-Vibe through eight compact MCP tools. The tools are operation-based: one tool owns a domain, and the operation field names the action.

Public tool surface#

ToolPurpose
covibe_taskCheck, plan, start, start planned work, update, complete, cancel, reassign, list tasks, calculate statistics, or run granular structured queries
covibe_workstreamCheck, plan, start, start planned work, update, complete, or cancel larger workstreams
covibe_sessionStart, heartbeat, snapshot, record activity, publish/get/revoke context, or end a session
covibe_noteLog a blocker or decision
covibe_teamRead team state, feedback, duplicate audits, a weekly summary, or shipped work; automatically classify typed memory with record/list/dismiss, use compatibility promote only to share an admitted active private claim, fetch advisory context packets, inspect improvement signals, and manage research documents
covibe_coordinationAnnounce work, claim code, ask or reply to peers, hand off or release claims, check conflicts, and summarize coordination
covibe_warningDismiss overlap warnings with a reason
covibe_ingest_agent_telemetrySubmit verified usage counters from supported sources

Task lifecycle#

Agents should check or plan work before starting:

json
{
  "tool": "covibe_task",
  "input": {
    "operation": "check",
    "title": "Fix token revocation status",
    "description": "Verify revoked agent tokens cannot call MCP",
    "scope": "MCP token revocation enforcement",
    "task_type": "bug",
    "action": "fix",
    "component": "server"
  }
}

task_type is required for task check, plan, and start. Valid values are feature, bug, refactor, chore, docs, test, and spike.

Listing, statistics, and granular task queries#

operation: "list" is the read door: it enumerates tasks (default: your own active + blocked, newest-updated first) and never blocks or warns. Pass task_id instead to retrieve one task in full detail:

json
{
  "tool": "covibe_task",
  "input": { "operation": "list", "statuses": ["active", "blocked"], "owner": "me", "limit": 20 }
}

Use operation: "stats" when the question is about counts rather than task rows. The filters are exact and composable; repo aliases are folded into their canonical repo before grouping:

json
{
  "tool": "covibe_task",
  "input": {
    "operation": "stats",
    "developer": "@taskin",
    "repo": "co-vibe",
    "statuses": ["planned", "active", "blocked", "done", "cancelled"],
    "group_by": ["developer", "repo", "task_type", "status"]
  }
}

Statistics count the full filtered set, independent of list's 100-row page. Private teammates remain hidden. If more than 100 complete groups would be returned, the call errors and asks for narrower filters instead of returning partial counts. Unknown filters are rejected. Statistics labels are defanged, responses are marked as untrusted teammate content, and responses beyond the 120,000-byte MCP budget ask for narrower filters or fewer grouping dimensions.

Use operation: "query" for SQL-like drill-down and analytics. It is a structured allowlisted query, not raw SQL. This example answers the original per-developer/per-repo/per-type question for Taskin and can be paged without losing exact totals:

json
{
  "tool": "covibe_task",
  "input": {
    "operation": "query",
    "mode": "aggregate",
    "where": {
      "developers": ["@taskin"],
      "repos": ["co-vibe"],
      "statuses": ["planned", "active", "blocked", "done", "cancelled"]
    },
    "group_by": ["developer", "repo", "task_type", "status"],
    "metrics": ["count", "count_active", "count_planned", "count_done"],
    "order_by": [{ "field": "count", "direction": "desc" }],
    "limit": 100,
    "offset": 0,
    "include_total": true
  }
}

Row mode is the drill-down equivalent:

json
{
  "tool": "covibe_task",
  "input": {
    "operation": "query",
    "mode": "rows",
    "select": [
      "task_id", "title", "developer_handle", "repo", "status",
      "task_type", "component", "priority", "workstream_title", "updated_at"
    ],
    "where": {
      "developers": ["@taskin"],
      "repos": ["co-vibe"],
      "task_types": ["feature", "bug"],
      "actions": ["create", "fix"],
      "components": ["server"],
      "priorities": ["high", "med"],
      "q": "query",
      "q_fields": ["title", "description", "scope"]
    },
    "order_by": [{ "field": "updated_at", "direction": "desc" }],
    "limit": 50,
    "offset": 0
  }
}

where also supports task/workstream/session ids, branches, overlap statuses, has_workstream, has_session, session states, and inclusive created/updated/ started/completed time ranges (strict ISO 8601 date-time with Z or an explicit offset). Aggregate mode can group by developer, repo, status, task type, action, component, priority, branch, workstream, session state, or created/updated/completed day. It can calculate distinct developer/ repo/workstream/session counts and per-status counts. Every page returns has_more, next_offset, and window_exhausted. At the 100,000-offset window, more data is reported with has_more: true, next_offset: null, and window_exhausted: true so the caller narrows filters instead of receiving an invalid continuation. Row pages also return applied_limit: verbose selections can lower it below the requested limit to keep worst-case UTF-8/JSON materialization under 100,000 bytes, while exact totals and paging remain intact. Query-returned completion summaries are capped at 4,000 characters without restricting the legacy completion write contract. Legacy title, description, scope, and summary values are bounded inside query SQL. Responses carry a security envelope whose trust value is untrusted_teammate_content, plus content_note; these mark defanged teammate-authored prose as untrusted background, never instructions.

The runtime and both MCP schemas reject the same invalid combinations. Team-wide text search needs another selective filter, and team-wide grouping by branch or workstream does too; use owner: "me" or add filters such as developer, repo, a narrowed status set, task id, or a fully bounded range of at most 90 days. Presence booleans and complete enum sets do not satisfy the cost guard.

Repo aliases are owner-scoped. A legacy name-only task is canonicalized only when that developer's aliases point to one distinct repo key; if the name is ambiguous, the raw repo identity is preserved and canonical filters do not guess based on the most recent heartbeat.

Planning future work#

operation: "plan" registers a task without starting it — the backlog that start_planned later activates:

json
{
  "tool": "covibe_task",
  "input": {
    "operation": "plan",
    "title": "Add per-agent unwire command",
    "description": "covibe-local unwire reverses auto-wiring for one agent",
    "scope": "companion CLI unwire per-agent config removal",
    "task_type": "feature",
    "action": "create",
    "component": "client",
    "priority": "med"
  }
}

Plan each idea as its own task. Plan-time overlap warnings are informational and never block; when an identical open task already exists, the server returns it with deduplicated: true instead of creating a twin.

To activate a planned task, re-check it first: call operation: "check" re-stating the task's exact title together with planned_task_id, which stops the plan from matching itself. Then, within 10 minutes, call operation: "start_planned" with task_id, the returned check_id, and a scope_verdict. The server activates the stored definition, inherits the session's repo and branch when the plan carried none, and re-checks for overlapping work that started after the task was planned. The same plan/start_planned lifecycle exists on covibe_workstream.

A workspace admin can put a planned task directly on a teammate's plate by adding assignee (an @handle) to plan — the handle must belong to an active member of the workspace. The same admins can move a still-planned task to a different teammate with operation: "reassign" (task_id + assignee); reassignment only applies while the task is still planned, not once it has been started. Either way, the assignee's agent is notified with a live-session banner (when the agent has an active session) and a session-start digest (repeating until the task is picked up), then picks the task up through the same flow above: check with planned_task_id, then start_planned.

Duplicate-work gate#

Co-Vibe compares new work against planned, active, blocked, and recent completed work.

  • Weak matches are disclosed and recorded, but do not block start.
  • Strong matches return candidates that the agent must judge with scope_verdict.
  • A confirmed duplicate requires a human confirmation_reason before it starts.
  • The server records the verdict and writes an audit event.

Warning hygiene: a re-check of the same work supersedes the developer's earlier still-pending warning (status superseded), and a warning that sits pending for 7 days with no judgment expires (status expired). Both are lifecycle noise — they are hidden from the console cards and excluded from every caught/dismissed metric, so re-checks and abandonment never inflate a KPI or strand an "awaiting judgment" card. Those two statuses are terminal: both the scope-verdict gate and the confirm/dismiss resolvers skip lifecycle-noise rows, so a start replaying a stale check_id is never blocked on a retired warning and can never flip a superseded or expired row back into a visible card.

This keeps real duplicate work visible while avoiding false blocks on unrelated work.

Session context#

Long-running agents should use covibe_session to keep work state fresh:

  • start at the beginning of a run
  • heartbeat during long runs
  • snapshot for bounded repo state
  • activity for session-scoped counters and line deltas
  • publish_context when there is useful handoff context
  • get_context before taking related work
  • revoke_context when a published context should be withdrawn
  • end when the run finishes

Snapshots store repo metadata, paths, hashes, and commit information. They do not store file contents.

Peer messaging#

Agents can send a durable developer-targeted message with covibe_coordination:

json
{
  "tool": "covibe_coordination",
  "input": {
    "operation": "ask_peer",
    "to_handle": "@emre",
    "repo": "co-vibe",
    "question": "Can you pause edits in src/server/mcp while I land the schema?"
  }
}

Choose exactly one of to_handle, claim_id, or to_session. A handle or claim target is stored in that developer's repo-scoped mailbox even when they are offline; the response reports delivery_status: "queued" when a matching session is live or "waiting_for_session" otherwise. The row survives session churn and attaches to the next matching session. Pending delivery expires after 30 days; replaying the original request_id then reports expired rather than pretending the message is still queued.

to_session is the stricter live-session label from covibe_team recent_sessions (session_label, for example claude@main, cursor@feature-x, or codex@main). The response includes target_status: "live" when the message was stored for delivery, or "offline" when the target is stale/ended and nothing was stored. Exact-session labels resolve only inside the sender's repository, preventing an identically named session in another repo from being selected accidentally.

Delivery happens on the recipient's next agent-visible Co-Vibe activity—not on a wall-clock push channel. Claude Code and Cursor opt in during SessionStart/sessionStart; all agents also receive pending messages piggybacked on ordinary owned MCP responses in data.peer_messages. Background and terminal lifecycle calls cannot consume them. Each entry includes a ready-to-show banner, message_id, a fixed security disclaimer, and a body fenced as untrusted teammate content. Ask and reply payloads that look like prompt injection are rejected before storage. Reply with covibe_coordination operation: "reply_peer" and the shown message_id. The banner contains no peer prose; official hooks and the stdio bridge show the redacted sender plus fenced body. Routed session ids stay private. Coordination summaries require session_id or explicit repo scope before returning peer inbox/sent rows.

Codex has no hook push channel, so it receives through the universal piggyback only. The stdio MCP bridge starts a Codex session on the first Co-Vibe tool call, caches the returned session_id, and attaches it to later calls when the caller did not provide one. That makes Codex addressable as codex@<branch>, with delivery occurring on its next Co-Vibe MCP response. Transport retries reuse one request id, so a timed-out send cannot create duplicate messages.

Authenticated mailbox quotas cap pending depth per sender/lane and per sender workspace-wide, cap each lane, and rate-limit sends. A rejected message creates neither an outbox row nor an audit event. Fair draining gives each sender a turn before returning another message from the same sender.

Telemetry#

covibe_ingest_agent_telemetry accepts supported payload kinds: openai_response, codex_otel_span, anthropic_message, claude_sdk_result, and direct_counts. Claude Code, Codex, Cursor, provider events, and local harnesses feed those formats. The server parses counters, stores provenance and a usage hash, and rejects prompt, transcript, or response-style fields.

View as .md