Welcome to the Bamboo AI Agent API documentation. Bamboo provides a fully self-contained AI agent backend framework with built-in HTTP/HTTPS server capabilities.
Bamboo offers RESTful API endpoints for creating and managing AI agent conversations, executing agent loops, and streaming real-time events.
http://localhost:9562/api/v1
POST /api/v1/chatCreate a new chat session or add a message to an existing session.
Supply an optional Idempotency-Key header when a client may retry after an
ambiguous timeout. For 10 minutes, an equivalent retry returns the first
response without appending the message again. Reusing the key with another
payload returns 409 idempotency_key_conflict. Receipts are process-local and
bounded; omitting the header preserves the normal behavior.
Request Body:
{
"message": "Help me write a function",
"session_id": "optional-session-id",
"model": "claude-sonnet-4-6",
"system_prompt": "You are a helpful assistant",
"enhance_prompt": "Additional instructions",
"workspace_path": "/path/to/workspace"
}Root sessions can opt into delegation guidance with
"root_orchestration_prompt": true in the chat request. The choice is stored
with the session and applies to the first execution and later resumes; omitting
the field keeps the current choice, and false turns it off. The guidance
covers child planning, progress checks, correction, scope control, and final
evidence. Child sessions do not receive it. This prompt choice does not change
tool permissions.
Root sessions can also select "root_orchestration_only": true. This durable
execution mode supplies the same delegation guidance even when
root_orchestration_prompt is unset. It limits the Root to these nine exact
tool execution identities: SubAgent, Plan, Task,
session_history_current, Read, Grep, Glob, GetFileInfo, and
ViewImage. Other tools, including shell and editing tools, are unavailable
to that Root; delegated children retain their own tool authority. The mode and
the prompt-only choice are independent.
The first chat request for a new Root may select this mode with
root_orchestration_only. For an existing Root, omit that field from
POST /chat and use the recoverable mode operation below. An explicit value
on an existing Root chat returns 428 root_mode_operation_required before a
message is appended. A Child cannot select or clear the mode. The authoritative
selection is returned by GET /api/v1/sessions/{session_id}; clients should
read it after reload instead of treating a local choice as persisted state.
Response: 201 Created
{
"session_id": "uuid-string",
"stream_url": "/api/v1/events/session-id",
"status": "streaming"
}Next Steps: After creating a chat, call POST /api/v1/execute/{session_id} to start the agent.
GET /api/v1/sessions/{session_id} returns these detail-only fields for a Root:
{
"session": {
"root_orchestration_only": false,
"root_mode_transition_epoch": 0,
"root_mode_birth_token": "opaque-64-character-hex-token"
}
}To change an existing Root, generate one canonical lowercase UUID and form the
operation ID as <expected_epoch>:<uuid>, for example
0:550e8400-e29b-41d4-a716-446655440000. Send the detail's birth token,
epoch, and desired mode to:
POST /api/v1/sessions/{session_id}/root-mode-operations/{operation_id}{
"birth_token": "opaque-64-character-hex-token",
"expected_epoch": 0,
"enabled": true
}A committed response is 200 OK, with Cache-Control: no-store:
{
"status": "committed",
"operation_id": "0:550e8400-e29b-41d4-a716-446655440000",
"expected_epoch": 0,
"resulting_epoch": 1,
"enabled_at_completion": true,
"root_tool_authority_revision": 1
}If the response is lost or times out, keep the same operation ID and body
and call POST /api/v1/sessions/{session_id}/root-mode-operations/{operation_id}/recover.
Recovery returns the committed terminal receipt if selection finished first.
If recovery reaches the durable writer first, it records a terminal fenced
receipt and prevents a late selection from changing the mode. An ordinary
selection replay after that fence returns 409 root_mode_operation_fenced.
If a later operation has already advanced the epoch after this receipt was
evicted, recovery returns 200 with status: "fenced_by_successor",
the validated operation_id and expected_epoch, current_epoch,
current_enabled, and root_tool_authority_revision; the old
selection remains unable to commit.
The server retains the latest eight terminal receipts for exact retries across
process restarts. Older receipts are replaced by the durable successor epoch;
their original operation ID cannot be rebound to another epoch. Reuse of a
retained operation ID with a different enabled value returns
409 root_mode_operation_conflict. A stale epoch or changed Root birth returns
412; a selected Skill or Workflow, or active legacy PlanMode, gives a durable
rejected_incompatible terminal result (409 on selection, 200 on
recovery). A storage backend without this operation returns 503 and makes
no mode change. Other storage or proof errors return
503 root_mode_outcome_unconfirmed: the commit may already have happened, so
retain the operation ID and recover it. The default Supervisor uses its
existing strict management proof when refreshing the next provider catalog.
Roots with no terminal mode operation remain readable with their legacy v1 authority proof. The first terminal operation writes a v2 proof, so an older backend's v1-only writer fails closed rather than discarding its epoch/history. Run current backend versions for mode operations; old processes cannot serve that Root after this upgrade. Downgrading an authority proof is unsupported.
A successful mode response and a subsequent ordinary chat are separate
operations. Another client can change the mode between them; clients should
check session detail if the mode used by that chat matters. A running tool
batch retains its admitted catalog snapshot, while later tool boundaries
adopt durable Root authority. Legacy clients that hold an ambiguous combined
POST /chat marker cannot clear it with this new operation: that old POST had
no operation ID, so its terminal result cannot be proven here.
POST /api/v1/execute/{session_id}Start the agent execution loop for a session.
Idempotency-Key has the same optional 10-minute replay contract as chat. An
equivalent retry returns the original status, body, and run_id without
starting another run. The canonical nested route
POST /api/v1/sessions/{session_id}/execute shares the same receipt.
Path Parameters:
session_id- Session identifier from/api/v1/chat
Request Body:
{
"model": "claude-sonnet-4-6"
}Response: 202 Accepted
{
"session_id": "session-id",
"status": "started",
"events_url": "/api/v1/events/session-id"
}Note: The model parameter is required and must be provided in every request.
GET /api/v1/events/{session_id}Subscribe to real-time agent events via Server-Sent Events (SSE).
Path Parameters:
session_id- Session identifier
Response: 200 OK (text/event-stream)
Event Format:
data: {"type":"token","content":"Hello"}
data: {"type":"tool_start","tool_call_id":"call_1","tool_name":"Read","arguments":{"file_path":"README.md"}}
data: {"type":"tool_complete","tool_call_id":"call_1","result":{ /* ToolResult */ }}
data: {"type":"complete","usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15}}
The type discriminant is the snake_case form of the AgentEvent variant (the enum is #[serde(tag = "type", rename_all = "snake_case")]).
Terminal Events:
complete- Agent finished successfullycancelled- Run cancelled by the usererror- Agent encountered an error
Example (JavaScript):
const eventSource = new EventSource('/api/v1/events/session-123');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Event:', data);
if (data.type === 'Complete' || data.type === 'Error') {
eventSource.close();
}
};Other event transports. Besides the per-session
GET /api/v1/events/{session_id}feed above, there is an account-wide, resumable change feedGET /api/v1/stream(SSE) that multiplexes events across all sessions — resume with?since=<seq>or theLast-Event-IDheader. There is also a live WebSocket transport at/v2/stream(per-device token auth) which is the primary transport used by the web/desktop clients; the SSE feeds remain available for simple/curl clients.
POST /api/v1/sessions/{session_id}/copyCreate an independent root session from an existing session. The copy receives a new id and preserves the complete transcript, durable configuration, permission mode, Project assignment, Workspace assignment, and attachments. Attachment URLs are rewritten to the copied session id. Durable workflow selection/activation snapshots are retained so the conversation can be reconstructed; workflow run ids, lifecycle outbox/cache data, and other transient execution, pending approval/question, child identity, schedule, placement, and run-status state are not copied. Copying a child session always produces an independent root with no parent chain.
The operation is failure-atomic: storage or attachment-copy failure leaves no target session or index entry. Concurrent requests intentionally create different copies.
Response: 201 Created with { "session": SessionSummary }, 404 Not Found when the source does not exist, or 500 Internal Server Error when the
copy could not be committed.
DELETE /api/v1/sessions/{session_id}Delete a session and cancel any running execution.
Path Parameters:
session_id- Session identifier
Response: 200 OK (no body) or 404 Not Found
Side Effects:
- Session removed from storage
- Session removed from memory
- Running execution cancelled
GET /api/v1/sessions/{session_id}/historyRetrieve message history for a session.
Path Parameters:
session_id- Session identifier
Response: 200 OK
{
"session_id": "session-id",
"messages": []
}Note: Currently returns empty messages array. Full implementation planned.
POST /api/v1/stop/{session_id}Cancel a running agent execution.
Path Parameters:
session_id- Session identifier
Response: 200 OK
{
"success": true,
"message": "Agent execution stopped"
}Behavior:
- Completes current LLM request
- Cancels pending tool executions
- Saves session state
- Updates status to
Cancelled
GET /api/v1/sessions/{session_id}/questionCheck if the agent is waiting for user input.
Path Parameters:
session_id- Session identifier
Response (Pending Question): 200 OK
{
"has_pending_question": true,
"question": "Which language should I use?",
"options": ["TypeScript", "JavaScript", "Python"],
"allow_custom": false,
"tool_call_id": "call_123"
}Response (No Question): 200 OK
{
"has_pending_question": false
}POST /api/v1/sessions/{session_id}/respondSubmit a response to a pending question from a pause-capable custom tool, a permission gate, or a compatible persisted session.
Path Parameters:
session_id- Session identifier
Request Body:
{
"response": "TypeScript"
}Response: 200 OK
{
"success": true,
"message": "Response recorded. Agent loop will continue.",
"response": "TypeScript"
}Validation: If allow_custom is false, response must match one of the provided options.
POST /api/v1/tools/executeExecute a built-in tool without running the full agent loop.
Request Body:
{
"tool_name": "read_file",
"parameters": [
{"name": "path", "value": "/path/to/file"}
]
}Response: 200 OK
{
"result": "{\"tool_name\":\"read_file\",\"result\":\"file contents\",\"display_preference\":\"Default\"}"
}Available Tools:
read_file- Read file contentswrite_file- Write file contentsexecute_command- Execute shell commandlist_directory- List directory contentsfile_exists- Check if file existsget_file_info- Get file metadatagit_status- Get git repository statusgit_diff- Get git diff- And more...
GET /healthSimple health check for load balancers and monitoring.
Response: 200 OK (plain text "OK")
Usage:
- Load balancer health probes
- Monitoring systems
- Kubernetes liveness/readiness probes
curl -X POST http://localhost:9562/api/v1/chat \
-H "Content-Type: application/json" \
-d '{
"message": "Help me write a Rust function",
"model": "claude-sonnet-4-6"
}'Response includes session_id and stream_url.
curl -X POST http://localhost:9562/api/v1/execute/{session_id} \
-H "Content-Type: application/json" \
-d '{"model": "claude-sonnet-4-6"}'Response includes events_url.
const eventSource = new EventSource('/api/v1/events/{session_id}');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(data);
if (data.type === 'Complete' || data.type === 'Error') {
eventSource.close();
}
};If the agent asks a question:
# Check for pending question
curl http://localhost:9562/api/v1/sessions/{session_id}/question
# Submit response
curl -X POST http://localhost:9562/api/v1/sessions/{session_id}/respond \
-H "Content-Type: application/json" \
-d '{"response": "Use async/await"}'# Stop execution
curl -X POST http://localhost:9562/api/v1/stop/{session_id}
# Delete session
curl -X DELETE http://localhost:9562/api/v1/sessions/{session_id}All endpoints return consistent error responses:
{
"error": "Error message",
"session_id": "session-id" // When applicable
}Common HTTP Status Codes:
200 OK- Success201 Created- Resource created202 Accepted- Request accepted for processing400 Bad Request- Invalid request or missing parameters404 Not Found- Resource not found500 Internal Server Error- Server error
The type field is the snake_case name of the AgentEvent variant
(crates/core/bamboo-agent-core/src/agent/events.rs). The most common
streaming variants:
type |
Description | Fields |
|---|---|---|
token |
Assistant text token | content |
reasoning_token |
Reasoning/thinking token | content |
tool_token |
Live output from a running tool | tool_call_id, content |
tool_start |
Tool execution started | tool_call_id, tool_name, arguments |
tool_complete |
Tool finished successfully | tool_call_id, result |
tool_error |
Tool failed | tool_call_id, error |
token_budget_updated |
Token usage / budget update | usage |
complete |
Execution finished | usage |
cancelled |
Run cancelled by the user | message |
error |
Execution failed | message |
The enum also carries session-, task-, plan-, and sub-agent-lifecycle variants
(e.g. tool_lifecycle, need_clarification, task_list_updated,
sub_agent_started, plan_mode_entered, message_appended); consult
events.rs for the exhaustive list and exact field shapes.
Bamboo can be configured via command-line flags or environment variables:
bamboo serve --port 9562 --data-dir ~/.local/share/bambooEnvironment Variables:
BAMBOO_PORT- Server port (default: 9562)BAMBOO_DATA_DIR- Data directoryBAMBOO_BIND- Bind address (default: 127.0.0.1)BAMBOO_WORKSPACE_ROOT- Root directory for session workspaces that have no explicit path (default:<data-dir>/workspaces). A session with no configured/explicit workspace gets<workspace-root>/<session-id>instead of the server process's working directory.BAMBOO_WORKSPACE_CONFINE- Set to1/trueto require every explicit workspace path to be canonicalized and confined underBAMBOO_WORKSPACE_ROOT(escapes via.., a symlink, or an absolute path elsewhere are relocated under the root instead of honored as-is). Off by default for local single-user use, where pointing bamboo at an existing project directory anywhere on disk must keep working; implicitly enabled whenBAMBOO_WORKSPACE_ROOTis set explicitly. Intended for orchestrated / multi-tenant deployments that want "one folder = one tenant's entire state".
The live config.json (providers, subagents, notifications, MCP servers,
bamboo-connect platforms, ...) is updated with a partial JSON PATCH — you
only send the fields you want to change. Two rules:
-
An omitted key leaves the existing value unchanged. This is unconditional back-compat: a patch that doesn't mention a field never touches it.
-
An explicit JSON
nulldeletes that field, opt-in per value (RFC 7386 JSON Merge Patch semantics). What "deleted" means depends on what's there:- an optional field (e.g.
subagents.claude_code_binary) → cleared back to unset. - a whole object subtree (e.g.
notifications: null) → reset to defaults. - one entry of a dynamic map (e.g.
provider_instances: {"<id>": null},mcpServers: {"<name>": null}) → that one entry removed, siblings untouched. - a whole array (e.g.
connect.platforms: null) → emptied. Anullinside an array element is never a delete marker — arrays are always replaced wholesale, not merged element-by-element.
Secret fields (
api_key,token,device_key,app_secret) treatnullas an explicit clear, equivalent to sending""— both are distinct from a masked placeholder (****...****, which means "keep the existing secret").Choose your blast radius by choosing which level you null out: nulling a single leaf (
providers.openai.api_key: null) clears just that field; nulling an enclosing object (providers.openai: null) wipes the whole provider's config. - an optional field (e.g.
See bamboo_config::patch::deep_merge_json's doc comment for the full
semantics table and the precedence rules against masked-placeholder
resolution.
Bamboo follows a session-based architecture with unified server implementation:
- Session: Contains conversation history and state
- Agent Loop: Processes messages and executes tools
- LLM Provider: Communicates with AI model APIs (OpenAI, Anthropic, Gemini, Copilot)
- Tool Executor: Runs built-in tools (read, write, execute, etc.)
- Event Broadcaster: Streams real-time events via Server-Sent Events
- Unified Server: Single HTTP server with explicit routing (~120 routes)
bamboo-servercrate: Unified HTTP server with explicit routing- Explicit routing: All routes registered in
crates/bamboo-server/src/routes/ - Direct provider access: No HTTP callbacks to self (eliminates proxy pattern)
- Handler organization (
crates/bamboo-server/src/handlers/):- Core agent handlers in
handlers/agent/(chat, execute, events, stop, history, respond, etc.) - Provider handlers in
handlers/(openai/, anthropic/, gemini/, copilot_auth/, agent_api.rs) - Feature handlers in
handlers/(settings/, tools/, workspace/, skill/, command/)
- Core agent handlers in
MIT License
- GitHub Issues: https://github.com/bigduu/Bamboo-agent/issues
- Documentation: https://docs.rs/bamboo-agent