Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ Two kinds:
- **Interactive:** Long-running, accepts follow-up input from the channel. Coding sessions, complex multi-step tasks.

Workers are pluggable. A worker can be:
- A Rig agent with shell/file/exec tools
- A Rig agent with shell/file tools
- An OpenCode subprocess
- Any external process that accepts a task and reports status

**Tools:** shell, file, exec, set_status (varies by worker type)
**Tools:** shell, file, set_status (varies by worker type)
**Context:** Fresh prompt + task description. No channel history.
**Lifecycle:** Fire-and-forget or long-running. Reports status via `set_status` tool.

Expand Down Expand Up @@ -176,9 +176,8 @@ src/
│ ├── memory_recall.rs— search + curate memories (branch only)
│ ├── channel_recall.rs— retrieve transcript from any channel (branch only)
│ ├── set_status.rs — update worker status (workers only)
│ ├── shell.rs — execute shell commands (task workers)
│ ├── shell.rs — execute shell commands and subprocesses (task workers)
│ ├── file.rs — read/write/list files (task workers)
│ ├── exec.rs — run subprocess (task workers)
│ ├── browser.rs — web browsing (task workers)
│ ├── task_create.rs — create task-board task (branch + cortex chat)
│ ├── task_list.rs — list task-board tasks (branch + cortex chat)
Expand Down Expand Up @@ -292,7 +291,7 @@ let branch_history = channel_history.clone();
**ToolServer topology:**
- Per-channel `ToolServer` (no memory tools, just channel action tools added per turn)
- Per-branch `ToolServer` with memory tools (memory_save, memory_recall, memory_delete), channel recall, docs introspection (`spacebot_docs`), and task-board tools
- Per-worker `ToolServer` with task-specific tools (shell, file, exec)
- Per-worker `ToolServer` with task-specific tools (shell, file)
- Per-cortex `ToolServer` with memory_save

**Max turns:** Rig defaults to 0 (single call). Always set explicitly.
Expand Down
37 changes: 32 additions & 5 deletions interface/src/components/ToolCall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function isErrorResult(
): boolean {
if (parsed?.error) return true;
if (parsed?.status === "error") return true;
// Shell/exec structured results: { success: false } or non-zero exit code
// Shell structured results: { success: false } or non-zero exit code
if (parsed?.success === false) return true;
if (typeof parsed?.exit_code === "number" && parsed.exit_code !== 0) return true;
const lower = text.toLowerCase();
Expand Down Expand Up @@ -504,16 +504,43 @@ const toolRenderers: Record<string, ToolRenderer> = {
},
},

