Skip to content

Executor plugins

Executors own the provider-specific LLM harness: tool loops, hook injection, session persistence, telemetry normalisation, and the model catalogue. The runner orchestrates which executor to call; it never imports Anthropic/OpenAI SDK code, never reads models.json itself, and never hardcodes model ids.

Scaffold a drop-in with coro plugin init my-llm --kind executor. That writes coro-plugin.json, a PhaseExecutorBase stub, and a starter models.json.

Model catalogue (models.json)

Each executor package ships a versioned models.json next to the package (published in "files"). Updating shipped models is a package bump, not a runner or dashboard change.

{
"idPrefixes": ["claude-"],
"idPatterns": ["^o\\d"],
"extraAliases": {
"planning": "tier:planning",
"mini": "tier:coding"
},
"models": [
{
"id": "claude-opus-5",
"displayName": "Claude Opus 5",
"contextTokens": 1000000,
"tier": "planning",
"isDefault": true,
"supportsThinking": true,
"pricing": {
"inputPerMTokens": 5,
"outputPerMTokens": 25,
"cacheReadPerMTokens": 0.5,
"cacheCreationPerMTokens": 6.25
}
}
]
}

@coro-ai/plugin-sdk owns the schema and helpers:

  • loadExecutorModelCatalogue(path) / parseExecutorModelCatalogue(json)
  • supportsFromCatalogue — exact id, ${id}-… snapshots, idPrefixes, idPatterns
  • defaultAliasesFromCatalogue — tier:* from isDefault plus extraAliases
  • calculateCostFromCatalogue — per-million input / output / cache pricing

Pass the catalogue to PhaseExecutorBase so a drop-in author only implements init and executePhase. Shipped packages: packages/llm-anthropic/models.json and packages/llm-openai/models.json.

Core interface (PhaseExecutorRuntime)

MethodNotes
executePhase(req)Returns an async iterable of PhaseExecutorEvent chunks (assistant deltas, tool proposals, usage metrics)
listModels()Emits ExecutorModelDescriptor rows for dashboards + alias validation
supports(modelId)Guards unknown models before network I/O

Optional:

  • defaultAliases() — Seed settings.llm.aliases (tier:* plus provider-specific keys).
  • calculateCost — Per-model USD from the catalogue (OpenAI); omit when the upstream reports total_cost_usd (Anthropic).
  • classifyPhaseError(err) — Return 'stale-session' or 'recoverable-abort' so the runner can recover without importing an LLM package.
  • mcpServer() — Attach executor-local MCP (uncommon).
  • chat(req) — Plan-mode conversation (POST /intake/stream). Honor sessionState with the same strategy as executePhase: persist sessionId if supportsSessionResume, persist conversationHistory if supportsConversationReplay. messages is the textual fallback. Request also carries optional cwd (stable work root for Claude Code), systemPrompt, model, maxOutputTokens, signal, and read-only tools. Result returns output, usage, toolCalls, and the next sessionState.

PhaseExecutionRequest highlights

Carries HookPolicy (allowedTools, writeRoots, onPreToolUse), resume handles, combined MCP server list, workflow-scoped subagents, and working-directory metadata.

Capability bits (ExecutorCapabilities)

FlagRunner effect
supportsNativeSubagentsLets YAML subagents use the executor’s native Task tooling; otherwise Coro registers run_subagent.
supportsClaudeMdNativeWalkUpWhen true, executor loads .claude/CLAUDE.md hierarchies itself.
supportsNativeFileToolsSuppresses fallback file_* + shell tools if the SDK already exposes them.
supportsSessionResumeClaude-style sessionId persistence.
supportsConversationReplayStateless models replay conversationHistory.
supportsThinkingSurfaces reasoningEffort hints from llm.aliases.
supportsImageInputEnables multimodal blocks.
maxContextTokensHard ceiling enforced before calling upstream APIs.

Exactly one resume strategy should be true for a given plugin build.

Reference packages

PackageRole
@coro-ai/llm-anthropicWraps @anthropic-ai/claude-agent-sdk — native Tasks + CLAUDE.md walk-up. Catalogue in models.json.
@coro-ai/llm-openaiOpenAI Responses / chat-completions style loop with conversation replay. Catalogue in models.json.

Executors must remain workflow-agnostic: no references to campaign, lane, or business guardrails beyond what the runner already encodes in hooks.