Skip to content
Productivity
Agent

agent-provider-architecture

This document is a reference for implementing a new **agent provider** in Nimbalyst. It is the architectural counterpart to `docs/AI_PROVIDER_TYPES.md` (which is end-user / product oriented) and walks through every seam a new agent has to fit through: session start and resume,

From plugin
nimbalyst
2.1k3 skills3 agents54 commands3 MCP

Claude Code

> /plugin marketplace add nimbalyst/nimbalyst
> /plugin install nimbalyst-wiki@nimbalyst

Agent definition

agent-provider-architecture.md
planStatus:
  planId: plan-agent-provider-architecture
  title: Agentic Provider Architecture
  status: draft
  planType: system-design
  priority: medium
  owner: ghinkle
  stakeholders:
    - ghinkle
  tags:
    - ai
    - agents
    - architecture
    - reference
  created: "2026-04-25"
  updated: "2026-04-25T00:00:00.000Z"
  progress: 0

Agentic Provider Architecture

This document is a reference for implementing a new **agent provider** in Nimbalyst. It is the architectural counterpart to `docs/AI_PROVIDER_TYPES.md` (which is end-user / product oriented) and walks through every seam a new agent has to fit through: session start and resume, prompt handling, transcript output, tool calling, MCP configuration, and file-edit tracking.

A companion document, [`agent-providers-as-extensions.md`](./agent-providers-as-extensions.md), explores how the same surfaces could one day be exposed to extensions.

1. Two-layer abstraction

Nimbalyst splits an agent into **two stacked interfaces**, not one. New providers fill in both.

| Layer | File | Lifetime | Purpose | | --- | --- | --- | --- | | `AIProvider` | `packages/runtime/src/ai/server/AIProvider.ts:47` | One per `(provider type, sessionId)`, cached in `ProviderFactory` | High-level provider, owns config/system prompt/auth, drives the session, writes raw messages, emits stream chunks | | `AgentProtocol` | `packages/runtime/src/ai/server/protocols/ProtocolInterface.ts:187` | One per provider instance (or shared singleton) | Transport adapter — speaks the SDK / wire protocol of the underlying agent and yields normalized `ProtocolEvent`s |

The split exists so that a provider can stay stable across SDK upgrades and so that protocol adapters are unit-testable without dragging in DB, IPC, or auth. **Chat providers** (`ClaudeProvider`, `OpenAIProvider`, `LMStudioProvider`) skip the protocol layer entirely — they call vendor APIs directly inside `sendMessage`. **Agent providers** (`ClaudeCodeProvider`, `OpenAICodexProvider`, `CopilotCLIProvider`, `OpenCodeProvider`) implement `AIProvider` and delegate transport to a corresponding `AgentProtocol`.

2. The `AgentProtocol` contract

Verbatim from `ProtocolInterface.ts`:

interface AgentProtocol {
  readonly platform: string;
  createSession(options: SessionOptions): Promise<ProtocolSession>;
  resumeSession(sessionId: string, options: SessionOptions): Promise<ProtocolSession>;
  forkSession(sessionId: string, options: SessionOptions): Promise<ProtocolSession>;
  sendMessage(session: ProtocolSession, message: ProtocolMessage): AsyncIterable<ProtocolEvent>;
  abortSession(session: ProtocolSession): void;
  cleanupSession(session: ProtocolSession): void;
}

Inputs:

  • `SessionOptions`: `workspacePath`, `model`, `systemPrompt`, `abortSignal`, `permissionMode`, `mcpServers`, `env`, `allowedTools`, `disallowedTools`, plus a `raw` escape hatch for platform-specific knobs.
  • `ProtocolMessage`: `content` (text), `attachments` (image / pdf), `sessionId` (for logging), `mode` (`'planning' | 'agent'`).

Output is a unified `ProtocolEvent` stream. Event types:

type ProtocolEventType =
  | 'raw_event' | 'text' | 'reasoning'
  | 'tool_call' | 'tool_result'
  | 'error' | 'complete' | 'usage'
  | 'planning_mode_entered' | 'planning_mode_exited';

A new agent's only job at this layer is: **translate its native event stream into this normalized event vocabulary**, and capture the platform-native session ID into `session.id` as soon as the underlying SDK reveals it.

