@cuylabs/agent-server
v6.2.2
Published
Transport-neutral local server for @cuylabs/agent-core sessions, turns, and streamed events
Maintainers
Readme
@cuylabs/agent-server
Local session-and-turn host for @cuylabs/agent-core, with in-process, stdio, and websocket clients.
See docs/README.md for the modular package docs.
This package adds an interactive host layer above the existing framework packages:
@cuylabs/agent-corestill owns agent execution, tools, sessions, and plugins@cuylabs/agent-runtimestill owns background scheduling/orchestration@cuylabs/agent-runtime-daprstill owns Dapr durability and workflow hosting@cuylabs/agent-serverowns local multi-session turn execution, live event fanout, and client-facing session APIs
Boundary
Use @cuylabs/agent-server when you want a long-lived local process to:
- manage multiple persisted sessions
- start and interrupt turns independently of a single UI client
- stream
AgentEvents to multiple subscribers - let clients disconnect and reconnect without losing server-owned turn state
This package is intentionally not:
- a replacement for
agent-runtimeoragent-runtime-dapr - a new plugin system
- a renderer/UI extension layer
The current implementation ships three access patterns:
- in-process client/server for the CLI and tests
- stdio transport for one external client process
- websocket transport for reconnectable multi-client use
Capability contract
@cuylabs/agent-server now exposes an explicit capability snapshot through
getCapabilities().
That contract is split into:
protocol- transport kind, reconnect semantics, multi-client support
sessions- persistence and session management features such as branching
turns- streaming, waiting, interruption, steering, follow-up queuing, follow-up management, and concurrency rules
interactive- approval request and human input request routing
runtime- whether execution is local, remote, or hybrid
- whether orchestration is direct,
agent-runtime, oragent-runtime-dapr - whether Dapr-backed workflow delegation is available
agent- workspace summary snapshots for startup and welcome surfaces
- model inspection and model switching
- tool, skill, and sub-agent profile inspection
- session runtime status snapshots for model/session/context state
- context compaction plus turn undo / diff helpers
plugins- the server-side plugin contract: headless behavior, command metadata, no client extension execution
This keeps the server/client boundary inspectable without coupling clients to implementation details.
What this enables
@cuylabs/agent-server is useful when one agent session should outlive one UI process.
It gives us a place to:
- keep a turn running even if one TUI client disconnects
- let multiple clients observe the same live session
- expose session read/list/branch APIs without booting a fresh UI-owned loop
- share one backend between TUI, automation, and future web or chat clients
- keep plugin and tool execution on the server side while clients focus on rendering and input
- expose steering and approval-request flows through a stable protocol instead of tying them to one UI
Core model
This package uses the framework's own vocabulary instead of copying another tool's protocol:
- session — the persisted tree-backed conversation in
agent-corestorage - turn — one user input plus the streamed agent work that follows
- event — the raw
AgentEventemitted during a turn
That matches the current framework better than introducing a second conversation model.
Quick start
import { createAgent } from "@cuylabs/agent-core";
import {
InProcessAgentServer,
InProcessAgentServerClient,
createAgentServerAdapter,
} from "@cuylabs/agent-server";
const agent = createAgent({
model,
cwd: process.cwd(),
systemPrompt: "You are a helpful coding agent.",
});
const server = new InProcessAgentServer(createAgentServerAdapter(agent));
const client = new InProcessAgentServerClient(server);
const session = await client.createSession({ title: "Refactor auth flow" });
const turn = await client.startTurn(session.id, "Find the auth bug and fix it.");
const off = client.subscribe((notification) => {
if (notification.type === "turn/event") {
console.log(notification.event.type);
}
}, { turnId: turn.id });
const completed = await client.waitForTurn(turn.id);
off();
console.log(client.getCapabilities().runtime);
console.log(completed.status, completed.output);Transport examples
WebSocket server
import {
InProcessAgentServer,
createAgentServerAdapter,
startAgentServerWebSocketServer,
} from "@cuylabs/agent-server";
const server = new InProcessAgentServer(createAgentServerAdapter(agent));
const transport = startAgentServerWebSocketServer(server, {
host: "127.0.0.1",
port: 4647,
});
await transport.ready;
console.error(`listening on ${transport.address()}`);Remote stdio client
import { connectStdioAgentServerClient } from "@cuylabs/agent-server";
const client = await connectStdioAgentServerClient({
command: process.execPath,
args: ["./bin/cuylabs.js", "server", "--transport", "stdio"],
});Human Input
Direct agent-core human input now plugs into agent-server automatically
when the agent is configured with both:
humanInputoncreateAgent(...)- at least one human-input tool such as
createHumanInputTool()
Example:
import {
createAgent,
createHumanInputTool,
} from "@cuylabs/agent-core";
const agent = createAgent({
model,
humanInput: {},
tools: [createHumanInputTool()],
});
const adapter = createAgentServerAdapter(agent);In that setup:
getCapabilities().interactive.humanRequestsbecomestruehuman-input-requestandhuman-input-resolvedflow through turn eventsrespondToInputRequest(..., { kind: "human", response })resolves the pending controller request
This keeps direct server mode aligned with the durable Dapr semantics:
- request state is authoritative
- events are live notifications
- explicit response APIs resume execution
Why this exists
Without an app server, the CLI process usually owns the live turn:
- UI starts the turn
- UI consumes
agent.chat(...) - UI dies, the live interaction dies
With an app server:
- server starts the turn
- server owns session and turn state
- server fans out updates to subscribers
- clients can reconnect or multiple clients can observe the same session
That is the key shift: the live agent loop stops belonging to one terminal window.
Plugins
This package does not require a plugin API rewrite.
agent-core plugins already contribute headless behavior:
- tools
- middleware
- host commands
- prompt sections
- lifecycle hooks
That logic stays on the server side. The app server executes the same agent, so plugin business logic continues to run where it already belongs.
Server-backed hosts can now also discover and execute plugin commands through the same agent-server contract:
listPluginCommands()executePluginCommand(name, args)
That keeps plugin command routing attached to the hosted agent instead of duplicating command execution logic in every client.
If you later add client-specific UI extensions, that should be a separate client-layer extension surface. Do not mix TUI/web UI contributions into the server plugin contract.
Relationship to Dapr
agent-runtime-dapr and agent-server solve different problems:
- Dapr gives durable workflows, checkpoints, state stores, and jobs
- agent-server gives session/turn APIs, subscriptions, and live client semantics
They compose well:
- agent-server can start turns locally
- some turns can be delegated to a durable runtime later while still presenting the same client-facing capability contract
- the client-facing protocol stays the same
Current status
The current package provides:
- in-process session listing, reading, creation, deletion, and branching
- background turn execution with
startTurn(...) - per-turn interruption, steering, and follow-up queuing
- follow-up management: list, get, resolve/discard queued follow-ups
- approval request routing over server notifications
- human input request routing over server notifications
- multi-subscriber event fanout
waitForTurn(...)and turn inspection- capability-gated agent affordances such as runtime status, model switching, tool inventory, skill inspection, sub-agent profiles, compact, undo, and diff
- workspace summary snapshots that let clients render startup state from the server instead of stitching local counters together
- stdio and websocket transports for external clients
The next layer is not “add a server.” The server already exists. The next layer is broader client adoption and richer request/response flows on top of the same ownership model.
