DeepAgents Reference (internal)¶
Working reference for the DeepAgents 0.6.1 SDK Quoriv is built on. Source:
.venv/Lib/site-packages/deepagents/Read this before building anything that might overlap with the SDK — most of what looks like "agent infrastructure" is already provided.
TL;DR — what DeepAgents gives you for free¶
| Feature | Built-in? | Mechanism |
|---|---|---|
| Planning / todo list | ✅ | TodoListMiddleware → write_todos tool |
| File ops (read/write/edit/ls/glob/grep) | ✅ | FilesystemMiddleware |
| Shell execute | ✅ | SandboxBackendProtocol.execute (when backend supports it) |
| Sub-agents (sync + remote/async) | ✅ | SubAgentMiddleware, AsyncSubAgentMiddleware → task tool |
| Auto general-purpose subagent | ✅ | Inserted automatically unless overridden |
| Context compaction | ✅ | SummarizationMiddleware (token-aware) |
| Human-in-the-loop / approval | ✅ | HumanInTheLoopMiddleware via interrupt_on={...} |
| Filesystem permissions (allow/deny rules) | ✅ | FilesystemPermission evaluated by FilesystemMiddleware |
| Persistent project/user memory | ✅ | MemoryMiddleware (AGENTS.md spec) |
| Anthropic-style skills | ✅ | SkillsMiddleware (SKILL.md with YAML frontmatter) |
| Anthropic prompt caching | ✅ | AnthropicPromptCachingMiddleware (always on; no-ops elsewhere) |
| Multi-provider model resolution | ✅ | init_chat_model(...) + ProviderProfile |
| Per-model harness tuning | ✅ | HarnessProfile |
| Streaming | ✅ | Compiled LangGraph graph |
| Checkpointing / session persistence | ✅ | Pass checkpointer= (any LangGraph Checkpointer) |
| Structured output | ✅ | response_format= |
Quoriv adds: CLI/TUI, config layer, OS keychain, multi-tier permission UX, AST tools, git tools, web tools, MCP client, cost tracking, our Phase-3 providers, and the Rich-based renderers. Nothing more agent-runtime-y than that.
Public API surface¶
from deepagents import (
create_deep_agent, # main entry point
SubAgent, # TypedDict spec for a sync subagent
CompiledSubAgent, # TypedDict wrapping a pre-built runnable
AsyncSubAgent, # TypedDict for remote/async subagent
SubAgentMiddleware,
AsyncSubAgentMiddleware,
SubagentTransformer,
SubagentRunStream,
AsyncSubagentRunStream,
FilesystemMiddleware,
FilesystemPermission,
MemoryMiddleware,
HarnessProfile,
HarnessProfileConfig,
GeneralPurposeSubagentProfile,
ProviderProfile,
register_harness_profile,
register_provider_profile,
__version__, # currently "0.6.1"
)
Also useful (not in top-level __all__ but stable):
from deepagents.backends import (
BackendProtocol,
StateBackend, # default — files in LangGraph state (ephemeral)
FilesystemBackend, # real disk (no sandbox)
LocalShellBackend, # real disk + real shell (FilesystemBackend + SandboxBackendProtocol)
StoreBackend, # LangGraph BaseStore
LangSmithSandbox, # remote sandbox via LangSmith
ContextHubBackend, # LangChain context hub
CompositeBackend, # combine multiple
BackendContext,
NamespaceFactory,
DEFAULT_EXECUTE_TIMEOUT, # 120s
)
from deepagents.middleware import (
SkillsMiddleware,
SummarizationMiddleware,
SummarizationToolMiddleware,
create_summarization_tool_middleware,
)
create_deep_agent — the entry point¶
create_deep_agent(
model: str | BaseChatModel | None = None,
tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,
*,
system_prompt: str | SystemMessage | None = None,
middleware: Sequence[AgentMiddleware] = (),
subagents: Sequence[SubAgent | CompiledSubAgent | AsyncSubAgent] | None = None,
skills: list[str] | None = None,
memory: list[str] | None = None,
permissions: list[FilesystemPermission] | None = None,
backend: BackendProtocol | BackendFactory | None = None,
interrupt_on: dict[str, bool | InterruptOnConfig] | None = None,
response_format: ResponseFormat[ResponseT] | type[ResponseT] | dict[str, Any] | None = None,
context_schema: type[ContextT] | None = None,
checkpointer: Checkpointer | None = None,
store: BaseStore | None = None,
debug: bool = False,
name: str | None = None,
cache: BaseCache | None = None,
) -> CompiledStateGraph
Returns a compiled LangGraph graph with:
- recursion_limit=9999 (effectively unbounded loops by default)
- Metadata: ls_integration="deepagents", version tag, optional lc_agent_name
Key parameters in plain language¶
| Param | What it does | Quoriv mapping |
|---|---|---|
model |
"provider:name" string OR pre-built BaseChatModel. None is deprecated (will be removed in 1.0.0) — always pass explicitly. |
Use our quoriv.models.get_model() to build the BaseChatModel, then pass instance. |
tools |
Additional tools merged with the built-ins. Additive only — to drop a built-in, use HarnessProfile.excluded_tools. |
Pass our AST/git/web/MCP tools here. |
system_prompt |
User prefix; sits at the front of the final prompt. Can be SystemMessage to preserve Anthropic cache_control markers. |
Empty for v1; later we can pass project-specific guidance. |
middleware |
Extra middleware inserted between the base stack and the tail stack. | Mostly leave empty; we add custom only if we need cross-call interception. |
subagents |
Specs for delegated workers. Three types (see below). | Define researcher/debugger/reviewer subagents in Phase 2. |
skills |
Paths to skill directories (POSIX, relative to backend root). | Could map to project .quoriv/skills/ + user ~/.quoriv/skills/. |
memory |
Paths to AGENTS.md files. Loaded at startup, injected into system prompt. | Map our PROJECT.md + ~/.quoriv/memory.md here. |
permissions |
List of FilesystemPermission rules for the main agent (subagents inherit unless overridden). |
Translate our read-only/ask/auto/yolo mode into this list. |
backend |
File storage + (optional) execution. Default is StateBackend(). |
For Quoriv we want LocalShellBackend(root_dir=cwd) so reads/writes/shell hit the real repo. |
interrupt_on |
{"edit_file": True, ...} — pause for approval before listed tools. |
Map our ask/auto modes to this dict. |
response_format |
Pydantic/TypedDict for structured output. | Not used in v1. |
checkpointer |
Persist conversation state across runs. | Use langgraph.checkpoint.sqlite.SqliteSaver pointed at our session DB. |
store |
Optional persistent KV store (required if backend is StoreBackend). |
Not used unless we choose StoreBackend. |
Always-on middleware stack (in order)¶
TodoListMiddleware
SkillsMiddleware (if skills=)
FilesystemMiddleware ← REQUIRED scaffolding; cannot be excluded
SubAgentMiddleware ← REQUIRED scaffolding; cannot be excluded (when subagents exist)
AsyncSubAgentMiddleware (if any AsyncSubAgent in subagents=)
SummarizationMiddleware
PatchToolCallsMiddleware
─── user middleware= inserted here ───
(HarnessProfile.extra_middleware)
_ToolExclusionMiddleware (if profile has excluded_tools)
AnthropicPromptCachingMiddleware ← unconditional; no-ops for non-Anthropic
MemoryMiddleware (if memory=)
HumanInTheLoopMiddleware (if interrupt_on=)
Two are required scaffolding — FilesystemMiddleware and SubAgentMiddleware. Excluding them via HarnessProfile.excluded_middleware raises ValueError.
Built-in tools¶
Always present (unless filtered by HarnessProfile.excluded_tools):
| Tool | Source | Notes |
|---|---|---|
write_todos |
TodoListMiddleware |
LangChain SDK's built-in; manages a markdown-style todo list. |
ls |
FilesystemMiddleware |
Lists files; returns FileInfo dicts with path, is_dir, size, modified_at. |
read_file |
FilesystemMiddleware |
cat -n style output; default 2000-line limit; supports offset/limit. |
write_file |
FilesystemMiddleware |
Errors if file exists. |
edit_file |
FilesystemMiddleware |
Exact string replacement; replace_all flag; old_string must be unique unless replace_all. |
glob |
FilesystemMiddleware |
wcmatch globs with **, *, ?, [abc]. POSIX paths. |
grep |
FilesystemMiddleware |
Literal substring match (NOT regex). Optional path + glob filters. Returns GrepMatch (path, line, text). |
execute |
FilesystemMiddleware (delegates to SandboxBackendProtocol.execute) |
Only registered if the backend implements SandboxBackendProtocol. Returns ExecuteResponse(output, exit_code, truncated). |
task |
SubAgentMiddleware |
Only registered if at least one synchronous subagent exists (the default general-purpose counts). |
Important nuance: grep is literal substring, not regex. If we want regex search we add our own tool.
How tools see the filesystem¶
All built-in file tools talk to a BackendProtocol, not to disk directly. The backend decides where bytes actually live (state, real disk, sandbox, remote, etc.). Custom Quoriv tools can use the same backend by calling runtime.context_state.backend (via ToolRuntime) for consistency, or hit disk directly if we don't care.
Backends (file storage + optional execution)¶
| Backend | Storage | execute()? |
When to use |
|---|---|---|---|
StateBackend (default) |
LangGraph state — ephemeral within thread, persists via checkpointer. | ❌ | Demos, tests, anywhere disk shouldn't be touched. |
FilesystemBackend |
Real disk under root_dir. |
❌ | Local coding tools that want disk reads but no shell. |
LocalShellBackend |
Real disk + real shell. No sandbox. | ✅ | What Quoriv uses. Local dev CLIs. |
StoreBackend |
LangGraph BaseStore (Postgres, Redis, etc.). |
❌ | Web/server deployments wanting persistent files. |
LangSmithSandbox |
Remote sandbox over LangSmith. | ✅ | Cloud sandbox execution. |
ContextHubBackend |
LangChain context hub. | ❌ | Hosted file context. |
CompositeBackend |
Combines multiple backends with path-based routing. | depends on parts | Layer real disk + state, etc. |
SandboxBackendProtocol extends BackendProtocol with:
- id: str — unique identifier
- execute(command, *, timeout=None) -> ExecuteResponse
- aexecute(command, *, timeout=None) -> ExecuteResponse
execute() returns:
@dataclass
class ExecuteResponse:
output: str # combined stdout + stderr
exit_code: int | None # None if unknown
truncated: bool # backend-side truncation flag
DEFAULT_EXECUTE_TIMEOUT = 120 seconds.
Quoriv choice: LocalShellBackend(root_dir=<cwd>)¶
For Quoriv's "live in your terminal, edit your real files" UX, LocalShellBackend is the right default. It gives us:
- Real file reads/writes/edits on disk
- Real shell execute for tests, git, builds
- HITL gating via our permission modes
Security risks (per DeepAgents docs) are exactly the ones we already plan to mitigate: HITL approval, path protection (.env, ~/.ssh), command allowlists.
Middleware (composable features)¶
DeepAgents differentiates two tool paths:
- SDK middleware — intercepts every LLM request via
wrap_model_call(). Can inject tools, modify system prompt per-call, filter tools dynamically, track cross-turn state. - Plain tools (in
tools=[]) — stateless functions the LLM can call. No interception.
Use middleware when you need: - Modify system prompt or tool list per call - Cross-turn state - Dynamic tool filtering
Use plain tools when: - Function is stateless - No per-call modification needed - Tool is consumer-specific
Built-in middleware¶
| Middleware | Effect | Where it lives |
|---|---|---|
TodoListMiddleware |
Adds write_todos tool; tracks a todo list across turns. |
langchain.agents.middleware |
SkillsMiddleware |
Loads SKILL.md files; injects skill instructions into system prompt; exposes any allowed_tools declared per skill. |
deepagents.middleware.skills |
FilesystemMiddleware |
Adds ls/read_file/write_file/edit_file/glob/grep/execute tools; enforces FilesystemPermission rules. |
deepagents.middleware.filesystem |
SubAgentMiddleware |
Adds task tool; manages declarative SubAgent specs. |
deepagents.middleware.subagents |
AsyncSubAgentMiddleware |
Adds async-subagent tools (launch_task, check_task, update_task, cancel_task, list_tasks) for remote/long-running subagents. |
deepagents.middleware.async_subagents |
SummarizationMiddleware |
Counts tokens; truncates old tool calls; replaces history with summaries when window fills. Model-aware. | langchain.agents.middleware (created via create_summarization_middleware(model, backend)) |
PatchToolCallsMiddleware |
Patches malformed tool calls before they reach the runtime. | deepagents.middleware.patch_tool_calls |
MemoryMiddleware |
Loads AGENTS.md files; injects into system prompt; optional Anthropic cache_control. | deepagents.middleware.memory |
HumanInTheLoopMiddleware |
Pauses before listed tools per interrupt_on= config. Requires a checkpointer. |
langchain.agents.middleware |
AnthropicPromptCachingMiddleware |
Marks cache breakpoints on Anthropic models; no-ops for others. | langchain_anthropic.middleware |
_ToolExclusionMiddleware |
Filters tools listed in HarnessProfile.excluded_tools. Private. |
deepagents.middleware._tool_exclusion |
Writing custom middleware¶
Subclass AgentMiddleware from langchain.agents.middleware.types. Override:
- wrap_model_call(request, handler) — intercept every LLM request
- on_message(message, state) — observe new messages
- Define a typed State for cross-turn persistence
Custom middleware is rarely needed for Quoriv — almost everything we want hangs off tools=, interrupt_on=, permissions=, or memory=.
Subagents¶
Three flavors, distinguished by the keys they declare:
1. SubAgent — declarative synchronous¶
SubAgent = TypedDict(
"SubAgent",
{
"name": str, # required, used as the task() target
"description": str, # required, helps main agent route
"system_prompt": str, # required
"tools": NotRequired[...], # inherits main agent's tools if omitted
"model": NotRequired[str | BaseChatModel],
"middleware": NotRequired[list[AgentMiddleware]],
"interrupt_on": NotRequired[dict[str, bool | InterruptOnConfig]],
"skills": NotRequired[list[str]],
"permissions": NotRequired[list[FilesystemPermission]],
"response_format": NotRequired[...],
},
)
Invoked through the task tool. Subagents get the full base middleware stack auto-applied (TodoList, Filesystem, Summarization, PatchToolCalls, etc.) before any middleware you specify.
2. CompiledSubAgent — pre-built runnable¶
CompiledSubAgent = TypedDict("CompiledSubAgent", {
"name": str,
"description": str,
"runnable": Runnable, # a pre-compiled LangGraph runnable
})
The runnable's state schema must include messages. The final message becomes the ToolMessage content returned to the parent.
3. AsyncSubAgent — remote/background¶
AsyncSubAgent = TypedDict("AsyncSubAgent", {
"name": str,
"description": str,
"graph_id": str, # identifies remote graph (e.g., LangSmith deployment)
"url": NotRequired[str],
"headers": NotRequired[dict[str, str]],
})
Routed to AsyncSubAgentMiddleware. Exposes async tools (launch_task, check_task, etc.) instead of a single task call. Useful for long-running background work.
Auto general-purpose subagent¶
Unless you provide a subagent named general-purpose (or the active HarnessProfile.general_purpose_subagent.enabled = False), DeepAgents auto-adds one with:
- name:
"general-purpose" - model: same as main agent
- tools: inherits main agent's tools
- All base middleware applied
To run without the task tool, set general_purpose_subagent=GeneralPurposeSubagentProfile(enabled=False) on a registered profile AND pass no synchronous subagents.
Permission inheritance¶
Subagent permissions resolution:
1. If SubAgent["permissions"] set → use it, replacing parent rules entirely.
2. Else → inherit create_deep_agent(permissions=...) from main agent.
Same logic applies to interrupt_on (declarative SubAgent inherits; CompiledSubAgent and AsyncSubAgent do not inherit).
Profiles¶
Two registries, both beta APIs:
ProviderProfile — model construction¶
Controls what init_chat_model does. Built-in profiles:
- OpenAI — defaults to Responses API; honors OPENAI_API_KEY.
- OpenRouter — adds app attribution headers.
Register with register_provider_profile().
HarnessProfile — agent shaping¶
Controls what create_deep_agent does after the model exists. Built-in profiles for:
- claude-sonnet-4-6
- claude-opus-4-7
- claude-haiku-4-5
- OpenAI Codex models
HarnessProfile(
base_system_prompt: str | None = None, # replaces BASE if set
system_prompt_suffix: str | None = None, # always appended last
excluded_tools: set[str] | None = None, # filter by tool name
excluded_middleware: list[type | str] = [] # filter by class or .name (NOT scaffolding)
extra_middleware: Sequence[AgentMiddleware | Callable[[ModelRequest], AgentMiddleware]] = (),
tool_description_overrides: dict[str, str] = {}, # rewrite tool descriptions
general_purpose_subagent: GeneralPurposeSubagentProfile | None = None,
)
Register with register_harness_profile(). Additive merge — re-registering under an existing key layers on top.
Permission system — FilesystemPermission¶
@dataclass
class FilesystemPermission:
operations: list[FilesystemOperation] # ["read"] | ["write"] | ["read", "write"]
paths: list[str] # wcmatch globs; MUST start with "/"; no ".." or "~"
mode: Literal["allow", "deny"] = "allow"
Where FilesystemOperation = Literal["read", "write"].
Built-in tool → operation mapping¶
{
"ls": "read",
"read_file": "read",
"glob": "read",
"grep": "read",
"write_file": "write",
"edit_file": "write",
}
Evaluation¶
- Rules evaluated in declaration order; first match wins.
- If no rule matches → call is allowed.
executeis not gated by this system — gate it separately viainterrupt_onor by choosing the backend.
Example¶
permissions = [
FilesystemPermission(operations=["write"], paths=["/.env", "/.env.*"], mode="deny"),
FilesystemPermission(operations=["write"], paths=["/.git/**"], mode="deny"),
FilesystemPermission(operations=["read", "write"], paths=["/secrets/**"], mode="deny"),
]
Human-in-the-Loop — interrupt_on¶
interrupt_on = {
"edit_file": True, # boolean = simple pause
"write_file": True,
"execute": InterruptOnConfig(
# full config for richer prompts; see langchain.agents.middleware
),
}
Pauses agent execution at the listed tool calls, surfacing the call to a checkpointed thread for user approval. Requires a checkpointer=.
Inheritance:
- Declarative SubAgent inherits parent's interrupt_on unless it sets its own.
- CompiledSubAgent does not inherit.
- AsyncSubAgent does not inherit.
Quoriv mapping of permission modes¶
| Mode | permissions |
interrupt_on |
|---|---|---|
read-only |
Deny all write paths (["/**"] write) |
(irrelevant — writes are blocked) |
ask |
Path protection only (.env, .git, etc.) |
{"write_file": True, "edit_file": True, "execute": True} |
auto |
Path protection | {"execute": True} (auto-approve writes, prompt for shell) |
yolo |
Path protection (still enforce critical denylist) | {} (no prompts) |
Memory (AGENTS.md)¶
memory= parameter takes paths to markdown files. Format: free-form markdown — no required structure. Loaded once at agent startup, concatenated in order (later sources after earlier), injected into the system prompt under <agent_memory>...</agent_memory>.
Conventional sources (per Anthropic's AGENTS.md spec):
memory=[
"~/.quoriv/AGENTS.md", # or PROJECT.md / memory.md — naming is free
"./.quoriv/AGENTS.md",
]
State key: memory_contents: dict[path → content] (private state attribute, not exposed in final state).
Anthropic prompt caching: MemoryMiddleware automatically applies cache_control breakpoints when an Anthropic model is in use. Safe to enable unconditionally.
Skills (SKILL.md)¶
skills= parameter takes paths to skill source directories (not individual files). Each skill is a directory containing SKILL.md with YAML frontmatter:
---
name: web-research
description: Structured approach to conducting thorough web research
license: MIT
allowed_tools: [web_search, web_fetch]
---
# Web Research Skill
## When to Use
- User asks you to research a topic
...
Sources are scanned in order; last wins on name collisions (so layering goes: built-in → user → project → team).
Special source labeling:
- Bare path: label = Path(source).name.capitalize()
- Special case: leaf skills climbs one level (~/.claude/skills → Claude, not Skills)
- Special case: leaf built_in_skills → Built-in
- Pass (path, label) tuple to disambiguate manually
Skills vs. Memory¶
| Skills | Memory | |
|---|---|---|
| Loaded into prompt | Lazily (progressive disclosure) | Always, in full |
| Format | SKILL.md with YAML frontmatter | Plain markdown (free-form) |
| Granularity | Named, discoverable | Just dumped in |
| Use case | Optional workflows ("how to do X") | Project facts & conventions |
Prompt assembly¶
Final system prompt = up to four named parts, in this order:
| Part | Source |
|---|---|
USER |
create_deep_agent(system_prompt=...). Always at the front, so caller instructions take precedence. |
BASE |
The hardcoded BASE_AGENT_PROMPT constant. Used when no profile sets base_system_prompt. |
CUSTOM |
HarnessProfile.base_system_prompt. When set, replaces BASE entirely. |
SUFFIX |
HarnessProfile.system_prompt_suffix. Always last, so model-tuning sits closest to the conversation. |
Joined by blank lines (\n\n).
Passing system_prompt as a SystemMessage preserves cache_control markers and appends right-hand content as an extra text block.
The default BASE_AGENT_PROMPT is opinionated:
- Be concise
- No preamble ("Sure!", "I'll now...")
- Understand → act → verify cycle
- Don't ask for details already supplied
- Brief progress updates for long tasks
For Quoriv we can pass our own system_prompt to layer project conventions on top, but probably leave the BASE alone unless we have a strong reason.
State & checkpointing¶
DeepAgents uses a custom _DeepAgentState extending AgentState with a DeltaChannel on messages (snapshots every 50 messages). This keeps checkpoint storage at O(N) instead of O(N²) for long sessions.
Pass any LangGraph Checkpointer to enable resumability:
from langgraph.checkpoint.sqlite import SqliteSaver
with SqliteSaver.from_conn_string("./.quoriv/sessions.db") as saver:
agent = create_deep_agent(model=..., checkpointer=saver)
result = agent.invoke({"messages": [...]}, config={"configurable": {"thread_id": "session-42"}})
Required for: interrupt_on= (HITL), session resume.
Model resolution¶
- If
modelis aBaseChatModel→ returned as-is. - If
modelis a string →init_chat_model(spec, **apply_provider_profile(spec)). init_chat_modelunderstands"provider:model"(e.g.,"openai:gpt-4.1","anthropic:claude-sonnet-4-6","ollama:qwen2.5-coder:32b").
OpenAI gotcha: openai: specs default to the Responses API. To use chat completions:
from langchain.chat_models import init_chat_model
model = init_chat_model("openai:gpt-4.1", use_responses_api=False)
agent = create_deep_agent(model=model, ...)
What DeepAgents does NOT provide (Quoriv must build)¶
- CLI / TUI — DeepAgents returns a
CompiledStateGraph; we drive it with Rich + prompt_toolkit. - Config files — DeepAgents takes parameters at construction time; we load them from TOML.
- OS keychain — we already built
quoriv.config.keychain. - Tree-sitter / AST tools — not in scope for DeepAgents; we add as plain tools.
- Git tools — not in scope.
- Web search/fetch — not in scope.
- MCP client — DeepAgents has no MCP integration; we connect to MCP servers and expose their tools via
tools=[]. - Cost tracking — LangSmith helps but we want a local
/costdashboard. - Trace export — LangGraph emits events; we wire UI/log subscribers.
- Approval prompt rendering —
interrupt_onpauses execution but the UI for "approve / deny / edit" is our responsibility. - Multi-tier permission MODES UX — we wrap
permissions=+interrupt_on=into our 4-mode model. - Streaming renderer — LangGraph streams events; Rich renders them.
Quoriv-specific reuse plan (replaces parts of the original plan)¶
Don't build, USE¶
| Original plan item | DeepAgents replacement |
|---|---|
quoriv.tools.files (read/write/edit/etc.) |
Built-in via FilesystemMiddleware |
quoriv.tools.search.grep |
Built-in (literal substring; not regex) |
quoriv.tools.shell.execute |
Built-in via LocalShellBackend |
quoriv.tools.patch |
Use edit_file (or multi_edit if we add it as a custom tool) |
quoriv.core.runtime (the loop) |
DeepAgents' compiled graph IS the loop |
quoriv.core.context (compaction) |
Built-in via SummarizationMiddleware |
quoriv.memory.project / memory.user |
Pass paths via memory= |
quoriv.permissions.guard / paths |
Map to FilesystemPermission + path-deny rules |
| Sub-agents (researcher/debugger/etc.) | Define as SubAgent dicts |
| Session save/resume | LangGraph SqliteSaver + thread_id |
| Streaming events | LangGraph compiled graph's astream_events() |
DO build (truly Quoriv-specific)¶
| Module | Purpose |
|---|---|
quoriv.cli |
Typer entry point + slash commands |
quoriv.app |
Main Rich loop driving the compiled graph |
quoriv.ui.* |
Streaming, diff, prompts, status, theme renderers |
quoriv.config.* |
Already done — TOML + keychain |
quoriv.models.* |
Already done — we build the BaseChatModel to pass to DeepAgents |
quoriv.permissions.modes |
Translate our 4 modes → permissions= + interrupt_on= |
quoriv.tools.ast_tools |
tree-sitter symbol/find_def/find_refs (plain tools) |
quoriv.tools.git |
git status/diff/log/commit/blame (plain tools) |
quoriv.tools.web |
web_search, web_fetch (plain tools) |
quoriv.tools.tests |
Language-aware test runner (plain tool) |
quoriv.plugins.mcp.* |
MCP client; connects and exposes server tools |
quoriv.plugins.api |
Python plugin entry-point loader |
quoriv.observability.cost |
Token/$ tracking via callback handlers |
quoriv.observability.trace |
Local JSON trace export |
quoriv.repo.ast |
tree-sitter parsers (powering ast_tools) |
Stays as Quoriv design but maps to DeepAgents primitives¶
| Quoriv abstraction | DeepAgents primitive |
|---|---|
quoriv.core.agent builder |
Wraps create_deep_agent |
quoriv.core.routing (per-task model routing) |
Implement by giving each SubAgent its own model= |
quoriv.core.events |
Subscribe to LangGraph stream events |
Common patterns / snippets¶
Minimal Quoriv-style agent¶
from deepagents import create_deep_agent, FilesystemPermission
from deepagents.backends import LocalShellBackend
from langgraph.checkpoint.sqlite import SqliteSaver
from quoriv.models import get_model
from quoriv.config import load_config
cfg = load_config()
model = get_model(cfg.model.default)
backend = LocalShellBackend(root_dir=".")
permissions = [
FilesystemPermission(operations=["write"], paths=["/.env", "/.env.*"], mode="deny"),
FilesystemPermission(operations=["write"], paths=["/.git/**"], mode="deny"),
FilesystemPermission(operations=["read", "write"], paths=["/.ssh/**"], mode="deny"),
]
with SqliteSaver.from_conn_string("./.quoriv/sessions.db") as saver:
agent = create_deep_agent(
model=model,
backend=backend,
permissions=permissions,
memory=["./PROJECT.md"] if Path("./PROJECT.md").is_file() else None,
interrupt_on={"write_file": True, "edit_file": True, "execute": True},
checkpointer=saver,
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "Read README.md and summarize."}]},
config={"configurable": {"thread_id": "session-1"}},
)
Streaming events¶
async for event in agent.astream_events(
{"messages": [...]},
config={"configurable": {"thread_id": "..."}},
version="v2",
):
# event["event"] is one of: "on_chat_model_start", "on_chat_model_stream",
# "on_chat_model_end", "on_tool_start", "on_tool_end", etc.
handle(event)
Adding a custom plain tool¶
from langchain_core.tools import tool
@tool
def find_symbol(name: str, language: str = "python") -> list[dict]:
"""Find a symbol definition across the repo using tree-sitter."""
return ... # quoriv.repo.symbols implementation
agent = create_deep_agent(model=..., tools=[find_symbol], ...)
Defining a specialist subagent¶
researcher: SubAgent = {
"name": "researcher",
"description": "Reads many files to answer questions about how the codebase works. Use for 'where is X?' or 'how does Y work?'",
"system_prompt": "You are a thorough code researcher. Read broadly, cite specific files and line numbers.",
"model": "openai:gpt-4o-mini", # cheaper model for read-heavy work
# tools and permissions inherited from main agent
}
debugger: SubAgent = {
"name": "debugger",
"description": "Investigates failing tests or runtime errors. Forms hypotheses, runs commands to test them.",
"system_prompt": "You are a debugging specialist. Use the scientific method.",
"model": "openai:gpt-4.1",
}
agent = create_deep_agent(model=..., subagents=[researcher, debugger], ...)
Disabling the auto general-purpose subagent¶
from deepagents import register_harness_profile, GeneralPurposeSubagentProfile, HarnessProfile
register_harness_profile("openai:gpt-4.1", HarnessProfile(
general_purpose_subagent=GeneralPurposeSubagentProfile(enabled=False),
))
# Now if no subagents are passed to create_deep_agent, the `task` tool is not exposed.
Open questions to verify before Day 5¶
These should be checked against the actual SDK before we commit:
- Does
LocalShellBackendcorrectly resolve relative paths toroot_dir, or does it use process cwd? - How does
SummarizationMiddlewaredecide token thresholds for non-Anthropic models? - Does the
tasktool's description get auto-populated with subagent name/description list, or do we need to format it? - What's the exact event schema from
astream_events(version="v2")for Rich rendering? - Does
InterruptOnConfigsupport arbitrary metadata for our approval UI?
Stop and read the relevant source before assuming behavior on any of these.
File map (paths inside the installed package)¶
deepagents/
├── __init__.py # public API
├── _version.py # __version__ = "0.6.1"
├── graph.py # create_deep_agent — the entry point
├── _models.py # resolve_model
├── _tools.py # tool description override helpers
├── _excluded_middleware.py # middleware filtering machinery
├── _messages_reducer.py # DeltaChannel reducer for messages
├── _subagent_transformer.py # scope-aware subagent stream transformer
├── _api/
│ └── deprecation.py
├── backends/
│ ├── __init__.py
│ ├── protocol.py # BackendProtocol, SandboxBackendProtocol, ExecuteResponse, FileInfo, etc.
│ ├── state.py # StateBackend (default)
│ ├── filesystem.py # FilesystemBackend
│ ├── local_shell.py # LocalShellBackend (Quoriv's choice)
│ ├── store.py # StoreBackend
│ ├── langsmith.py # LangSmithSandbox
│ ├── context_hub.py # ContextHubBackend
│ ├── composite.py # CompositeBackend
│ ├── sandbox.py # BaseSandbox helper
│ └── utils.py # shared helpers
├── middleware/
│ ├── __init__.py
│ ├── filesystem.py # FilesystemMiddleware + FilesystemPermission
│ ├── subagents.py # SubAgent, CompiledSubAgent, SubAgentMiddleware
│ ├── async_subagents.py # AsyncSubAgent, AsyncSubAgentMiddleware
│ ├── summarization.py # SummarizationMiddleware + create_summarization_middleware
│ ├── memory.py # MemoryMiddleware (AGENTS.md)
│ ├── skills.py # SkillsMiddleware (SKILL.md)
│ ├── permissions.py # legacy permission helpers
│ ├── patch_tool_calls.py # PatchToolCallsMiddleware
│ ├── _tool_exclusion.py # internal tool filtering
│ └── _utils.py
└── profiles/
├── __init__.py
├── _builtin_profiles.py
├── _keys.py
├── harness/
│ ├── harness_profiles.py # HarnessProfile, HarnessProfileConfig, GeneralPurposeSubagentProfile
│ ├── _anthropic_sonnet_4_6.py
│ ├── _anthropic_opus_4_7.py
│ ├── _anthropic_haiku_4_5.py
│ └── _openai_codex.py
└── provider/
├── provider_profiles.py # ProviderProfile, register_provider_profile
├── _openai.py
└── _openrouter.py
When in doubt, read the source. The reference above is a working summary, not a substitute.