Editor & IDE Compatibility

One Core, Many Surfaces

An agent harness is not a CLI. It is not a VS Code extension. It is not a web service. It is a core runtime — conversation loop, tool registry, permission model, configuration — that can be presented through any of these interfaces. The interface is a thin translation layer between the core’s event stream and whatever medium the user is working in.

This separation is not optional for any agent that intends to reach users where they work. Developers use terminals, VS Code, JetBrains IDEs, Vim/Neovim, web editors, and increasingly mobile interfaces. An agent that only works in one of these is an agent that most developers cannot use in their primary workflow.


The Interface Matrix

Each surface has distinct constraints on transport, input handling, output rendering, and lifecycle management.

InterfaceTransportInputOutputLifecycle
CLI / REPLstdin/stdoutLine editing (readline/rustyline), signal handling (Ctrl+C, Ctrl+D)Streaming markdown, syntax highlighting, terminal width adaptationProcess lifetime = session lifetime
VS CodeJSON-RPC over stdio or WebSocketExtension API messages, editor selections, file contextWebview panels, inline annotations, editor decorations, progress indicatorsExtension host manages lifecycle; survives editor restarts via state serialization
JetBrainsHTTP/WebSocket or stdioPlugin API messages, PSI tree context, editor selectionsTool windows, editor inlays, notification balloonsJVM plugin lifecycle; must handle project open/close events
Neovimstdio or TCP (via Lua RPC)Lua callbacks, buffer context, visual selectionsFloating windows, virtual text, quickfix listsNeovim process lifetime; plugin lazy-loaded on demand
WebHTTP/SSE or WebSocketREST API requests, WebSocket messagesJSON event stream consumed by frontend frameworkStateless server; session state externalized to database or cache

The Harness Event Stream

The core runtime should emit a single, interface-agnostic event stream that every surface consumes and translates into its native presentation.

graph LR
    subgraph core ["Agent Core"]
        CL["Conversation Loop"]
        TR["Tool Registry"]
        PM["Permission Model"]
    end

    ES["Event Stream"]

    subgraph surfaces ["Interface Surfaces"]
        CLI["CLI / REPL"]
        VSC["VS Code Extension"]
        JB["JetBrains Plugin"]
        WEB["Web Server"]
    end

    core --> ES
    ES --> CLI
    ES --> VSC
    ES --> JB
    ES --> WEB

Event Types

The event stream defines a vocabulary that is rich enough to drive any interface but contains no interface-specific concerns:

EventPayloadCLI renders asIDE renders asWeb emits as
MessageStart{ role, turn_id }Newline + role prefixNew chat bubble in panelSSE message_start
TokenDelta{ text }Append to terminal outputAppend to webview panelSSE token_delta
ToolCallRequest{ tool, args, requires_approval }Print tool name + argsShow inline annotation at cursorJSON tool_request
PermissionPrompt{ tool, args, risk_level }Interactive terminal prompt (y/n)Modal dialog with approve/deny buttonsJSON requiring frontend confirmation
ToolCallResult{ tool, status, content }Print result, syntax-highlightedCollapsible panel in chat viewSSE tool_result
Error{ code, message }Print to stderr in redError notification balloonSSE error
SessionEnd{ reason, summary }Print summary + exitUpdate status bar, offer to resumeSSE session_end

The key principle: the core never imports terminal, editor, or HTTP libraries. It emits events. The surface translates.


Permission Prompts Across Surfaces

Permission handling is the hardest interface problem. In the CLI, a permission prompt is a blocking readline call. In an IDE, it is a non-blocking dialog. In a web app, it is an asynchronous HTTP round-trip. The core must support all three.

sequenceDiagram
    participant C as Agent Core
    participant S as Interface Surface

    C->>S: PermissionPrompt { tool: "bash", args: "rm -rf dist/", risk: "destructive" }
    Note over S: CLI: blocking readline<br/>IDE: modal dialog<br/>Web: async HTTP

    alt User approves
        S->>C: PermissionResponse { approved: true }
        C->>C: Execute tool
    else User denies
        S->>C: PermissionResponse { approved: false }
        C->>C: Return denial to model
    end

Design Requirements


Core Extraction Pattern

The architectural pattern for achieving interface independence is core extraction: the agent’s logic is built as a library with no I/O dependencies, and each interface surface is a thin binary or plugin that wires the library to its environment.

agent-core/          # Library crate/package — no I/O, no UI
  conversation.rs    # Agentic loop, tool dispatch
  tools.rs           # Tool registry, execution
  permissions.rs     # Permission model, approval flow
  config.rs          # Configuration loading, merging
  events.rs          # Event type definitions

agent-cli/           # Binary — depends on agent-core
  main.rs            # REPL, terminal rendering, signal handling

agent-vscode/        # Extension — depends on agent-core (via WASM or subprocess)
  extension.ts       # VS Code API bindings, webview panels

agent-jetbrains/     # Plugin — depends on agent-core (via subprocess or HTTP)
  Plugin.kt          # IntelliJ API bindings, tool windows

agent-server/        # HTTP server — depends on agent-core
  server.rs          # REST/SSE endpoints, session management

Testing the Core Without an Interface

If the core is properly extracted, it can be tested with a mock surface that records events and replays permission responses:

core = AgentCore(config, tools)
mock_surface = MockSurface(
    permission_responses={"bash": True, "write_file": False}
)

events = core.run(
    message="Delete the dist folder and recreate it",
    surface=mock_surface
)

assert any(e.type == "PermissionPrompt" and "rm" in e.args for e in events)
assert any(e.type == "ToolCallResult" and e.tool == "bash" for e in events)
assert not any(e.type == "ToolCallResult" and e.tool == "write_file" for e in events)

If your tests require a terminal emulator or a browser, your abstraction is leaking.


Cross-Platform Adoption

PlatformCLIVS CodeJetBrainsWebArchitecture
Claude CodeNative REPLFirst-party extensionFirst-party extensionclaude.ai/codeCore library + thin shells per surface
OpenAI CodexNative CLIChatGPT desktop (limited)Not availableNot availableCLI-first, limited surface coverage
Gemini CLINative CLINot availableAndroid Studio integration (Gemini)AI StudioSeparate products, not shared core
CursorNot availableFork of VS Code (native)Not availableNot availableSingle-surface (VS Code fork)
WindsurfNot availableFork of VS Code (native)Not availableNot availableSingle-surface (VS Code fork)

The trend is clear: agents that start as single-surface products (Cursor, Windsurf) face increasing pressure to support additional interfaces as users expect the same agent in their terminal, their IDE, and their browser. Agents that extract the core early (Claude Code) can expand to new surfaces without rewriting the agent logic.


State Synchronization

When the same agent core serves multiple surfaces simultaneously (e.g., a user has both the CLI and VS Code open), session state must be synchronized or explicitly isolated.

StrategyHow It WorksTradeoff
Exclusive sessionsEach surface owns its session. No sharing.Simple. Users lose continuity when switching surfaces.
Shared serverA local daemon process runs the agent core. CLI and IDE connect as clients.Full continuity. Adds a daemon lifecycle to manage.
State file lockingSession state serialized to disk. Surfaces acquire a lock before reading/writing.Works offline. Lock contention possible.
Cloud syncSession state stored in a remote service. All surfaces read/write via API.Works across machines. Requires network. Adds latency.

The shared-server pattern is emerging as the dominant approach for local development: a single agent-server process manages sessions, and both the CLI and IDE extension connect to it as clients. This avoids duplicate model calls and ensures that a tool call initiated in VS Code is visible in the terminal.


Key Takeaways