// Legacy exec tool — kept for rendering old transcripts. The exec tool was
// merged into shell; new transcripts will only have "shell" calls. This
// renderer maps exec's structured args (program + args array) into the
// same display format as shell.
exec: {
summary(pair) {
const command = pair.args?.command;
if (!command) return null;
const program = pair.args?.program;
const cmdArgs = pair.args?.args;
if (!program) return null;
const parts = [String(program)];
if (Array.isArray(cmdArgs)) {
for (const arg of cmdArgs) parts.push(String(arg));
}
const full = parts.join(" ");
if (pair.result && typeof pair.result.exit_code === "number") {
const code = pair.result.exit_code;
const cmdStr = truncate(String(command), 50);
const cmdStr = truncate(full, 50);
return code === 0 ? cmdStr : `${cmdStr} (exit ${code})`;
}
return truncate(String(command), 60);
return truncate(full, 60);
},
argsView(pair) {
const program = pair.args?.program;
if (!program) return null;
const parts = [String(program)];
const cmdArgs = pair.args?.args;
if (Array.isArray(cmdArgs)) {
for (const arg of cmdArgs) parts.push(String(arg));
}
return (
<div className="border-b border-app-line/20 px-3 py-2">
<pre className="max-h-40 overflow-auto font-mono text-tiny text-ink-dull">
<span className="select-none text-ink-faint">$ </span>
{parts.join(" ")}
</pre>
</div>
);
},
resultView(pair) {
if (!pair.resultRaw) return null;
Expand Down
4 changes: 2 additions & 2 deletions interface/src/routes/AgentConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const SECTIONS: {
{ id: "memory", label: "Memory Persistence", group: "config", description: "Auto-save interval", detail: "Spawns a silent background branch at regular intervals to recall existing memories and save new ones from the recent conversation. Runs without blocking the channel." },
{ id: "browser", label: "Browser", group: "config", description: "Chrome automation", detail: "Controls browser automation tools available to workers. When enabled, workers can navigate web pages, take screenshots, and interact with sites. JavaScript evaluation is a separate permission." },
{ id: "channel", label: "Channel Behavior", group: "config", description: "Reply behavior", detail: "Listen-only mode suppresses unsolicited replies in busy channels. The agent still responds to slash commands, @mentions, and replies to its own messages." },
{ id: "sandbox", label: "Sandbox", group: "config", description: "Process containment", detail: "OS-level filesystem containment for shell and exec tool subprocesses. When enabled, worker processes run inside a kernel-enforced sandbox (bubblewrap on Linux, sandbox-exec on macOS) with an allowlist-only filesystem — only system paths, the workspace, and explicitly configured extra paths are accessible." },
{ id: "sandbox", label: "Sandbox", group: "config", description: "Process containment", detail: "OS-level filesystem containment for shell tool subprocesses. When enabled, worker processes run inside a kernel-enforced sandbox (bubblewrap on Linux, sandbox-exec on macOS) with an allowlist-only filesystem — only system paths, the workspace, and explicitly configured extra paths are accessible." },
{ id: "projects", label: "Projects", group: "config", description: "Workspace management", detail: "Controls how the agent manages project workspaces, git repos, and worktrees. Use worktrees for parallel feature branches, auto-discover to scan for repos on project creation, and set a disk usage warning threshold." },
];

Expand Down Expand Up @@ -1016,7 +1016,7 @@ function ConfigSectionEditor({ sectionId, label, description, detail, config, on
<div className="grid gap-4">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-ink">Mode</label>
<p className="text-tiny text-ink-faint">Kernel-enforced filesystem containment for shell and exec subprocesses.</p>
<p className="text-tiny text-ink-faint">Kernel-enforced filesystem containment for shell subprocesses.</p>
<Select
value={localValues.mode as string}
onValueChange={(v) => handleChange("mode", v)}
Expand Down
2 changes: 1 addition & 1 deletion prompts/en/branch.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Forget a memory by ID. Use this when the user wants something removed, or when y
Read embedded Spacebot docs, including `AGENTS.md`, `CHANGELOG.md`, and product docs from `docs/content/`. Use `action: "list"` to discover IDs, then `action: "read"` for the specific document.

### spawn_worker
If the user wants something done now and it needs execution tools (shell, file, exec), spawn a worker. Give it a specific task description with enough context to work independently. The worker won't have the conversation history — it only knows what you tell it. If the user is describing something for later rather than requesting immediate action, save a **todo** memory instead.
If the user wants something done now and it needs execution tools (shell, file), spawn a worker. Give it a specific task description with enough context to work independently. The worker won't have the conversation history — it only knows what you tell it. If the user is describing something for later rather than requesting immediate action, save a **todo** memory instead.

### task_create
Create a task on the board. The description is a **markdown spec** — write it like instructions for a worker who has no conversation context. Include requirements, constraints, file paths, examples, and anything the executor needs. Always pre-fill subtasks as a checklist execution plan.
Expand Down
4 changes: 2 additions & 2 deletions prompts/en/channel.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ Never suggest that the user do something you could do yourself. If someone asks
## Builtin Worker Sandbox

{%- if sandbox_enabled %}
Sandbox mode is **enabled** for builtin workers, so `shell`/`exec` run with OS-level containment while `file` remains workspace-scoped by path validation.
Sandbox mode is **enabled** for builtin workers, so `shell` runs with OS-level containment while `file` remains workspace-scoped by path validation.
{%- else %}
Sandbox mode is **disabled** for builtin workers, so `shell`/`exec`/`file`/`send_file` all have full host filesystem access (OS permissions apply). Environment sanitization still applies.
Sandbox mode is **disabled** for builtin workers, so `shell`/`file`/`send_file` all have full host filesystem access (OS permissions apply). Environment sanitization still applies.
{%- endif %}

When an interactive worker is active and the user's message is directed at that work, route the message to the worker instead of spawning a new one.
Expand Down
3 changes: 1 addition & 2 deletions prompts/en/fragments/worker_capabilities.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ When you spawn a worker, it runs independently with a task description and retur
{%- endif %}

**Builtin worker tools:**
- **shell** — run shell commands
- **shell** — run shell commands (supports per-command environment variables via `env` parameter)
- **file** — read, write, search, and list files
- **exec** — run subprocesses with environment control
- **set_status** — update worker status visible in your status block
{%- if browser_enabled %}
- **browser_*** — suite of browser tools: navigate, snapshot, click, type, screenshot, press_key, evaluate, tab management
Expand Down
1 change: 0 additions & 1 deletion prompts/en/tools/exec_description.md.j2

This file was deleted.

4 changes: 3 additions & 1 deletion prompts/en/tools/shell_description.md.j2
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Execute a shell command. Use this for file operations, running scripts, building projects, git commands, and any system-level operations. Be careful with destructive operations. The command runs with a 60 second timeout by default.
Execute a shell command. Use this for file operations, running scripts, building projects, git commands, running subprocesses, and any system-level operations. Be careful with destructive operations. The command runs with a 60 second timeout by default.

Use the optional `env` parameter to set per-command environment variables (e.g. `[{"key": "RUST_LOG", "value": "debug"}]`). Dangerous variables that enable library injection (LD_PRELOAD, NODE_OPTIONS, etc.) are blocked.

To install tools that persist across restarts, place binaries in the persistent tools directory at $SPACEBOT_DIR/tools/bin (already on PATH). For example: `curl -fsSL https://example.com/tool -o $SPACEBOT_DIR/tools/bin/tool && chmod +x $SPACEBOT_DIR/tools/bin/tool`
16 changes: 6 additions & 10 deletions prompts/en/worker.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ Binaries installed via package managers (apt, brew, etc.) land on the root files
{%- if sandbox_containment_active %}
Sandbox mode is **enabled** with active OS-level containment.

Read allowlist for `shell`/`exec`:
Read allowlist for `shell`:
{%- for path in sandbox_read_allowlist %}
- `{{ path }}`
{%- endfor %}

Write allowlist for `shell`/`exec`:
Write allowlist for `shell`:
{%- for path in sandbox_write_allowlist %}
- `{{ path }}`
{%- endfor %}

The agent data directory is blocked from `shell`/`exec` subprocess reads and writes.
The agent data directory is blocked from `shell` subprocess reads and writes.
{%- elif sandbox_enabled %}
Sandbox mode is **enabled** in config, but no supported backend is available on this host, so `shell`/`exec` run in passthrough (no OS-level filesystem containment).
Sandbox mode is **enabled** in config, but no supported backend is available on this host, so `shell` runs in passthrough (no OS-level filesystem containment).
{%- else %}
Sandbox mode is **disabled**.

- `shell`/`exec` subprocesses run without OS-level filesystem containment.
- `shell` subprocesses run without OS-level filesystem containment.
- Host filesystem access follows the OS permissions of the Spacebot process.
- Environment sanitization still applies (clean env + explicit passthrough/tool vars).
- The file tools (`file_read`, `file_write`, `file_edit`, `file_list`) can access any path readable by the process (no workspace restriction).
Expand Down Expand Up @@ -81,7 +81,7 @@ Examples:

### shell

Execute shell commands. Use this for running builds, tests, git operations, package management, and any system commands.
Execute shell commands. Use this for running builds, tests, git operations, package management, and any system commands. Supports optional `env` parameter for setting per-command environment variables (e.g. `RUST_LOG=debug`).

### File tools (file_read, file_write, file_edit, file_list)

Expand All @@ -94,10 +94,6 @@ Four separate tools for file operations:

Path restrictions apply: you cannot write to identity files (SOUL.md, IDENTITY.md, USER.md) or memory storage paths. Use the appropriate system tools for those.

### exec

Run a subprocess with specific arguments. Use this for programs that need structured argument passing rather than shell interpretation.

### Browser tools (browser_*)

Automate a headless Chrome browser. Each action is a separate tool.
Expand Down
2 changes: 1 addition & 1 deletion src/agent/cortex_chat.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Cortex chat: persistent admin conversation with the cortex.
//!
//! One session per agent. The admin talks to the cortex interactively,
//! with the full toolset (memory, shell, file, exec, browser, web search).
//! with the full toolset (memory, shell, file, browser, web search).
//! When opened on a channel page, the channel's recent history is injected
//! into the system prompt as context.

Expand Down
2 changes: 1 addition & 1 deletion src/opencode/worker.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! OpenCode worker: drives an OpenCode session for coding tasks.
//!
//! Instead of running a Rig agent loop with shell/file/exec tools, this worker
//! Instead of running a Rig agent loop with shell/file tools, this worker
//! delegates to an OpenCode subprocess that has its own codebase exploration,
//! context management, and tool suite. Communication happens over HTTP + SSE.

Expand Down
1 change: 0 additions & 1 deletion src/prompts/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ fn lookup(lang: &str, key: &str) -> &'static str {
("en", "tools/file_list") => {
include_str!("../../prompts/en/tools/file_list_description.md.j2")
}
("en", "tools/exec") => include_str!("../../prompts/en/tools/exec_description.md.j2"),
("en", "tools/browser") => include_str!("../../prompts/en/tools/browser_description.md.j2"),
("en", "tools/web_search") => {
include_str!("../../prompts/en/tools/web_search_description.md.j2")
Expand Down
Loading
Loading