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}`
}