2.1 The four reference adapters

The four shipping protocols deliberately use different transports — together they exercise every transport family we've considered.

| Adapter | File | Transport | Native session unit | Forking | | --- | --- | --- | --- | --- | | `ClaudeSDKProtocol` | `protocols/ClaudeSDKProtocol.ts:48` | In-process function call to `query()` from `@anthropic-ai/claude-agent-sdk` | Session is implicit — created by first `query()`, ID arrives in stream | Native (`forkSession: true`) | | `CodexSDKProtocol` | `protocols/CodexSDKProtocol.ts:44` | In-process SDK from `@openai/codex-sdk` that spawns a native binary subprocess (`asarUnpack`'d in packaged builds) | Thread, `client.startThread()` | Not supported — degrades to new thread | | `CopilotACPProtocol` | `protocols/CopilotACPProtocol.ts:59` | Long-lived subprocess `copilot --acp --stdio` over JSON-RPC framed by readline | ACP `session/new` returns ID | Not supported | | `OpenCodeSDKProtocol` | `protocols/OpenCodeSDKProtocol.ts:44` | Reference-counted subprocess server, communicated with via HTTP + Server-Sent Events | Server-managed `session.create` | Not supported |

The shape is consistent regardless of transport: the adapter wraps process / socket / function-call lifecycle and yields `ProtocolEvent`s.

3. Session lifecycle

3.1 Where session state lives

Two stores, two scopes:

  • **Database (`ai_sessions` table)** — durable. One row per Nimbalyst session. Holds `provider`, `model`, `provider_session_id` (the platform-native ID returned by the protocol), `workspace_id`, `parent_session_id`, `worktree_id`, `mode`, and JSONB `metadata`. See `AISessionsRepository` (`packages/runtime/src/storage/repositories/AISessionsRepository.ts`).
  • **In-memory caches** — non-durable. `ProviderFactory.providers` keyed by `${type}-${sessionId}` (`ProviderFactory.ts:15`). Each protocol may also hold transport state (active subprocess, active queries).

The Nimbalyst `sessionId` is the canonical ID the rest of the system uses (UI, transcript, file tracking). The `provider_session_id` is platform-specific and belongs to the protocol — never assume the formats are interchangeable.

3.2 Starting a new session

1. UI / IPC asks for a session: `SessionHandlers.ts` calls `SessionManager.createSession({...})`. 2. `SessionManager` writes a row to `ai_sessions` and returns a Nimbalyst `sessionId`. 3. On first prompt: `ProviderFactory.createProvider(type, sessionId)`

Read more
@nimbalyst
Ships withnimbalyst

Nimbalyst - The open-source visual workspace for Claude Code, Codex, and OpenCode. Run multiple coding agents in parallel, edit their work visually in markdown, mockups, and diagrams, and track tasks. Free, MIT-licensed desktop app for macOS, Windows, Linux, with mobile companion for iOS and Android.

2.1k16 skills3 agents3 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
2.1k
Star history of nimbalyst/nimbalyst on GitHub, a chart by star-history.com

Other agents on nimbalyst.

e2e-runner

e2e-runner

Agent

Run E2E tests in a dev container for isolated, reproducible test execution. Use proactively when asked to run Playwright tests,…

@nimbalyst@nimbalystInstall Agent

More agents from other plugins.

cto-p10

cto-p10

Agent

P10 CTO/架构委员会 Agent。定义技术战略方向、组织 agent 团队拓扑、建设基础能力。当面对超大型项目(5+ agents, 3+ sprints)、需要战略级架构决策、或需要跨多个 P9 协调时使用。触发词:CTO…

pua2mo20k
@tanweai@tanweaiInstall Agent
verifier
Auto-invoked

verifier

Agent

Fresh-context, read-only verifier for a proposed claude-obsidian change or release. Inspects the requested staged diff, unstaged…

inbox-zero-api-cli

inbox-zero-api-cli

Agent

Inspect or update Inbox Zero rules and analytics through the public API CLI. Use when tasks involve rules, stats, or API-driven…

@elie222@elie222Install Agent

Questions about agent-provider-architecture.

Which coding agents can use agent-provider-architecture?
agent-provider-architecture installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.