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,idPatternsdefaultAliasesFromCatalogue—tier:*fromisDefaultplusextraAliasescalculateCostFromCatalogue— 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)
| Method | Notes |
|---|---|
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()— Seedsettings.llm.aliases(tier:*plus provider-specific keys).calculateCost— Per-model USD from the catalogue (OpenAI); omit when the upstream reportstotal_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). HonorsessionStatewith the same strategy asexecutePhase: persistsessionIdifsupportsSessionResume, persistconversationHistoryifsupportsConversationReplay.messagesis the textual fallback. Request also carries optionalcwd(stable work root for Claude Code),systemPrompt,model,maxOutputTokens,signal, and read-onlytools. Result returnsoutput,usage,toolCalls, and the nextsessionState.
PhaseExecutionRequest highlights
Carries HookPolicy (allowedTools, writeRoots, onPreToolUse), resume handles, combined MCP server list, workflow-scoped subagents, and working-directory metadata.
Capability bits (ExecutorCapabilities)
| Flag | Runner effect |
|---|---|
supportsNativeSubagents | Lets YAML subagents use the executor’s native Task tooling; otherwise Coro registers run_subagent. |
supportsClaudeMdNativeWalkUp | When true, executor loads .claude/CLAUDE.md hierarchies itself. |
supportsNativeFileTools | Suppresses fallback file_* + shell tools if the SDK already exposes them. |
supportsSessionResume | Claude-style sessionId persistence. |
supportsConversationReplay | Stateless models replay conversationHistory. |
supportsThinking | Surfaces reasoningEffort hints from llm.aliases. |
supportsImageInput | Enables multimodal blocks. |
maxContextTokens | Hard ceiling enforced before calling upstream APIs. |
Exactly one resume strategy should be true for a given plugin build.
Reference packages
| Package | Role |
|---|---|
@coro-ai/llm-anthropic | Wraps @anthropic-ai/claude-agent-sdk — native Tasks + CLAUDE.md walk-up. Catalogue in models.json. |
@coro-ai/llm-openai | OpenAI 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.