docs: restore project goals and scope to README

This commit is contained in:
Nathan Flurry 2026-01-25 04:06:25 -08:00
parent 924bacae4e
commit ab2c1c2b62
7 changed files with 897 additions and 527 deletions

View file

@ -2,6 +2,12 @@
Universal API for running Claude Code, Codex, OpenCode, and Amp inside sandboxes.
- **Any coding agent**: Universal API to interact with all agents with full feature coverage
- **Server Mode**: Run as HTTP server from any sandbox provider or as TypeScript & Python SDK
- **Universal session schema**: Universal schema to store agent transcripts
- **Supports your sandbox provider**: Daytona, E2B, Vercel Sandboxes, and more
- **Lightweight, portable Rust binary**: Install anywhere with 1 curl command
Documentation lives in `docs/` (Mintlify). Start with:
- `docs/index.mdx` for the overview
@ -23,3 +29,24 @@ sandbox-agent credentials extract-env
# Export to current shell
eval "$(sandbox-agent credentials extract-env --export)"
```
Run the web console (includes all dependencies):
```bash
pnpm dev -F @sandbox-agent/web
```
## Project Scope
This project aims to solve 3 problems with agents:
- **Universal Agent API**: Claude Code, Codex, Amp, and OpenCode all have put a lot of work in to the agent scaffold. Each have respective pros and cons and need to be easy to be swapped between.
- **Agent Transcript**: Maintaining agent transcripts is difficult since the agent manages its own sessions. This provides a simpler way to read and retrieve agent transcripts in your system.
- **Agents In Sandboxes**: There are many complications with running agents inside of sandbox providers. This lets you run a simple curl command to spawn an HTTP server for using any agent from within the sandbox.
Features out of scope:
- **Storage of sessions on disk**: Sessions are already stored by the respective coding agents on disk. It's assumed that the consumer is streaming data from this machine to an external storage, such as Postgres, ClickHouse, or Rivet.
- **Direct LLM wrappers**: Use the [Vercel AI SDK](https://ai-sdk.dev/docs/introduction) if you want to implement your own agent from scratch.
- **Git Repo Management**: Just use git commands or the features provided by your sandbox provider of choice.
- **Sandbox Provider API**: Sandbox providers have many nuanced differences in their API, it does not make sense for us to try to provide a custom layer. Instead, we opt to provide guides that let you integrate this project with sandbox providers.

View file

@ -505,6 +505,24 @@ impl SessionManager {
Ok(EventsResponse { events, has_more })
}
async fn list_sessions(&self) -> Vec<SessionInfo> {
let sessions = self.sessions.lock().await;
sessions
.values()
.map(|state| SessionInfo {
session_id: state.session_id.clone(),
agent: state.agent.as_str().to_string(),
agent_mode: state.agent_mode.clone(),
permission_mode: state.permission_mode.clone(),
model: state.model.clone(),
variant: state.variant.clone(),
agent_session_id: state.agent_session_id.clone(),
ended: state.ended,
event_count: state.events.len() as u64,
})
.collect()
}
async fn subscribe(
&self,
session_id: &str,
@ -1247,6 +1265,25 @@ pub struct AgentListResponse {
pub agents: Vec<AgentInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct SessionInfo {
pub session_id: String,
pub agent: String,
pub agent_mode: String,
pub permission_mode: String,
pub model: Option<String>,
pub variant: Option<String>,
pub agent_session_id: Option<String>,
pub ended: bool,
pub event_count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
pub struct SessionListResponse {
pub sessions: Vec<SessionInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct HealthResponse {

View file

@ -103,7 +103,13 @@
gap: 12px;
}
.status-indicator {
.header-endpoint {
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace;
font-size: 11px;
color: var(--muted);
}
.status-indicator.disconnected {
display: flex;
align-items: center;
gap: 8px;
@ -113,14 +119,6 @@
border: 1px solid var(--border-2);
font-size: 11px;
font-weight: 600;
}
.status-indicator.connected {
color: var(--success);
border-color: rgba(48, 209, 88, 0.3);
}
.status-indicator.disconnected {
color: var(--muted);
}
@ -388,7 +386,8 @@
}
.panel-header {
padding: 10px 16px;
height: 41px;
padding: 0 16px;
border-bottom: 1px solid var(--border);
background: var(--surface-2);
flex-shrink: 0;
@ -412,13 +411,41 @@
color: var(--muted);
}
.session-badge {
.session-input {
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace;
font-size: 11px;
color: var(--text-secondary);
font-size: 12px;
color: var(--text);
background: var(--surface);
padding: 3px 8px;
border: 1px solid var(--border-2);
padding: 4px 10px;
border-radius: 4px;
outline: none;
width: 140px;
transition: border-color var(--transition);
}
.session-input:focus {
border-color: var(--accent);
}
.session-input::placeholder {
color: var(--muted-2);
}
.session-new-btn {
background: var(--accent);
border: none;
border-radius: 4px;
padding: 4px 10px;
font-size: 11px;
font-weight: 600;
color: #fff;
cursor: pointer;
transition: background var(--transition);
}
.session-new-btn:hover {
background: var(--accent-hover);
}
.messages-container {
@ -517,6 +544,16 @@
border-bottom-left-radius: 4px;
}
.message-error {
background: rgba(255, 59, 48, 0.1);
border: 1px solid rgba(255, 59, 48, 0.3);
border-radius: var(--radius-sm);
padding: 10px 14px;
color: var(--danger);
font-size: 12px;
margin-top: 8px;
}
.cursor {
display: inline-block;
width: 2px;
@ -601,6 +638,143 @@
height: 16px;
}
/* Setup Row */
.setup-row {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--surface-2);
border-top: 1px solid var(--border);
flex-shrink: 0;
flex-wrap: wrap;
}
.setup-select {
background: var(--surface);
border: 1px solid var(--border-2);
border-radius: 4px;
padding: 4px 8px;
font-size: 11px;
color: var(--text);
cursor: pointer;
outline: none;
min-width: 70px;
}
.setup-select:focus {
border-color: var(--accent);
}
.setup-input {
background: var(--surface);
border: 1px solid var(--border-2);
border-radius: 4px;
padding: 4px 8px;
font-size: 11px;
color: var(--text);
outline: none;
width: 70px;
}
.setup-input-wide {
background: var(--surface);
border: 1px solid var(--border-2);
border-radius: 4px;
padding: 4px 8px;
font-size: 11px;
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace;
color: var(--text);
outline: none;
width: 120px;
}
.setup-input::placeholder,
.setup-input-wide::placeholder {
color: var(--muted-2);
}
.setup-input:focus,
.setup-input-wide:focus {
border-color: var(--accent);
}
.setup-new-btn {
background: var(--accent);
border: none;
border-radius: 4px;
padding: 4px 10px;
font-size: 11px;
font-weight: 600;
color: #fff;
cursor: pointer;
transition: background var(--transition);
}
.setup-new-btn:hover {
background: var(--accent-hover);
}
.setup-divider {
width: 1px;
height: 16px;
background: var(--border-2);
margin: 0 4px;
}
.setup-stream {
display: flex;
align-items: center;
gap: 4px;
}
.setup-select-small {
background: var(--surface);
border: 1px solid var(--border-2);
border-radius: 4px;
padding: 4px 6px;
font-size: 10px;
color: var(--text);
cursor: pointer;
outline: none;
width: 50px;
}
.setup-stream-btn {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
background: var(--surface);
border: 1px solid var(--border-2);
border-radius: 4px;
color: var(--muted);
cursor: pointer;
transition: all var(--transition);
}
.setup-stream-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
.setup-stream-btn.active {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
.setup-version {
font-size: 10px;
color: var(--muted);
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace;
background: var(--surface);
padding: 4px 8px;
border-radius: 4px;
margin-left: auto;
}
/* Debug Panel */
.debug-panel {
display: flex;
@ -610,6 +784,7 @@
.debug-tabs {
display: flex;
height: 41px;
border-bottom: 1px solid var(--border);
background: var(--surface-2);
flex-shrink: 0;
@ -617,7 +792,8 @@
}
.debug-tab {
padding: 10px 16px;
height: 100%;
padding: 0 16px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
@ -629,6 +805,8 @@
cursor: pointer;
transition: color var(--transition), border-color var(--transition);
white-space: nowrap;
display: flex;
align-items: center;
}
.debug-tab:hover {

File diff suppressed because it is too large Load diff

View file

@ -137,6 +137,26 @@
}
}
},
"/v1/health": {
"get": {
"tags": [
"meta"
],
"operationId": "get_health",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HealthResponse"
}
}
}
}
}
}
},
"/v1/sessions/{session_id}": {
"post": {
"tags": [
@ -789,6 +809,17 @@
}
}
},
"HealthResponse": {
"type": "object",
"required": [
"status"
],
"properties": {
"status": {
"type": "string"
}
}
},
"MessageRequest": {
"type": "object",
"required": [
@ -1405,6 +1436,10 @@
}
},
"tags": [
{
"name": "meta",
"description": "Service metadata"
},
{
"name": "agents",
"description": "Agent management"

View file

@ -19,6 +19,9 @@ export interface paths {
"/v1/agents/{agent}/modes": {
get: operations["get_agent_modes"];
};
"/v1/health": {
get: operations["get_health"];
};
"/v1/sessions/{session_id}": {
post: operations["create_session"];
};
@ -117,6 +120,9 @@ export interface components {
events: components["schemas"]["UniversalEvent"][];
hasMore: boolean;
};
HealthResponse: {
status: string;
};
MessageRequest: {
message: string;
};
@ -343,6 +349,15 @@ export interface operations {
};
};
};
get_health: {
responses: {
200: {
content: {
"application/json": components["schemas"]["HealthResponse"];
};
};
};
};
create_session: {
parameters: {
path: {

View file

@ -18,6 +18,7 @@
- [x] Implement offset semantics for events (exclusive last-seen id, default offset 0)
- [x] Implement SSE endpoint for events with same semantics as JSON endpoint
- [x] Replace in-memory session store with sandbox session manager (questions/permissions routing, long-lived processes)
- [x] Remove legacy token header support
## CLI
- [x] Implement clap CLI flags: `--token`, `--no-token`, `--host`, `--port`, CORS flags