feat: restore LETTA_SDK_TOOLS=off as listen-only mode

off now means "no client-side tools" — Sub still receives transcripts
via the SDK transport but can only use memory operations. Useful for
listen-only agents that observe without filesystem access.

Written by Cameron ◯ Letta Code

"Listening is an art that requires attention over talent." - Dean Jackson
This commit is contained in:
Cameron
2026-03-13 18:19:47 -07:00
parent 8329de34a2
commit 792cd99f52
3 changed files with 12 additions and 6 deletions

View File

@@ -111,7 +111,7 @@ export LETTA_BASE_URL="http://localhost:8283" # For self-hosted Letta
export LETTA_MODEL="anthropic/claude-sonnet-4-5" # Model override
export LETTA_CONTEXT_WINDOW="1048576" # Context window size (e.g. 1M tokens)
export LETTA_HOME="$HOME" # Consolidate .letta state to ~/.letta/
export LETTA_SDK_TOOLS="read-only" # Or "full"
export LETTA_SDK_TOOLS="read-only" # Or "full", "off"
```
- `LETTA_MODE` - Controls what gets injected. `whisper` (default, messages only), `full` (blocks + messages), `off` (disable). See [Modes](#modes).
@@ -120,7 +120,7 @@ export LETTA_SDK_TOOLS="read-only" # Or "full"
- `LETTA_MODEL` - Override the agent's model. Optional - the plugin auto-detects and selects from available models. See [Model Configuration](#model-configuration) below.
- `LETTA_CONTEXT_WINDOW` - Override the agent's context window size (in tokens). Useful when `LETTA_MODEL` is set to a model with a large context window that differs from the server default. Example: `1048576` for 1M tokens.
- `LETTA_HOME` - Base directory for plugin state files. Creates `{LETTA_HOME}/.letta/claude/` for session data and conversation mappings. Defaults to current working directory. Set to `$HOME` to consolidate all state in one location.
- `LETTA_SDK_TOOLS` - Controls client-side tool access for the Subconscious agent. `read-only` (default) or `full`. See [SDK Tools](#sdk-tools).
- `LETTA_SDK_TOOLS` - Controls client-side tool access for the Subconscious agent. `read-only` (default), `full`, or `off`. See [SDK Tools](#sdk-tools).
### Modes
@@ -288,6 +288,7 @@ By default, the Subconscious agent now gets **client-side tool access** via the
|------|----------------|----------|
| `read-only` (default) | `Read`, `Grep`, `Glob`, `web_search`, `fetch_webpage` | Safe background research and file reading |
| `full` | All tools (Bash, Edit, Write, etc.) | Full autonomy — Sub can make changes |
| `off` | None (memory-only) | Listen-only — Sub processes transcripts but has no client-side tools |
> **Note:** Requires `@letta-ai/letta-code-sdk` (installed as a dependency).

View File

@@ -63,7 +63,7 @@ export function getTempStateDir(): string {
// SDK Tools Configuration
// ============================================
export type SdkToolsMode = 'read-only' | 'full';
export type SdkToolsMode = 'read-only' | 'full' | 'off';
/** Read-only tool set: safe defaults for background Sub execution */
export const SDK_TOOLS_READ_ONLY = ['Read', 'Grep', 'Glob', 'web_search', 'fetch_webpage'];
@@ -75,10 +75,11 @@ export const SDK_TOOLS_BLOCKED = ['AskUserQuestion', 'EnterPlanMode', 'ExitPlanM
* Get the SDK tools mode from LETTA_SDK_TOOLS env var.
* - read-only (default): Sub can read files and search the web
* - full: Sub has full tool access (use with caution)
* - off: No client-side tools (listen-only, memory operations only)
*/
export function getSdkToolsMode(): SdkToolsMode {
const mode = process.env.LETTA_SDK_TOOLS?.toLowerCase();
if (mode === 'full') return mode;
if (mode === 'full' || mode === 'off') return mode;
return 'read-only';
}

View File

@@ -56,15 +56,19 @@ async function sendViaSdk(payload: SdkPayload): Promise<boolean> {
sleeptime: { trigger: 'off' }, // don't recurse sleeptime
};
if (payload.sdkToolsMode === 'read-only') {
if (payload.sdkToolsMode === 'off') {
// Listen-only: block all client-side tools, Sub can only use memory operations
sessionOptions.disallowedTools = [...blockedTools, ...readOnlyTools, 'Bash', 'Edit', 'Write', 'Task', 'Glob', 'Grep', 'Read'];
} else if (payload.sdkToolsMode === 'read-only') {
sessionOptions.allowedTools = readOnlyTools;
}
// 'full' mode: no allowedTools restriction (all tools available)
const toolsLabel = payload.sdkToolsMode === 'off' ? 'none' : payload.sdkToolsMode === 'read-only' ? readOnlyTools.join(', ') : 'all';
log(`Creating SDK session for conversation ${payload.conversationId} (mode: ${payload.sdkToolsMode})`);
log(` agent: ${payload.agentId}`);
log(` cwd: ${payload.cwd}`);
log(` allowedTools: ${payload.sdkToolsMode === 'read-only' ? readOnlyTools.join(', ') : 'all'}`);
log(` allowedTools: ${toolsLabel}`);
const session = resumeSession(payload.conversationId, sessionOptions);