Server-scoped routes manage the whole server: projects, workspace lifecycle, and auth accounts. Runtime context is for anything resolved from an active directory, including config, provider capabilities, tools, files, and VCS.
API map
A single /api route surface for simple clients and multi-directory frontends. The important
design question is not route nesting; it is where runtime context comes from.
Context Model
Request-context calls
These calls operate against a directory, optionally through a workspace. Simple clients omit context and use the default runtime.
GET /api/fs/tree?path=.&directory=/repo/app&workspace=ws_123
Session-pinned calls
These calls never take request context. The session is already pinned to the directory and workspace it was created in.
POST /api/session/ses_123/prompt
// server resolves
sessionID -> { directory, workspaceID? }
Operation Inventory
The SDK is the source of truth. HTTP routes are mounts for RPC-style operations. server operations do not use runtime context. request operations use request/default runtime context from directory and workspace query parameters. session operations use pinned session context and should not accept context input.
| Operation | Input | Context | HTTP mount | Purpose |
|---|---|---|---|---|
agent.list | {} | request | GET /api/agent | Available agents. |
auth.activate | { accountID: AccountID } | server | POST /api/auth/:accountID/activate | Set the account as active for its service. |
auth.create | {
serviceID: ServiceID
credential:
| { type: "oauth", refresh: string, access: string, expires: number }
| { type: "api", key: string, metadata?: Record<string, string> }
description?: string
active?: boolean
} | server | POST /api/auth | Create an auth account. |
auth.delete | { accountID: AccountID } | server | DELETE /api/auth/:accountID | Remove an auth account. |
auth.get | { accountID: AccountID } | server | GET /api/auth/:accountID | Get one auth account. |
auth.list | { serviceID?: ServiceID } | server | GET /api/auth | List saved auth accounts. Response includes active account mapping. |
auth.update | {
accountID: AccountID
description?: string
credential?:
| { type: "oauth", refresh: string, access: string, expires: number }
| { type: "api", key: string, metadata?: Record<string, string> }
} | server | PATCH /api/auth/:accountID | Update account description or credential. |
catalog.model.get | {
providerID: ProviderID
modelID: ModelID
} | server | GET /api/catalog/model/:providerID/:modelID | Get one catalog model. |
catalog.model.list | {} | server | GET /api/catalog/model | List flattened catalog models. |
command.list | {} | request | GET /api/command | Available commands. |
config.get | {} | request | GET /api/config | Resolved config. |
config.update | { config: Config } | request | PATCH /api/config | Update config. |
event.subscribe | {} | request | GET /api/event | Server-sent events for the resolved runtime context. |
formatter.status | {} | request | GET /api/formatter | Formatter status. |
fs.file | { path: string } | request | GET /api/fs/file | Read one file. |
fs.grep | {
pattern: string
include?: string
limit?: number
} | request | POST /api/fs/grep | Search file contents. |
fs.search | {
query: string
type?: "file" | "directory"
limit?: number
} | request | POST /api/fs/search | Search paths by name. |
fs.tree | { path: string } | request | GET /api/fs/tree | Browse a directory. |
lsp.status | {} | request | GET /api/lsp | LSP status. |
mcp.prompt.list | {} | request | GET /api/mcp/prompt | List MCP prompts. |
mcp.prompt.render | {
server: string
name: string
arguments?: Record<string, string>
} | request | POST /api/mcp/prompt/render | Render one MCP prompt. |
mcp.resource.list | {} | request | GET /api/mcp/resource | List MCP resources. |
mcp.resource.read | {
server: string
uri: string
} | request | GET /api/mcp/resource/read | Read one MCP resource. |
mcp.server.create | {
name: string
config:
| { type: "local", command: string, arguments?: string[], environment?: Record<string, string> }
| { type: "remote", url: string, headers?: Record<string, string>, oauth?: boolean | object }
} | request | POST /api/mcp/server | Add an MCP server to runtime config. |
mcp.server.list | {} | request | GET /api/mcp/server | List MCP servers with status and auth state. |
mcp.server.oauth.callback | {
name: string
code: string
} | request | POST /api/mcp/server/:name/oauth/callback | Complete MCP OAuth. |
mcp.server.oauth.delete | { name: string } | request | DELETE /api/mcp/server/:name/oauth | Remove MCP OAuth credentials. |
mcp.server.oauth.start | { name: string } | request | POST /api/mcp/server/:name/oauth | Start MCP OAuth. |
permission.list | {} | request | GET /api/permission | Pending permission requests. |
permission.reply | {
permissionID: PermissionID
response: PermissionReply
} | request | POST /api/permission/:permissionID/reply | Reply to a permission request. |
project.get | { projectID: ProjectID } | server | GET /api/project/:projectID | Get project metadata. |
project.list | {} | server | GET /api/project | List projects known to this server. |
project.update | {
projectID: ProjectID
name?: string
icon?: string
commands?: Array<{
name: string
command: string
}>
} | server | PATCH /api/project/:projectID | Update project metadata. |
provider.list | {} | request | GET /api/provider | Provider inventory for the runtime context. |
pty.create | {
command?: string
cwd?: string
shell?: string
} | request | POST /api/pty | Create PTY in the runtime context. |
pty.delete | { ptyID: PtyID } | request | DELETE /api/pty/:ptyID | Delete PTY. |
pty.get | { ptyID: PtyID } | request | GET /api/pty/:ptyID | Get PTY info. |
pty.list | {} | request | GET /api/pty | List PTYs for the runtime. |
pty.update | {
ptyID: PtyID
title?: string
size?: { columns: number, rows: number }
} | request | PATCH /api/pty/:ptyID | Update PTY. |
question.list | {} | request | GET /api/question | Pending user questions. |
question.reject | { questionID: QuestionID } | request | POST /api/question/:questionID/reject | Reject a question. |
question.reply | {
questionID: QuestionID
response: QuestionResponse
} | request | POST /api/question/:questionID/reply | Reply to a question. |
session.compact | { sessionID: SessionID } | session | POST /api/session/:sessionID/compact | Compact the session conversation. |
session.context | { sessionID: SessionID } | session | GET /api/session/:sessionID/context | Return active context messages after the last compaction. |
session.create | {
title?: string
agent?: string
model?: { providerID: ProviderID, modelID: ModelID }
permission?: PermissionRule[]
} | request | POST /api/session | Create a session pinned to resolved runtime context. |
session.delete | { sessionID: SessionID } | session | DELETE /api/session/:sessionID | Delete a session. |
session.diff | { sessionID: SessionID } | session | GET /api/session/:sessionID/diff | Return session diff summary. |
session.get | { sessionID: SessionID } | session | GET /api/session/:sessionID | Get one session. |
session.list | {
limit?: number
order?: "asc" | "desc"
path?: string
roots?: boolean
start?: number
search?: string
cursor?: string
} | request | GET /api/session | List sessions for the current runtime context by default. |
session.message.list | {
sessionID: SessionID
limit?: number
order?: "asc" | "desc"
cursor?: string
} | session | GET /api/session/:sessionID/message | Page through session messages. |
session.prompt | {
sessionID: SessionID
prompt: Prompt
delivery?: "immediate" | "deferred"
} | session | POST /api/session/:sessionID/prompt | Create a user message and queue the agent loop. |
session.todo | { sessionID: SessionID } | session | GET /api/session/:sessionID/todo | Return todos associated with the session. |
session.update | {
sessionID: SessionID
title?: string
archived?: number
permission?: PermissionRule[]
} | session | PATCH /api/session/:sessionID | Update title, archival state, or session metadata. |
session.wait | { sessionID: SessionID } | session | POST /api/session/:sessionID/wait | Wait until the session is idle. |
skill.list | {} | request | GET /api/skill | Available skills. |
vcs.diff | {
format?: "json" | "patch"
mode?: "worktree" | "default"
} | request | GET /api/vcs/diff | Diff for the runtime directory. |
vcs.get | {} | request | GET /api/vcs | VCS metadata. |
vcs.patch | { patch: string } | request | POST /api/vcs/patch | Apply a patch to the runtime directory. |
vcs.status | {} | request | GET /api/vcs/status | Changed files. |
workspace.create | {
projectID?: ProjectID
name?: string
directory?: string
type: string
metadata?: Record<string, unknown>
} | server | POST /api/workspace | Create or register a workspace. |
workspace.delete | { workspaceID: WorkspaceID } | server | DELETE /api/workspace/:workspaceID | Remove a workspace registration. |
workspace.get | { workspaceID: WorkspaceID } | server | GET /api/workspace/:workspaceID | Get workspace metadata. |
workspace.list | { projectID?: ProjectID } | server | GET /api/workspace | List workspaces, optionally filtered by project. |
workspace.status | {} | server | GET /api/workspace/status | Connection/lifecycle status for all workspaces. Needs team discussion. |
workspace.sync | {} | server | POST /api/workspace/sync | Sync workspace metadata from adapters. Needs team discussion. |
workspace.update | {
workspaceID: WorkspaceID
name?: string
metadata?: Record<string, unknown>
archived?: boolean
} | server | PATCH /api/workspace/:workspaceID | Update workspace metadata or lifecycle state. |
workspace.warp | {
workspaceID?: WorkspaceID
sessionID: SessionID
copyChanges: boolean
} | server | POST /api/workspace/warp | Move a session into or out of a workspace. Needs team discussion. |
Event Envelope
Every event uses the same envelope. Resource identity belongs in payload. Runtime identity belongs
in context.
type ApiEvent<Payload> = {
id: string
type: string
time: number
context: {
directory: string
workspaceID?: string
}
payload: Payload
}
{
"id": "evt_01",
"type": "message.part.delta",
"time": 1760000000000,
"context": {
"directory": "/repo/app",
"workspaceID": "ws_123"
},
"payload": {
"sessionID": "ses_123",
"messageID": "msg_456",
"partID": "part_789",
"field": "text",
"delta": "hello"
}
}
Frontend Sync Store
A frontend can keep one giant store like the current TUI. Runtime data is partitioned by
contextKey. Durable entities such as sessions and messages are keyed by their own IDs.
type RuntimeContext = {
directory: string
workspaceID?: string
}
type ContextKey = string
type SessionID = string
type MessageID = string
type SyncStore = {
status: "loading" | "partial" | "complete"
shared: {
provider: Provider[]
provider_default: Record<string, string>
provider_next: ProviderListResponse
provider_auth: Record<string, ProviderAuthMethod[]>
console_state: ConsoleState
}
contexts: Record<
ContextKey,
{
context: RuntimeContext
config: Config
agent: Agent[]
command: Command[]
lsp: LspStatus[]
formatter: FormatterStatus[]
vcs: VcsInfo | undefined
mcp: Record<string, McpStatus>
mcp_resource: Record<string, McpResource>
session: SessionID[]
session_status: Record<SessionID, SessionStatus>
}
>
session: Record<SessionID, Session & { context: RuntimeContext }>
session_diff: Record<SessionID, Snapshot.FileDiff[]>
todo: Record<SessionID, Todo[]>
permission: Record<SessionID, PermissionRequest[]>
question: Record<SessionID, QuestionRequest[]>
message: Record<SessionID, Message[]>
part: Record<MessageID, Part[]>
}
function contextKey(context: RuntimeContext) {
return `${context.workspaceID ?? "local"}:${context.directory}`
}