Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
c5167a7
feat: refactor-langgraph-to-deep-agents — OpenSpec proposal, design, …
avoidwork Jul 1, 2026
b7c22a0
feat: refactor to Deep Agents architecture
avoidwork Jul 1, 2026
a0257b8
fix: resolve lint errors and formatting issues
avoidwork Jul 1, 2026
15b5473
fix: pass configurable thread_id to Deep Agents stream call
avoidwork Jul 1, 2026
36886ef
fix: use systemPrompt from agent instance, not message array
avoidwork Jul 1, 2026
ce945e9
fix: use both updates and messages stream modes for Deep Agents
avoidwork Jul 1, 2026
0426494
fix: rewrite deepAgents.js — fix syntax error and missing variables
avoidwork Jul 1, 2026
fcd91a7
fix: remove systemPrompt prepending — agent instance handles it
avoidwork Jul 1, 2026
8ab3010
fix: remove unused namespace variable from stream loop
avoidwork Jul 1, 2026
50464cc
refactor: integrate deepagents middleware, remove LRU cache and overl…
avoidwork Jul 1, 2026
d067be4
fix: update tests for deepagents middleware integration
avoidwork Jul 1, 2026
7c28463
fix: pass permissions as [{ paths }] to createFilesystemMiddleware
avoidwork Jul 1, 2026
0b5ba25
fix: resolve permission paths to absolute paths for deepagents middle…
avoidwork Jul 1, 2026
ff23ee3
fix: filter out !node_modules exclusion from deepagents permissions
avoidwork Jul 1, 2026
248194e
fix: remove explicit middleware — deepagents adds it automatically
avoidwork Jul 1, 2026
1f2625d
chore: remove unused invokeAgent and streamAgent functions from deepA…
avoidwork Jul 1, 2026
7925a28
fix: use agent.stream() with proper await and messages stream mode
avoidwork Jul 1, 2026
fd4b1b1
WIP
avoidwork Jul 1, 2026
6c6fb87
fix: wrap streaming callback text in structured event object
avoidwork Jul 1, 2026
edadaaa
fix: wire middleware to deepAgents and fix streaming callback contract
avoidwork Jul 1, 2026
6caad37
docs: update documentation for Deep Agents in-lieu of Sub Agents
avoidwork Jul 1, 2026
4753b10
docs: remove all remaining Sub Agent references
avoidwork Jul 1, 2026
80eb388
docs: remove all subAgent tool references
avoidwork Jul 1, 2026
b2c7bf9
docs: update Built-in Tools section to reflect deepagents middleware
avoidwork Jul 2, 2026
8bdcbb5
chore: remove redundant tools replaced by deepagents middleware
avoidwork Jul 2, 2026
6965522
refactor: rename SUB_AGENT.md to CODE_AGENT.md and rewrite prompt
avoidwork Jul 2, 2026
ea953e1
chore: replace sub-agent terminology with deep agent, remove cache mo…
avoidwork Jul 2, 2026
97d6e63
chore: remove utility-agent, it duplicates the general-purpose agent …
avoidwork Jul 2, 2026
6f58584
Resolving merge conflict
avoidwork Jul 2, 2026
da239a8
WIP
avoidwork Jul 2, 2026
cccdb19
fix: remove unused imports and variables from deepAgents.js
avoidwork Jul 2, 2026
2f81aff
chore: remove unused fileBackend.js module
avoidwork Jul 2, 2026
9256930
fix: remove redundant text appended to codeAgentPrompt
avoidwork Jul 2, 2026
9a7104d
feat: add contextBackend factory for memory context directory
avoidwork Jul 2, 2026
4a704f6
docs: fix system prompt contradictions — skills via orchestrator, rem…
avoidwork Jul 2, 2026
d972c56
docs: merge SKILLS and DELEGATION into single cohesive section, remov…
avoidwork Jul 2, 2026
4c23dec
docs: rewrite TASK EXECUTION section without tool names
avoidwork Jul 2, 2026
141ebd2
feat: compose backends with CompositeBackend — core + context
avoidwork Jul 2, 2026
41f5648
feat: add shellBackend factory for LocalShellBackend
avoidwork Jul 2, 2026
8c9ff1c
fix: set shellBackend rootDir to /
avoidwork Jul 2, 2026
fbd2325
feat: add InMemoryStore to deepAgents orchestrator
avoidwork Jul 2, 2026
71ef77e
feat: give coding sub-agent its own shellBackend via createSubAgent
avoidwork Jul 2, 2026
41b7e70
refactor: rename toolsDir to subAgentsDir, toolsBackend to subAgentsB…
avoidwork Jul 2, 2026
b00a13e
fix: prefix unused cwd param with _ in shellBackend
avoidwork Jul 2, 2026
eb70b5f
fix: pass model to coding subagent createSubAgent
avoidwork Jul 2, 2026
c3b1a45
fix: pass tools to coding subagent createSubAgent
avoidwork Jul 2, 2026
f70d63a
fix: remove duplicate tools parameter in createDeepAgentsOrchestrator
avoidwork Jul 2, 2026
8d66402
feat: add createFilesystemMiddleware to coding sub-agent, fix duplica…
avoidwork Jul 2, 2026
214ad64
chore: remove shellBackend — terminal tool handles shell execution
avoidwork Jul 2, 2026
7fef9cb
feat: add createMemoryMiddleware with subAgentsBackend to coding sub-…
avoidwork Jul 2, 2026
aa11463
feat: ensure memory/sub-agents directory exists in createSubAgentsBac…
avoidwork Jul 2, 2026
ec92b33
refactor: drive backend paths from config instead of hardcoded
avoidwork Jul 2, 2026
b28445d
refactor: drive CompositeBackend routing key from config
avoidwork Jul 2, 2026
7996c70
chore: remove dead toolsDir from config
avoidwork Jul 2, 2026
6f68690
fix: add subAgentsDir to memory schema and default config
avoidwork Jul 2, 2026
4547ebb
chore: remove unused toolsDir from memory schema
avoidwork Jul 2, 2026
d82f02a
fix: expose full cwd in coreBackend by disabling virtualMode
avoidwork Jul 2, 2026
f90b7c2
chore: remove --cwd CLI arg and process.chdir from index.js
avoidwork Jul 2, 2026
8bb9bfc
docs: remove --cwd CLI arg from README
avoidwork Jul 2, 2026
8845761
refactor: move system prompt loading into orchestrator, use deepagent…
avoidwork Jul 2, 2026
b7393f5
refactor: make orchestrator fully self-contained, load system prompt …
avoidwork Jul 2, 2026
c0e2825
feat: wire up deepagents skills middleware, discover skills in orches…
avoidwork Jul 2, 2026
25b653f
chore: format index.js
avoidwork Jul 2, 2026
1621813
refactor: move model and tools loading into orchestrator
avoidwork Jul 2, 2026
10e6add
feat: add sessionDir to config.memory, use it in orchestrator
avoidwork Jul 2, 2026
c90870d
refactor: use join with config paths, remove fallbacks
avoidwork Jul 2, 2026
6044af5
fix: rename sessionDir to sessionsDir in config, schema, and orchestr…
avoidwork Jul 2, 2026
32f1666
fix: await buildToolConfig in orchestrator
avoidwork Jul 2, 2026
26e4ab9
fix: restore providerConfig in index.js for TUI
avoidwork Jul 2, 2026
25ae5eb
fix: remove duplicate providerName declaration
avoidwork Jul 2, 2026
203aabe
fix: use config.memory.contextDir for PROFILE_DIR in profile.js
avoidwork Jul 2, 2026
958d0ac
feat: add summarization, todo, and patch middleware to coding subagent
avoidwork Jul 2, 2026
1a1cc69
fix: use subAgentsBackend for summarization middleware
avoidwork Jul 2, 2026
5845f33
chore: remove unused imports from index.js
avoidwork Jul 2, 2026
3e7bcdc
chore: remove compactContext tool and COMPACTION.md (built into deepa…
avoidwork Jul 2, 2026
e3c0291
docs: remove stale ReAct agent references from architecture docs
avoidwork Jul 2, 2026
ee6113a
fix: remove unused event types from TUI streaming callbacks
avoidwork Jul 2, 2026
e0b51cb
fix: use native message event type in TUI streaming
avoidwork Jul 2, 2026
ed68282
fix: close if block in TUI streaming callback
avoidwork Jul 2, 2026
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
70 changes: 28 additions & 42 deletions README.md

Large diffs are not rendered by default.

17 changes: 6 additions & 11 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ sandbox:
memory:
directory: memory/
contextDir: memory/context/
toolsDir: memory/tools/
subAgentsDir: memory/sub-agents/
errorsDir: memory/errors/
schedulesDir: memory/schedules/
sessionsDir: memory/sessions/
gc:
enabled: true
idleTimeoutMs: 300000
Expand Down Expand Up @@ -75,19 +76,13 @@ agent:
recursionLimit: 1000
autoContinueLimit: 1000
nodeTimeout: 600000
turnHashWindow: 20
turnBufferMax: 64
deepAgents:
codingAgent:
description: "Specialized agent for code-related tasks including file editing, debugging, and implementation."
temperature: 0.3
lru:
size: 100
ttl: 600000
process:
subAgent:
timeout: 600000
maxConcurrent: 4
sessionMode: isolated
defaultStrategy: parallel
defaultOnError: continue
temperature: 0.7
persistence:
mode: memory
sqlite_path: memory/checkpoints.db
Expand Down
211 changes: 55 additions & 156 deletions docs/FLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,14 @@ Call chains and data flows for all primary code paths in the project, excluding
- [File Tool Execution Flow](#file-tool-execution-flow)
- [Terminal Tool Execution Flow](#terminal-tool-execution-flow)
- [Web Tool Execution Flow](#web-tool-execution-flow)
- [Sub-Agent Tool Execution Flow](#sub-agent-tool-execution-flow)
- [Deep Agents Orchestration Flow](#deep-agents-orchestration-flow)
- [Sandbox Skill Execution](#sandbox-skill-execution)
- [Memory Persistence Flow](#memory-persistence-flow)
- [Context Loading](#context-loading)
- [Schedule Manager Lifecycle](#schedule-manager-lifecycle)
- [Memory Retention Cleanup](#memory-retention-cleanup)
- [Profile Management](#profile-management)
- [Shutdown Flow](#shutdown-flow)
- [Sub-Agent Log Tool Flow](#sub-agent-log-tool-flow)
- [Sub-Agent Message Tool Flow](#sub-agent-message-tool-flow)
- [Additional Tool Flows](#additional-tool-flows)
- [File Dependencies](#file-dependencies)

Expand Down Expand Up @@ -408,46 +406,6 @@ createChatModel(config)
})
```

## Agent ReAct Streaming

**Entry:** `src/agent/react.js` → `callReactAgent(..., streamingCallback)`

```
callReactAgent(agent, message, config, systemPrompt, callback)
├── messages = [HumanMessage(message)]
├── if systemPrompt && isNewThread → messages = [SystemMessage(systemPrompt), ...messages]
└── callReactAgentStreaming(agent, messages, message, config, callback)
├── stream = await agent.streamEvents(
│ │ { messages },
│ │ { version: "v2", configurable: config.configurable }
│ │ )
│ └── v2 event stream protocol
├── for await (event of stream):
│ ├── event.event === "on_chat_model_stream":
│ │ ├── textContent = chunk.content (string or block.text)
│ │ └── callback({ type: "text", text: textContent })
│ ├── chunk.reasoning:
│ │ └── callback({ type: "reasoning", text: chunk.reasoning })
│ ├── event.event === "on_tool_start" && name === "tool":
│ │ └── for each tc in input.tool_calls:
│ │ └── if not duplicate → callback({ type: "tool_start", toolName, toolCallId })
│ ├── event.event === "on_tool_end" && name === "tool":
│ │ └── callback({ type: "tool_end", toolName, toolCallId, data: result.slice(0,500) })
│ └── event.event === "on_tool_error" && name === "tool":
│ └── callback({ type: "tool_error", toolName, toolCallId, error })
├── for each remaining key in toolCallSet → callback({ type: "tool_end", toolName })
└── return { content: originalMessage } -- fallback
```

**Context length error handling:** Both `callReactAgent` and `callReactAgentStreaming` are wrapped in a retry loop (max 3 iterations). When the LLM returns a 400 error matching context-length patterns (`/maximum\s+context\s+length[^0-9]*?(\d+)\s*tokens?/i` or `/limit[:\s]*(\d+)/i`), the system:

1. Extracts the max context length from the error message
2. Calculates `targetTokens = maxContextLength - maxTokens`
3. Compacts the conversation via `compactConversation()` using tiered retention
4. Rebuilds messages from compacted result and retries

If all 3 iterations fail, the user receives: "The conversation is too long. Please start a new session."

---

## Context Compaction
Expand Down Expand Up @@ -668,64 +626,28 @@ Multi-engine search backends (webSearch):
```


## Sub-Agent Tool Execution Flow
## Deep Agents Orchestration Flow
## Deep Agents Orchestration Flow

**Entry:** `src/tools/subAgent.js` → `createSubAgentTool()`
**Entry:** `src/agent/deepAgents.js` → `createDeepAgentsOrchestrator()`

```
subAgent tool (zero-permission, always registered):
├── validate input: delegation (required), context (optional), tasks (optional for fan-out), cwd (optional)
├── if tasks provided (fan-out mode):
│ ├── for each task in tasks (bounded by maxConcurrent):
│ │ ├── spawn("node", ["index.js", "--sub-agent=true", `--cwd=${targetCwd}`, `--message="${prompt}"`])
│ │ ├── trackProcess(child, command) → { pid, child, status: "running", startTime }
│ │ ├── wait for completion or timeout (resolveTimeout: per-call > env > config)
│ │ └── parseSubAgentOutput(stdout) → { ok, result, error?, pid? }
│ │ └── Split on "# SubAgent" marker, parse JSON after marker
│ ├── if strategy === "sequential": wait for each to complete before next
│ ├── if strategy === "parallel": run up to maxConcurrent simultaneously
│ └── if onError === "fail-fast": abort remaining on first error
│ └── if onError === "continue": collect errors, return all results
├── else (single execution mode):
│ ├── spawn("node", ["index.js", "--sub-agent=true", `--cwd=${targetCwd}`, `--message="${prompt}"`])
│ ├── trackProcess(child, command) → { pid, child, status: "running", startTime }
│ ├── wait for completion or timeout
│ └── parseSubAgentOutput(stdout) → { ok, result, error?, pid? }
├── if returnParams provided:
│ └── filter result to only include specified keys
│ └── fallback to full text if not valid JSON
└── return { ok, result, error?, pid? }

escapeShellArg(arg):
├── Replace backticks, dollar signs, single quotes, double quotes
├── Escape newlines, tabs, carriage returns
└── Wrap in double quotes for safe shell passing

parseSubAgentOutput(stdout):
├── Split stdout on "# SubAgent" marker
├── Take content after marker
├── Try JSON.parse(content)
├── if valid JSON → { ok: true, result: parsed }
├── else → { ok: false, error: "Failed to parse sub-agent output" }

resolveTimeout(options):
├── if options.timeout provided → options.timeout
└── else → config.process.subAgent.timeout (default 600000)
```

**Process tracking:** Sub-agents share the `processTracker` Map from `terminal.js` for PID tracking and lifecycle management. Each sub-agent gets a unique PID that can be polled, waited on, or killed via the `process` tool.

**Session isolation modes:**

| Mode | Description |
|------|-------------|
| `isolated` | Fresh session, no parent context |
| `forked` | Forked from parent session with compaction |
| `shared` | Shared parent session context |

---
Deep Agents orchestrator (native multi-agent architecture):
├── createDeepAgent({ model, systemPrompt, tools, middleware, subagents, checkpointer })
│ ├── middleware: filesystem, memory, skills, summarization
│ ├── subagents:
│ │ ├── coding-agent: code editing, debugging, implementation, code review
│ └── orchestrator routes tasks automatically based on task nature
├── agent.stream(input, { streamMode: "messages", subgraphs: true })
│ ├── for each chunk:
│ │ ├── extract text content
│ │ └── streamingCallback({ type: "text", text })
│ └── returns { provider, content, tokens }
└── orchestrator manages routing, state, and observability natively

## Scan Agents Tool Flow
No process spawning, no marker-based parsing, no manual fan-out coordination.
The deepagents library handles agent lifecycle, state management, and streaming internally.
```

**Entry:** `src/tools/scanAgents.js` → `createScanAgentsTool()`

Expand Down Expand Up @@ -789,65 +711,8 @@ runScheduledSkill(schedule, sandbox, sessionState)

```

## Sub-Agent Log Tool Flow
## Deep Agents Log Management

**Entry:** `src/tools/subAgentLog.js` → `createSubAgentLogTool()`

```
subAgentLog tool (zero-permission, always registered):
├── validate input: action (required), pid (optional), maxAgeHours (optional)
├── switch action:
│ ├── "list":
│ │ ├── readdir("/tmp") → filter files matching "sub-agent-{pid}.log"
│ │ ├── for each log file:
│ │ │ ├── stat(filePath) → size, mtime
│ │ │ ├── isProcessRunning(pid) → process.kill(pid, 0)
│ │ │ └── { pid, file, size, modified, running }
│ │ └── sort by modified (descending) → return { ok: true, logs }
│ ├── "read":
│ │ ├── if pid missing → { ok: false, error: "PID is required" }
│ │ ├── readFile("/tmp/sub-agent-{pid}.log") → content
│ │ └── return { ok: true, pid, content }
│ └── "cleanup":
│ ├── readdir("/tmp") → filter "sub-agent-{pid}.log"
│ ├── for each file:
│ │ ├── stat(filePath) → mtimeMs
│ │ ├── if age > maxAgeHours * 60 * 60 * 1000 → unlinkSync
│ │ └── removed++
│ └── return { ok: true, removed }
└── default → { ok: false, error: "Unknown action" }

isProcessRunning(pid):
├── process.kill(pid, 0) → true (signal 0 checks existence)
└── catch → false
```

**Log file pattern:** `sub-agent-{pid}.log` stored in `/tmp`. Files are automatically cleaned up by the `cleanup` action based on age threshold.

---

## Sub-Agent Message Tool Flow

**Entry:** `src/tools/subAgentMessage.js` → `createSubAgentMessageTool()`

```
subAgentMessage tool (requires process:spawn permission):
├── validate input: pid (required), message (required)
├── if pid missing → { ok: false, error: "PID is required" }
├── if message missing → { ok: false, error: "Message is required" }
├── lookup processTracker.get(pid):
│ ├── if not found → { ok: false, error: "Process {pid} not found in tracker" }
│ └── if status is "exited" or "error" → { ok: false, error: "Process {pid} is not running" }
├── entry.child.stdin.write(message + "\\n")
│ └── Append newline to message before writing
└── return { ok: true, pid, messageSent: true }
```

**Prerequisites:** The target subAgent process must be spawned with `stdio: ["pipe", "pipe", "pipe"]` (stdin exposed). The subAgent tool was updated to expose stdin for this to work.

---

## Additional Tool Flows

### Code Execution

Expand Down Expand Up @@ -1202,7 +1067,7 @@ index.js
│ ├── tools/moa.js → OPENROUTER_API_KEY — mixture-of-agents (4 parallel OpenRouter calls + aggregation)
│ ├── tools/cron.js → node:fs/promises — cron job CRUD operations
│ ├── tools/compactContext.js → @langchain/core, zod — automatic conversation context compaction on LLM 400 errors (tiered retention, retry loop, error detection)
── tools/subAgentLog.js → node:fs/promises, node:path — subAgent log management (list, read, cleanup); zero-permission, always registered
── tools/...
│ └── tools/...
├── sandbox/pathResolver.js → node:path
├── sandbox/urlFilter.js → node:url
Expand All @@ -1229,3 +1094,37 @@ index.js
├── session/onboarding.js → session/stateManager.js — onboarding state machine (INIT → ATTRACTOR → COLLECT → SAVE → TRANSCEND)
└── telemetry/provider.js → @opentelemetry/sdk-node
```
I TTS API
│ ├── tools/moa.js → OPENROUTER_API_KEY — mixture-of-agents (4 parallel OpenRouter calls + aggregation)
│ ├── tools/cron.js → node:fs/promises — cron job CRUD operations
│ ├── tools/compactContext.js → @langchain/core, zod — automatic conversation context compaction on LLM 400 errors (tiered retention, retry loop, error detection)
│ └── tools/...
│ └── tools/...
├── sandbox/pathResolver.js → node:path
├── sandbox/urlFilter.js → node:url
├── sandbox/runner.js → node:child_process, sandbox/timeoutHandler.js, envInjector.js, capability.js
├── registry/registry.js → discoverer.js, validator.js
├── registry/discoverer.js → js-yaml, node:fs, node:path
├── registry/validator.js → types.js (zod schemas)
├── registry/types.js → zod
├── scheduler/scheduler.js → node:fs/promises — ScheduleManager CRUD class (register, list, pause, resume, runNow)
├── scheduler/cron.js → node:child_process, node:fs/promises, node:path — Cron object (isAvailable, add, remove)
├── scheduler/autoSchedule.js → node:fs — setupAutoSchedule() callback for reflection-daily cron
├── scheduler/index.js → re-exports ScheduleManager and Cron
├── memory/writer.js → node:fs, node:path
├── memory/reader.js → js-yaml, node:fs
├── memory/context.js → node:fs, node:path, memory/reader.js
├── memory/retention.js → node:fs, node:path
├── memory/prompts.js → node:fs
├── memory/profile.js → node:fs — user profile management: ATTRIBUTES, loadProfile, saveProfile, formatProfileContext, processOnboardingInput
├── session/factory.js → node:crypto (randomUUID)
├── session/stateManager.js
├── session/checkpointer.js → @langchain/langgraph, @langchain/langgraph-checkpoint-sqlite — createCheckpointer() returns MemorySaver (in-memory) or SQLiteCheckpointer (persistent)
├── session/loader.js → fs, path, memory/reader.js
├── session/saver.js → fs, path, memory/writer.js
├── session/onboarding.js → session/stateManager.js — onboarding state machine (INIT → ATTRACTOR → COLLECT → SAVE → TRANSCEND)
└── telemetry/provider.js → @opentelemetry/sdk-node
```
→ SAVE → TRANSCEND)
└── telemetry/provider.js → @opentelemetry/sdk-node
```
Loading