Runtimes¶
Cognitia supports four runtimes. All implement the same AgentRuntime Protocol, so you can switch between them without changing business logic.
For API keys, provider environment variables, and base_url patterns, see the canonical reference:
Comparison¶
| Feature | Claude SDK | ThinRuntime | CLI Runtime | DeepAgents |
|---|---|---|---|---|
| LLM | Claude (via SDK subprocess) | Anthropic + OpenAI-compatible + Google | External CLI with NDJSON stream | Anthropic baseline; OpenAI/Google via provider package |
| MCP | Native support | Built-in MCP client | No portable guarantee | Not in portable baseline |
| Sandbox | Native Read/Write/Bash | Via SandboxProvider | Depends on wrapped CLI | Via SandboxProvider |
| Planning | Native plan mode | ThinPlannerMode | Depends on wrapped CLI | DeepAgentsPlannerMode |
| Subagents | Native Task tool | asyncio.Task | No portable guarantee | Native task / LangGraph |
| Team mode | ClaudeTeamOrchestrator | (backlog) | No portable guarantee | DeepAgentsTeamOrchestrator |
| Extras | cognitia[claude] | cognitia[thin] | N/A | cognitia[deepagents] |
| Offline | No | Yes (via local/proxy base_url) | Depends on wrapped CLI | Depends on provider/local endpoint; not guaranteed |
Portable Matrix (current coverage)¶
- The offline portable baseline is confirmed by the integration matrix for
claude_sdkanddeepagents: Agent.query()Agent.stream()Conversation.say()deepagentsnative built-ins and store/resume surface are covered by separate offline graph tests, outside the portable matrix.thinremains alighttier runtime and is not a target for full parity withclaude_sdk/deepagents.cliis a light-tier subprocess NDJSON runtime for external CLI agents; MCP/subagents parity is not guaranteed.- Provider-specific risks confirmed by live smoke tests:
Gemini + DeepAgents built-inson tool-heavy prompts remains an unstable provider-specific path. For minimal migration cost, usefeature_mode="portable".
Claude SDK Runtime¶
Wraps the Claude Agent SDK subprocess. Provides native support for MCP, tools, and subagents.
from cognitia.runtime import RuntimeConfig
config = RuntimeConfig(runtime_name="claude_sdk", model="claude-sonnet-4-20250514")
Claude SDK -- when to use¶
- You need full integration with the Claude ecosystem
- You need native MCP servers
- You need subagents via the Task tool
Claude SDK -- how it works¶
- The SDK manages a subprocess; Cognitia normalizes events into
RuntimeEvent permission_modeis configurable; default isbypassPermissionsallowed_system_toolswhitelist enables native Read/Write for sandbox operations
Claude SDK -- capabilities¶
Tier: full. Supports: mcp, resume, interrupt.
ThinRuntime¶
Cognitia's own lightweight agent loop. Direct API calls without subprocess overhead.
from cognitia.runtime import RuntimeConfig
config = RuntimeConfig(runtime_name="thin", model="claude-sonnet-4-20250514")
ThinRuntime -- when to use¶
- You need maximum control over agent behavior
- You need multi-provider API access without additional provider packages
- Simple projects that do not require MCP
ThinRuntime -- modes¶
ThinRuntime automatically selects a mode based on keyword heuristics, or you can set mode_hint explicitly:
conversational-- single LLM call, no tool usereact-- ReAct loop (LLM call -> tool calls -> results -> next iteration)planner-- plan-then-execute (generate plan JSON -> execute steps -> assemble final answer)
ThinRuntime -- how it works¶
cognitia[thin]extra includes Anthropic, OpenAI-compatible, and Google SDK paths- Built-in MCP client (STDIO transport)
ToolExecutorhandles local/builtin tool invocation- Streaming via
async for event in runtime.run(...) - Supports custom
llm_call,local_tools,mcp_servers, andsandboxinjection - Configurable budgets:
max_iterations,max_tool_calls,max_model_retries
ThinRuntime -- capabilities¶
Tier: light. Supports: mcp, provider_override.
DeepAgents Runtime¶
Integration via native DeepAgents graph path with a portable facade on top.
from cognitia.runtime import RuntimeConfig
config = RuntimeConfig(runtime_name="deepagents", model="claude-sonnet-4-20250514")
DeepAgents -- when to use¶
- You need DeepAgents/LangGraph graphs, built-ins, and store-backed sessions
- Multi-agent workflows
- You need a full-tier runtime but the Claude-specific SDK path is not an option
DeepAgents -- feature modes¶
The feature_mode parameter controls the balance between portability and native features:
"portable"-- offline-tested parity baseline forquery/stream/conversation"hybrid"-- portable core + native built-ins/store seams"native_first"-- native built-ins and graph semantics as the primary path
DeepAgents -- how it works¶
- Baseline extra
cognitia[deepagents]covers the runtime + Anthropic-ready provider path - OpenAI and Google provider paths require separate bridge packages
- Native metadata and resume surface are exposed to the application explicitly
- Native built-ins require an explicit
native_config["backend"]
DeepAgents -- capabilities¶
Tier: full. Supports: resume, native_subagents, builtin_todo, provider_override.
CLI Runtime¶
Subprocess-based runtime for external CLI agents. Runs an external process, feeds it prompt via stdin, and parses NDJSON output from stdout into a RuntimeEvent stream.
from cognitia.runtime import RuntimeConfig
from cognitia.runtime.cli import CliConfig
cli_config = CliConfig(
command=["claude", "--print", "--verbose", "--output-format", "stream-json", "-"],
output_format="stream-json",
timeout_seconds=300.0,
)
config = RuntimeConfig(runtime_name="cli")
CLI -- when to use¶
- Wrapping an external CLI agent (e.g., Claude CLI) as a Cognitia runtime
- You need a lightweight subprocess bridge without full SDK integration
CLI -- how it works¶
- Launches the configured command as a subprocess
- Serializes system prompt and conversation history into stdin
- Parses NDJSON output lines using pluggable parsers (
ClaudeNdjsonParser,GenericNdjsonParser) - Auto-detects Claude CLI commands and normalizes flags for NDJSON contract
CLI -- capabilities¶
Tier: light. No additional capability flags.
Switching Runtimes¶
Runtime selection is configuration-driven -- business code stays the same:
from cognitia.runtime import RuntimeConfig
# Development: ThinRuntime (fast, no subprocess)
config = RuntimeConfig(runtime_name="thin")
# Production: Claude SDK (full integration)
config = RuntimeConfig(runtime_name="claude_sdk")
# Experiments: DeepAgents (LangGraph)
config = RuntimeConfig(runtime_name="deepagents")
# CLI subprocess runtime
config = RuntimeConfig(runtime_name="cli")
Resolution priority: runtime_override > RuntimeConfig.runtime_name > COGNITIA_RUNTIME env var > default (claude_sdk).
AgentRuntime Protocol¶
from collections.abc import AsyncIterator
from typing import Any, Protocol, runtime_checkable
@runtime_checkable
class AgentRuntime(Protocol):
def run(
self,
*,
messages: list[Message],
system_prompt: str,
active_tools: list[ToolSpec],
config: RuntimeConfig | None = None,
mode_hint: str | None = None,
) -> AsyncIterator[RuntimeEvent]: ...
async def cleanup(self) -> None: ...
def cancel(self) -> None: ...
async def __aenter__(self) -> AgentRuntime: ...
async def __aexit__(self, *exc: Any) -> None: ...
Key design principle: the runtime does not own state. It receives messages each turn and returns new_messages in the final event. SessionManager is the source of truth for conversation history.
RuntimeEvent Types¶
| Type | Data | When emitted |
|---|---|---|
assistant_delta | {"text": "..."} | Streaming text fragment |
status | {"text": "..."} | Status update (thinking, tool call in progress) |
tool_call_started | {"name": "...", "correlation_id": "...", "args": {...}} | Tool call begins |
tool_call_finished | {"name": "...", "correlation_id": "...", "ok": bool, "result_summary": "..."} | Tool call completes |
approval_required | {"action_name": "...", "args": {...}, "allowed_decisions": [...], "interrupt_id": "..."} | Human approval needed |
user_input_requested | {"prompt": "...", "interrupt_id": "..."} | Runtime requests human input |
native_notice | {"text": "...", "metadata": {...}} | Runtime-specific semantics notice |
final | {"text": "...", "new_messages": [...], "metrics": {...}} | Turn complete |
error | {"kind": "...", "message": "...", "recoverable": bool} | Error occurred |
Capability Negotiation¶
You can declare required capabilities at configuration time. If the selected runtime does not support them, a ValueError is raised immediately:
from cognitia.runtime import RuntimeConfig, CapabilityRequirements
config = RuntimeConfig(
runtime_name="thin",
required_capabilities=CapabilityRequirements(
tier="full",
flags=("mcp", "resume"),
),
)
# Raises ValueError: Runtime 'thin' does not support required capabilities: tier:full, resume
Available capability flags: mcp, resume, interrupt, native_permissions, user_input, native_subagents, builtin_memory, builtin_todo, builtin_compaction, hitl, project_instructions, provider_override.
Custom Runtimes¶
Register a custom runtime via RuntimeRegistry:
from cognitia.runtime import RuntimeRegistry, get_default_registry, RuntimeCapabilities
def my_factory(config, **kwargs):
return MyCustomRuntime(config)
registry = get_default_registry()
registry.register(
"my_runtime",
factory_fn=my_factory,
capabilities=RuntimeCapabilities(
runtime_name="my_runtime",
tier="light",
supports_mcp=True,
),
)
Third-party runtimes can also be registered via entry points (group cognitia.runtimes).