DeepAgent¶
Two complementary primitives for long-horizon research:
create_deepagent— a plainAgentwith reflexion + grounding, typed termination, and optional filesystem / todo / subagent layers. Best for single-agent loops.create_research_workflow— aStateGraphwith a post-execution quality loop: execute (ReAct) → summarize → grounding eval → replan if needed. Best for production research where you need verifiable, grounded summaries.
Factory — single agent¶
create_deepagent ¶
create_deepagent(*, model: str | Any, tools: list[Any], system_prompt: str, output_schema: type[BaseModel] | None = None, submit_tool: str = 'submit_research', min_confidence: float = 0.8, max_tokens: int = 80000, max_iterations: int = 40, reflexion: bool = True, grounding: bool = True, checkpointer: Any | None = None, enable_filesystem: bool = False, backend: Any | None = None, enable_todos: bool = False, todo_state: Any | None = None, memory_files: list[str] | tuple[str, ...] | None = None, subagents: list[Any] | None = None, summarize_after_messages: int | None = None, summarize_keep_recent: int = 10, **agent_kwargs: Any) -> Any
Construct a research-shaped locus.Agent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str | Any
|
A locus model string ( |
required |
tools
|
list[Any]
|
All tools the agent can call — MCP-derived,
|
required |
system_prompt
|
str
|
The agent's identity, rules, and contract for
calling |
required |
output_schema
|
type[BaseModel] | None
|
Pydantic model the agent's |
None
|
submit_tool
|
str
|
The tool name whose call signals "I'm done — here
is my structured answer". Default |
'submit_research'
|
min_confidence
|
float
|
Confidence threshold the submission must clear for early-exit. Default 0.8. |
0.8
|
max_tokens
|
int
|
Token budget. Default 80k. |
80000
|
max_iterations
|
int
|
Cap on reasoning steps. Default 40. |
40
|
reflexion
|
bool
|
Self-critique pass after each step. Default True. |
True
|
grounding
|
bool
|
Citation-grounding eval against tool-call evidence. Default True. |
True
|
checkpointer
|
Any | None
|
Optional |
None
|
enable_filesystem
|
bool
|
When True, attaches the six filesystem-as-
memory tools ( |
False
|
backend
|
Any | None
|
Optional :class: |
None
|
enable_todos
|
bool
|
When True, attaches |
False
|
todo_state
|
Any | None
|
Optional pre-built :class: |
None
|
memory_files
|
list[str] | tuple[str, ...] | None
|
Optional list of |
None
|
subagents
|
list[Any] | None
|
Optional list of :class: |
None
|
summarize_after_messages
|
int | None
|
If set, attaches locus's
:class: |
None
|
summarize_keep_recent
|
int
|
How many recent messages
|
10
|
**agent_kwargs
|
Any
|
Forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
A configured |
Any
|
or |
Source code in src/locus/deepagent/factory.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | |
Research workflow — StateGraph with quality loop¶
create_research_workflow ¶
create_research_workflow(*, model: Any, tools: list[Any], system_prompt: str = '', output_schema: type[BaseModel] | None = None, grounding_threshold: float = 0.65, max_replans: int = 2, max_regenerations: int = 1, max_iterations: int = 20, summarization_model: Any | None = None, grounding_model: Any | None = None, reflexion: bool = True, causal_inference: bool = True, checkpointer: Any | None = None) -> Any
Compose the standard research workflow from individual node primitives.
Graph topology::
START → execute → [causal_inference →] summarize → grounding_eval
↓
score ≥ threshold → END
regens < max → regenerate → grounding_eval
else → replan → execute
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
Any
|
Primary model for execute + causal inference phases. |
required |
tools
|
list[Any]
|
Tools available to the execute agent. |
required |
system_prompt
|
str
|
Domain identity for the execute agent. |
''
|
output_schema
|
type[BaseModel] | None
|
Optional Pydantic model for structured output. |
None
|
grounding_threshold
|
float
|
Minimum grounding score to accept (default 0.65). |
0.65
|
max_replans
|
int
|
Maximum full-execute retries (default 2). |
2
|
max_regenerations
|
int
|
Maximum lightweight summary rewrites (default 1). |
1
|
max_iterations
|
int
|
Max ReAct iterations per execute phase (default 20). |
20
|
summarization_model
|
Any | None
|
Model for summarize + regenerate nodes. |
None
|
grounding_model
|
Any | None
|
Model for grounding_eval node. |
None
|
reflexion
|
bool
|
Enable per-turn self-evaluation in execute agent. |
True
|
causal_inference
|
bool
|
Insert causal_inference node before summarize. |
True
|
checkpointer
|
Any | None
|
Optional locus checkpointer for the StateGraph. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
A compiled |
Source code in src/locus/deepagent/workflow.py
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 | |
make_execute_node ¶
make_execute_node(model: Any, tools: list[Any], system_prompt: str = '', reflexion: bool = True, max_iterations: int = 20) -> Any
Return an async node that runs the ReAct agent loop.
Reads: KEY_EXECUTE_PROMPT (falls back to KEY_PROMPT)
Writes: KEY_EVIDENCE, KEY_GROUNDING_FACTS
Each tool result is appended to evidence as a raw string and also
stored in grounding_facts with a stable fact_id so downstream
nodes (grounding_eval, regenerate) can cite specific pieces of evidence.
Source code in src/locus/deepagent/workflow.py
make_causal_inference_node ¶
Return an async node that builds a causal chain from evidence.
Uses an LLM call to extract causal events from the evidence, then
constructs a locus.reasoning.causal.CausalChain and identifies
the primary root-cause hypothesis.
Reads: KEY_EVIDENCE, KEY_PROMPT
Writes: KEY_CAUSAL_CHAIN, KEY_CAUSAL_HYPOTHESIS, KEY_CAUSAL_CONFIDENCE
Source code in src/locus/deepagent/workflow.py
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | |
make_summarize_node ¶
Return an async node that distills evidence + causal context into a summary.
Incorporates the causal hypothesis (if present) so the summary narrative is causally grounded before claims are evaluated.
Reads: KEY_EVIDENCE, KEY_PROMPT, KEY_CAUSAL_HYPOTHESIS
Writes: KEY_SUMMARY, KEY_STRUCTURED_OUTPUT (if output_schema set)
Source code in src/locus/deepagent/workflow.py
make_grounding_eval_node ¶
Return an async node that scores summary claims against evidence (LLM-as-judge).
Reads: KEY_SUMMARY, KEY_EVIDENCE
Writes: KEY_GROUNDING_SCORE, KEY_UNGROUNDED_CLAIMS
Source code in src/locus/deepagent/workflow.py
make_regenerate_summary_node ¶
Return an async node that rewrites the summary using grounding feedback.
Cheaper than a full replan: preserves all tool outputs and only re-synthesizes the narrative, targeting ungrounded claims specifically.
KEY_SUMMARY, KEY_EVIDENCE, KEY_UNGROUNDED_CLAIMS,
KEY_GROUNDING_FACTS, KEY_REGENERATION_COUNT
Writes: KEY_SUMMARY, KEY_STRUCTURED_OUTPUT, KEY_REGENERATION_COUNT
Source code in src/locus/deepagent/workflow.py
make_replan_node ¶
Return an async node that generates a focused re-plan prompt.
Unlike regenerate_summary, this triggers a full execute phase with a narrower scope targeting the ungrounded claims.
Reads: KEY_PROMPT, KEY_UNGROUNDED_CLAIMS, KEY_REPLAN_COUNT
Writes: KEY_EXECUTE_PROMPT, KEY_REPLAN_COUNT
Source code in src/locus/deepagent/workflow.py
route_after_grounding ¶
route_after_grounding(threshold: float = 0.65, max_replans: int = 2, max_regenerations: int = 1) -> Any
Return a routing function for the grounding_eval conditional edge.
Recovery strategy (mirrors Optic's two-level approach):
1. First failure → "regenerate" (cheap: rewrite without re-running tools)
2. Subsequent → "replan" (expensive: full execute retry)
3. Limits reached → END
Compatible with StateGraph.add_conditional_edges.
Source code in src/locus/deepagent/workflow.py
Subagents¶
SubAgentDef ¶
Bases: BaseModel
Declarative definition of a subagent the parent can spawn.
Mirrors deepagents' SubAgent TypedDict, mapped to a Pydantic
model so locus's typed-config conventions hold.
task_tool ¶
Build a single task() tool the parent agent can call.
The tool's parameters are flat: subagent_type (str) and
description (str). On call:
- Look up
subagent_typein the registered subagents. - Spawn a fresh
locus.Agentwith the subagent's tools, prompt, and (optional) model override. - Run the subagent on
descriptionusingagent.run()and capture the finalTerminateEvent.final_message. - Return that string as the tool's output to the parent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subagents
|
list[SubAgentDef]
|
List of :class: |
required |
parent_model
|
Any
|
Model used when a subagent doesn't override. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
A locus |
Any
|
tool list. |
Source code in src/locus/deepagent/subagent.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | |
Todos¶
TodoState ¶
Thread-safe holder for the agent's TODO list.
A single TodoState is shared between write_todos and
read_todos for one agent run. Pass the same state to
:func:make_todo_tools if you want to inspect the list externally
after the run.
Source code in src/locus/deepagent/todos.py
Todo ¶
Bases: BaseModel
One task on the agent's structured task list.
make_todo_tools ¶
Build the write_todos and read_todos tools.
Both tools take / return a JSON string (a list of
{content, status} objects). Strings keep OpenAI's strict
structured-output mode happy — nested object schemas would
require recursive additionalProperties: false annotations.
Source code in src/locus/deepagent/todos.py
Filesystem¶
make_filesystem_tools ¶
Build the six filesystem-as-memory tools bound to backend.
Returns:
| Type | Description |
|---|---|
list[Any]
|
list of locus |
list[Any]
|
|
Source code in src/locus/deepagent/tools/filesystem.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | |
FilesystemBackend ¶
On-disk backend rooted at a caller-supplied directory.
The agent sees / as the root; on disk this maps to root.
All resolved targets must remain under root — symlink
traversal and absolute-path escapes are rejected.
Source code in src/locus/deepagent/backends/filesystem.py
StateBackend ¶
In-memory dict-of-paths-to-bytes backend.
Files are stored as UTF-8 text. Directories are implicit: a file
at "/notes/draft.md" makes "/notes" a directory in
listings even though no explicit entry exists for it.
Source code in src/locus/deepagent/backends/state.py
Knowledge protocol¶
KnowledgeProvider ¶
Bases: Protocol
The contract a research domain implements.
The deepagent runtime calls these methods in order:
open()once per scandiscover(query)to enumerateItemRefs- for each item:
a.
ground(item)for live evidence b. agent runs withtools_for_agent()and submits structured output c.merge_to_row(item, grounding, research, model_id, prompt_hash) close()at end of scan
All methods are async. The runtime calls them from its event loop.
tools_for_agent ¶
KnowledgeRow ¶
Bases: BaseModel
One catalog row — what the agent ultimately produces per item.
Concrete providers subclass this to add typed fields. The base keeps only the cross-provider invariants so a generic catalog layer can search / persist across providers without coupling to any one schema.
ItemRef ¶
Bases: BaseModel
A discoverable research target.
The key uniquely identifies the item across runs (used as a
checkpointer thread id and as a catalog primary key). The
provider field tags which provider produced the ref so a
cross-provider catalog can keep them straight.