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
16 changes: 11 additions & 5 deletions docs/content/docs/(features)/tools.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Tools
description: All 16 tools that give LLM processes the ability to act.
description: Tools that give LLM processes the ability to act.
---

# Tools
Expand All @@ -11,7 +11,7 @@ How Spacebot gives LLM processes the ability to act.

Every tool implements Rig's `Tool` trait and lives in `src/tools/`. Tools are organized by function, not by consumer. Which process gets which tools is configured via ToolServer factory functions in `src/tools.rs`.

All 16 tools:
Core tools include:

| Tool | Purpose | Consumers |
|------|---------|-----------|
Expand All @@ -25,6 +25,7 @@ All 16 tools:
| `memory_save` | Write a memory to the store | Branch, Cortex, Compactor |
| `memory_recall` | Search memories via hybrid search | Branch |
| `channel_recall` | Retrieve transcript from another channel | Branch |
| `email_search` | Search IMAP mailbox content directly | Branch |
| `set_status` | Report worker progress to the channel | Worker |
| `shell` | Execute shell commands | Worker |
| `file` | Read, write, and list files | Worker |
Expand Down Expand Up @@ -71,10 +72,11 @@ Each branch gets its own isolated ToolServer, created at spawn time via `create_
│ memory_save (Arc<MemorySearch>) │
│ memory_recall (Arc<MemorySearch>) │
│ channel_recall (ConversationLogger) │
│ email_search (IMAP mailbox search) │
└──────────────────────────────────────────────┘
```

Branch isolation ensures `memory_recall` and `channel_recall` are never visible to the channel. All tools are registered at creation and live for the lifetime of the branch.
Branch isolation ensures `memory_recall`, `channel_recall`, and `email_search` are never visible to the channel. All tools are registered at creation and live for the lifetime of the branch.

### Worker ToolServer (per-worker)

Expand Down Expand Up @@ -136,7 +138,7 @@ create_cortex_tool_server(memory_search) -> ToolServerHandle

### Static tools (registered at creation)

`memory_save`, `memory_recall`, `channel_recall` on branch ToolServers. `shell`, `file`, `exec` on worker ToolServers. `memory_save` on cortex and compactor ToolServers. These are registered before `.run()` via the builder pattern and live for the lifetime of the ToolServer.
`memory_save`, `memory_recall`, `channel_recall`, `email_search` on branch ToolServers. `shell`, `file`, `exec` on worker ToolServers. `memory_save` on cortex and compactor ToolServers. These are registered before `.run()` via the builder pattern and live for the lifetime of the ToolServer.

### Dynamic tools (added/removed at runtime)

Expand All @@ -153,7 +155,7 @@ create_cortex_tool_server(memory_search) -> ToolServerHandle

### Per-process tools (created and destroyed with the process)

Branch and worker ToolServers are created when the process spawns and dropped when it finishes. Each branch gets `memory_save` + `memory_recall` + `channel_recall`. Each worker gets `shell`, `file`, `exec`, `set_status` (bound to that worker's ID), and optionally `browser`.
Branch and worker ToolServers are created when the process spawns and dropped when it finishes. Each branch gets `memory_save` + `memory_recall` + `channel_recall` + `email_search`. Each worker gets `shell`, `file`, `exec`, `set_status` (bound to that worker's ID), and optionally `browser`.

## Tool Design Patterns

Expand Down Expand Up @@ -228,6 +230,10 @@ If the name doesn't match any channel, falls back to list mode so the LLM can se

Channel names are resolved from the `discord_channel_name` field stored in message metadata. The tool queries `conversation_messages` in SQLite directly — it reads persisted messages, not in-memory Rig history.

### email_search

Searches the configured email mailbox directly over IMAP with filters like sender (`from`), subject, text query, unread-only, and time window (`since_days`). Returns message metadata plus a body snippet for precise read-back in email workflows.

### set_status

Reports the worker's current progress. The status string appears in the channel's status block so the user-facing process knows what's happening without polling.
Expand Down
15 changes: 14 additions & 1 deletion docs/content/docs/(messaging)/email-setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ When the Email adapter is configured, you can intentionally send email from a no

This keeps email channels inbound-only while still allowing deliberate outbound send workflows.

## Search mailbox content (`email_search`)

Branches can use `email_search` to query IMAP directly when you ask for exact read-back details.

Examples:

- "Find emails from Alice about renewal in the last 14 days"
- "Search unread emails mentioning invoice"
- "Look for subject containing Q1 roadmap"

Use specific filters (`from`, `subject`, `query`, `since_days`) to avoid broad mailbox scans.

## Thread behavior

Spacebot keeps one conversation per email thread. It uses `References`, `In-Reply-To`, and `Message-ID` headers to map replies back to the correct conversation.
Expand Down Expand Up @@ -134,7 +146,8 @@ Use a longer interval if your provider rate limits IMAP polling.

1. Send an email to the configured mailbox from an allowed sender.
2. Confirm a new channel appears in Spacebot with a subject-based name.
3. Reply in the same thread and confirm Spacebot replies in-thread.
3. Reply in the same thread and confirm the message is ingested into the same conversation thread (no automated reply by default).
4. If direct email replies are explicitly enabled in your setup, verify outbound replies preserve threading headers.

## Troubleshooting

Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/(messaging)/messaging.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,4 @@ curl -X POST http://localhost:18789/webhook \

## Hot Reloading

Changes to bindings and permissions (channel filters, DM allowed users) take effect within a couple seconds — no restart needed. Token and credential changes are applied by reconnecting the adapter.
Changes to bindings and permissions (channel filters, DM allowed users) take effect within a couple of seconds — no restart needed. Token and credential changes are applied by reconnecting the adapter.
3 changes: 3 additions & 0 deletions prompts/en/tools/email_search_description.md.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Search the configured IMAP mailbox for specific emails and return matching results with metadata and body snippets. Use this when you need to read back what someone said in email, verify details from a previous message, or find messages by sender/subject/topic.

Prefer specific filters (`from`, `subject`, `query`, `since_days`) instead of broad searches. Keep `limit` small unless the user asks for a wider sweep.
1 change: 1 addition & 0 deletions src/agent/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,7 @@ async fn spawn_branch(
state.deps.agent_id.clone(),
state.deps.task_store.clone(),
state.deps.memory_search.clone(),
state.deps.runtime_config.clone(),
state.conversation_logger.clone(),
state.channel_store.clone(),
crate::conversation::ProcessRunLogger::new(state.deps.sqlite_pool.clone()),
Expand Down
1 change: 1 addition & 0 deletions src/agent/ingestion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ async fn process_chunk(
deps.agent_id.clone(),
deps.task_store.clone(),
deps.memory_search.clone(),
deps.runtime_config.clone(),
conversation_logger,
channel_store,
crate::conversation::ProcessRunLogger::new(deps.sqlite_pool.clone()),
Expand Down
8 changes: 7 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2618,11 +2618,17 @@ impl Config {
pub fn load() -> Result<Self> {
let instance_dir = Self::default_instance_dir();

Self::load_for_instance(&instance_dir)
}

/// Load configuration for a specific instance directory.
pub fn load_for_instance(instance_dir: &Path) -> Result<Self> {
let config_path = instance_dir.join("config.toml");

if config_path.exists() {
Self::load_from_path(&config_path)
} else {
Self::load_from_env(&instance_dir)
Self::load_from_env(instance_dir)
}
}

Expand Down
Loading