External Agents (Async Connector)
January 18, 2026 · View on GitHub
Note: References to artifact content/context reflect the legacy model. In the panel plugin architecture, these map to artifact item context providers. Item HTTP routes now live under
/api/plugins/artifacts/*.
Table of Contents
- Overview
- Source files
- Goals
- Non-goals (v1)
- Current State (Today)
- Proposed Model
- API/Flow Diagrams
- Server Responsibilities (External Runtime)
- Web UI Responsibilities
- Config Shape (Draft)
- Future Extensions
Overview
This document proposes a new agent runtime kind: external agents.
External agents do not use OpenAI (or any in-process LLM provider) to produce assistant messages. Instead:
- The web UI creates/attaches a session using a user-provided session ID (an “external session ID”).
- User chat input is forwarded to a configured inputUrl (an HTTP endpoint we control the contract for).
- Assistant messages arrive asynchronously and are injected into the session via a new callback URL (an inbound HTTP endpoint on the agent server).
This enables integrating an out-of-process agent system while keeping the AI Assistant UI as the front-end.
Source files
packages/agent-server/src/externalAgents.tspackages/agent-server/src/http/routes/external.tspackages/agent-server/src/ws/chatRunLifecycle.tspackages/agent-server/src/chatProcessor.ts
Goals
- Support a new agent kind that:
- forwards user input to
inputUrl, - receives assistant output asynchronously,
- displays messages in the UI as if they were produced by an in-process agent.
- forwards user input to
- Use a user-provided
sessionId(shared between UI/CLI/external system). - Avoid any “create session” outbound API calls in v1 (the external system can manage mapping/workflow externally).
- Keep the design forward-compatible with:
- additional external agents (multiple shims),
- additional in-process chat providers (non-OpenAI).
Non-goals (v1)
- No artifact item content attachment or tool forwarding (can be added later).
- No auth/ACLs for the inbound injection endpoint (internal network assumption for now).
- No streaming assistant output back to the browser.
Current State (Today)
- Agents are configuration templates (
AgentDefinition) with prompts and tool scoping, but there is no runtime “agent type”. - Sessions are local and are typically created with server-generated UUIDs.
- User chat input is handled by the in-process OpenAI-backed runtime and assistant messages stream back over WebSocket.
Proposed Model
Agent types
Introduce a discriminant on agent definitions:
type: "chat"(default; current behavior)type: "external"(new)
This keeps future “non-OpenAI chat providers” under "chat" (as provider configuration) without mixing them with the async external workflow.
Session IDs
For external agents, the session ID is entered by the user and becomes the session ID used everywhere:
- Web UI attaches via WS
hello { sessionId } - HTTP endpoints use
/api/plugins/sessions/operations/*(sessionId is passed in the JSON body) - The external system uses the same ID (no separate mapping required in v1)
This implies server-side validation constraints (URL-safe, length bounds, uniqueness, etc.).
API/Flow Diagrams
High-level architecture
(internal network; no auth in v1)
┌───────────────────────────────┐
│ Web Client (browser) │
│ - WS: /ws │
│ - HTTP: /api/plugins/sessions/operations/* │
└───────────────┬───────────────┘
│ WS hello(sessionId)
│ WS user_message(text)
▼
┌──────────────────────────────────────────────────────────────┐
│ Agent Server │
│ - SessionIndex + EventStore │
│ - ExternalAgent runtime │
│ • forwards user input to inputUrl │
│ • accepts inbound assistant messages │
└───────────────┬───────────────────────────────────────────────┘
│ POST user input (our contract)
▼
┌───────────────────────────────┐
│ inputUrl (per external agent) │
└───────────────┬───────────────┘
▼
┌───────────────────────────────┐
│ External system │
└───────────────┬───────────────┘
│ POST callbackUrl (raw text)
▼
┌───────────────────────────────┐
│ Agent Server → WS broadcast │
└───────────────────────────────┘
Sequence: create/attach external session (user enters ID)
User Web UI Agent Server
| | |
| enter ID | |
|----------->| POST /api/plugins/sessions/operations/create |
| | { agentId, sessionId } |
| |----------------------->|
| | 201 { ok: true, result: { sessionId, ... } } |
| |<-----------------------|
| | WS connect + hello |
| | { sessionId } |
| |----------------------->|
| | session_ready(...) |
| |<-----------------------|
Sequence: user sends chat message (forward to inputUrl; no streaming back)
User Web UI Agent Server inputUrl
| | | |
| type msg | WS user_message | |
|----------->|------------------------>| |
| | | persist user message |
| | |------------------------>|
| | | POST inputUrl |
| | | { sessionId, text, ... }|
| | |<------------------------|
| | | 200 { ok:true } |
| | (no assistant stream) | |
Sequence: external agent sends assistant message back (callback inject)
External system Agent Server Web UI
| | |
| produce assistant text | |
| POST callbackUrl | |
| (raw text body) | |
|------------------------>| persist assistant + broadcast over WS |
| |--------------------------------------->|
| | 200 { ok:true } |
|<------------------------| render assistant (+ optional TTS) |
Server Responsibilities (External Runtime)
1) Session creation/attach with user-provided ID
Extend the session creation path so clients can specify the session ID:
POST /api/plugins/sessions/operations/create- Body:
{ agentId: string, sessionId?: string } - For
type: "external"agents,sessionIdis required by the UI flow. - Behavior:
- If session does not exist: create it with that ID and set
agentId. - If session exists with same agentId: treat as attach (idempotent).
- If session exists with different agentId: reject (avoid cross-wiring).
- If session is deleted: “revive” (idempotent attach).
- If session does not exist: create it with that ID and set
- Body:
Validation (recommended):
- Non-empty, trimmed.
- Length limit (e.g. ≤ 128).
- URL-safe character set:
[A-Za-z0-9_-].
2) Forward user input to inputUrl
When a session is associated with an external agent:
- On WS
user_message:- persist the user message as normal (conversation log)
- asynchronously
POSTtoinputUrlwith our standard payload (timeout 5s, no retries) - do not attempt to run an in-process LLM completion
Proposed input payload (v1, draft):
{
"sessionId": "EXTERNAL-123",
"agentId": "external-agent-a",
"callbackUrl": "http://agent-server.internal/external/sessions/EXTERNAL-123/messages",
"message": {
"type": "user",
"text": "hello",
"createdAt": "2025-12-12T00:00:00.000Z"
}
}
Notes:
- If the external system needs its own internal IDs, it can map from
sessionIdto those IDs. - Error handling (v1): if
inputUrlreturns non-2xx (or times out), inject an error message into the chat.
3) Accept assistant output injection (new inbound endpoint)
Add a new HTTP endpoint that appends an assistant message to a session and broadcasts it to connected clients:
POST /external/sessions/:sessionId/messages- Body (v1): raw text (may contain Markdown)
- Behavior:
- Ensure the session exists and is not deleted.
- Append an assistant message to the conversation store (modality
text). - Broadcast an assistant message to connected clients (reuse the existing message path so Markdown + optional TTS work).
- Return
200 OK(no JSON body required).
Example curl heredoc (v1):
curl -sS -X POST \
"http://agent-server.internal/external/sessions/EXTERNAL-123/messages" \
--data-binary @- <<'MSG'
Here is a *Markdown* reply.
- One
- Two
MSG
Web UI Responsibilities
External session creation UX
For external agents, “New session” should prompt for an ID (or provide an “Attach” action):
- Pick an external agent.
- User enters external session ID.
- UI calls
POST /api/plugins/sessions/operations/create { agentId, sessionId }. - UI switches to that session and connects WS with
hello { sessionId }.
Chat UX for async agents
External agents will not stream assistant responses; UX options:
- After sending user input, append an italic status line (same style as the “Interrupted” indicator), for example: “Sent to external agent”.
- Remove the status line when the next assistant message arrives (or replace it with an error line if forwarding fails).
Config Shape (Draft)
Agents gain a type and (for external agents) an inputUrl plus a callbackBaseUrl.
{
"agentId": "external-agent-a",
"displayName": "External Agent A",
"description": "Async external agent via inputUrl",
"type": "external",
"external": {
"inputUrl": "http://external.internal/v1/assistant/input",
"callbackBaseUrl": "http://agent-server.internal"
}
}
No create-session URL is required in v1.
Future Extensions
Send active item pointer/content (legacy artifact naming)
Later we may add an opt-in UI toggle: “Send active item content with messages”.
If enabled, the forwarded payload could include:
activeArtifact: { type, id, name? }activeArtifactContent: <full artifact JSON>(legacy name; for example, full note content)
This would require defining:
- size/limits for content payloads,
- redaction rules (if needed),
- content refresh policy (send only on change vs every message).
External session discovery/attach
Add a dedicated endpoint to “attach” a session ID (or validate it exists externally via shim), and improve UX around conflicts.
Auth and transport hardening
- Require a shared token for
POST /external/sessions/:id/messages. - Add structured audit logs for injected messages.