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
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "claude-subconscious",
"version": "1.5.1",
"version": "2.0.0",
"description": "A subconscious for Claude Code. A Letta agent watches your sessions, accumulates context, and whispers guidance back.",
"author": {
"name": "Letta",
Expand Down
48 changes: 10 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ 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_CHECKPOINT_MODE="blocking" # Or "async", "off"
export LETTA_SDK_TOOLS="read-only" # Or "full", "off"
```

Expand All @@ -121,8 +120,7 @@ export LETTA_SDK_TOOLS="read-only" # Or "full", "off"
- `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_CHECKPOINT_MODE` - Controls checkpoint behavior at natural pause points (`AskUserQuestion`, `ExitPlanMode`). See [Checkpoint Hooks](#checkpoint-hooks).
- `LETTA_SDK_TOOLS` - Controls client-side tool access for the Subconscious agent. 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

Expand Down Expand Up @@ -255,9 +253,8 @@ The plugin uses four Claude Code hooks:
|------|--------|---------|---------|
| `SessionStart` | `session_start.ts` | 5s | Notifies agent, cleans up legacy CLAUDE.md |
| `UserPromptSubmit` | `sync_letta_memory.ts` | 10s | Injects memory + messages via stdout |
| `PreToolUse` (checkpoint) | `plan_checkpoint.ts` | 10s | Sends transcript at `AskUserQuestion`/`ExitPlanMode` |
| `PreToolUse` (general) | `pretool_sync.ts` | 5s | Mid-workflow updates via `additionalContext` |
| `Stop` | `send_messages_to_letta.ts` | 15s | Spawns background worker to send transcript |
| `PreToolUse` | `pretool_sync.ts` | 5s | Mid-workflow updates via `additionalContext` |
| `Stop` | `send_messages_to_letta.ts` | 120s | Spawns SDK worker to send transcript (async) |

### SessionStart

Expand All @@ -273,7 +270,6 @@ Before each prompt is processed:
- Fetches agent's current memory blocks and messages
- In `full` mode: injects all blocks on first prompt, diffs on subsequent prompts
- In `whisper` mode: injects only messages from Sub
- Sends user prompt to Letta early (gives the agent a head start)

### PreToolUse

Expand All @@ -282,29 +278,6 @@ Before each tool use:
- If updates found, injects them via `additionalContext`
- Silent no-op if nothing changed

### Checkpoint Hooks

At certain "natural pause points" — when Claude asks a question (`AskUserQuestion`) or finishes planning (`ExitPlanMode`) — the plugin sends the current transcript to Letta so your Subconscious can provide guidance before Claude proceeds.

**Why this matters:** Normally, Letta only sees transcripts when Claude stops responding (via the Stop hook). Checkpoint hooks let your Subconscious intervene at decision points:
- Before the user answers a question Claude asked
- Before implementation begins after a plan is approved

**Configuration via `LETTA_CHECKPOINT_MODE`:**

| Mode | Behavior |
|------|----------|
| `blocking` (default) | Wait for Letta response (~2-5s), inject as `additionalContext` before tool executes |
| `async` | Fire-and-forget; guidance arrives on next `UserPromptSubmit` |
| `off` | Disable checkpoint hooks; only Stop hook sends transcripts |

In blocking mode, Letta's response is injected as:
```xml
<letta_message checkpoint="AskUserQuestion">
Consider asking about X before proceeding...
</letta_message>
```

### SDK Tools

By default, the Subconscious agent now gets **client-side tool access** via the [Letta Code SDK](https://docs.letta.com/letta-code/sdk/). Instead of being limited to memory operations, Sub can read your files, search the web, and explore your codebase while processing transcripts.
Expand All @@ -315,9 +288,9 @@ 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) | Legacy behavior, raw API transport |
| `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). Set `LETTA_SDK_TOOLS=off` to use the legacy raw API path without the SDK.
> **Note:** Requires `@letta-ai/letta-code-sdk` (installed as a dependency).

### Stop

Expand All @@ -330,9 +303,9 @@ Uses an **async hook** pattern — runs in the background without blocking Claud
- Spawns detached background worker
- Exits immediately

2. Background worker runs independently:
- **SDK mode** (`send_worker_sdk.ts`): Opens a Letta Code SDK session, giving Sub client-side tools
- **Legacy mode** (`send_worker.ts`): Sends via raw API (memory-only)
2. Background worker (`send_worker_sdk.ts`) runs independently:
- Opens a Letta Code SDK session, giving Sub client-side tools
- Sub processes the transcript and can use Read/Grep/Glob to explore the codebase
- Updates state on success
- Cleans up temp file

Expand All @@ -353,9 +326,8 @@ Persisted in your project directory (this is **conversation bookkeeping**, not a
Log files for debugging:
- `session_start.log` - Session initialization
- `sync_letta_memory.log` - Memory sync operations
- `plan_checkpoint.log` - Checkpoint hooks (AskUserQuestion/ExitPlanMode)
- `send_messages.log` - Main Stop hook
- `send_worker.log` - Background worker
- `send_worker_sdk.log` - SDK background worker

## What Your Agent Receives

Expand Down Expand Up @@ -447,7 +419,7 @@ tail -f /tmp/letta-claude-sync-$(id -u)/*.log

# Or specific logs
tail -f /tmp/letta-claude-sync-$(id -u)/send_messages.log
tail -f /tmp/letta-claude-sync-$(id -u)/send_worker.log
tail -f /tmp/letta-claude-sync-$(id -u)/send_worker_sdk.log
```

## API Notes
Expand Down
12 changes: 6 additions & 6 deletions Subconscious.af

Large diffs are not rendered by default.

10 changes: 0 additions & 10 deletions hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@
}
],
"PreToolUse": [
{
"matcher": "AskUserQuestion|ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/silent-npx.cjs\" tsx \"${CLAUDE_PLUGIN_ROOT}/scripts/plan_checkpoint.ts\"",
"timeout": 10
}
]
},
{
"matcher": "*",
"hooks": [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "claude-subconscious",
"version": "1.5.1",
"version": "2.0.0",
"description": "A subconscious for Claude Code. A Letta agent watches your sessions, accumulates context, and whispers guidance back.",
"author": "Letta <hello@letta.com> (https://letta.com)",
"license": "MIT",
Expand Down
37 changes: 1 addition & 36 deletions scripts/conversation_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ 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: Legacy mode, no SDK — raw API only (memory-only Sub)
* - off: No client-side tools (listen-only, memory operations only)
*/
export function getSdkToolsMode(): SdkToolsMode {
const mode = process.env.LETTA_SDK_TOOLS?.toLowerCase();
Expand Down Expand Up @@ -314,41 +314,6 @@ export function lookupConversation(cwd: string, sessionId: string): string | nul
}
}

/**
* Send a message to a Letta conversation (fire-and-forget style)
* Returns the response for the caller to handle
*/
export async function sendMessageToConversation(
apiKey: string,
conversationId: string,
role: string,
text: string,
log: LogFn = noopLog
): Promise<Response> {
const url = `${LETTA_API_BASE}/conversations/${conversationId}/messages`;

log(`Sending ${role} message to conversation ${conversationId} (${text.length} chars)`);

const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: [
{
role: role,
content: text,
}
],
}),
});

log(`Response status: ${response.status}`);
return response;
}

// ============================================
// Agent and Memory Block Types
// ============================================
Expand Down
Loading