Skip to content

Commit ed83cae

Browse files
senamakelclaude
andauthored
feat(agent): multi-agent harness with 8 archetypes, DAG planning, and episodic memory (tinyhumansai#155)
* refactor(agent): update default model configuration and pricing structure - Changed the default model name in `AgentBuilder` to use a constant `DEFAULT_MODEL` instead of a hardcoded string. - Introduced new model constants (`MODEL_AGENTIC_V1`, `MODEL_CODING_V1`, `MODEL_REASONING_V1`) in `types.rs` for better clarity and maintainability. - Refactored the pricing structure in `identity_cost.rs` to utilize the new model constants, improving consistency across the pricing definitions. These changes enhance the configurability and readability of the agent's model and pricing settings. * refactor(models): update default model references and suggestions - Replaced hardcoded model names with a constant `DEFAULT_MODEL` in multiple files to enhance maintainability. - Updated model suggestions in the `TauriCommandsPanel` and `Conversations` components to reflect new model names, improving user experience and consistency across the application. These changes streamline model management and ensure that the application uses the latest model configurations. * style: fix Prettier formatting for model suggestions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(agent): introduce multi-agent harness with archetypes and task DAG - Added a new module for the multi-agent harness, defining 8 specialized archetypes (Orchestrator, Planner, CodeExecutor, SkillsAgent, ToolMaker, Researcher, Critic, Archivist) to enhance task management and execution. - Implemented a Directed Acyclic Graph (DAG) structure for task planning, allowing the Planner archetype to create and manage task dependencies. - Introduced a session queue to serialize tasks within sessions, preventing race conditions and enabling parallelism across different sessions. - Updated configuration schema to support orchestrator settings, including per-archetype configurations and maximum concurrent agents. These changes significantly improve the agent's architecture, enabling more complex task management and execution strategies. * feat(agent): implement orchestrator executor and interrupt handling - Introduced a new `executor.rs` module for orchestrated multi-agent execution, enabling a structured run loop that includes planning, executing, reviewing, and synthesizing tasks. - Added an `interrupt.rs` module to handle graceful interruptions via SIGINT and `/stop` commands, ensuring running sub-agents can be cancelled and memory flushed appropriately. - Implemented a self-healing interceptor in `self_healing.rs` to automatically create polyfill scripts for missing commands, enhancing the robustness of tool execution. - Updated the `mod.rs` file to include new modules and functionalities, improving the overall architecture of the agent harness. These changes significantly enhance the agent's capabilities in managing multi-agent workflows and handling interruptions effectively. * feat(agent): implement orchestrator executor and interrupt handling - Introduced a new `executor.rs` module for orchestrated multi-agent execution, enabling a structured run loop that includes planning, executing, reviewing, and synthesizing tasks. - Added an `interrupt.rs` module to handle graceful interruptions via SIGINT and `/stop` commands, ensuring running sub-agents are cancelled and memory is flushed. - Implemented a `SelfHealingInterceptor` in `self_healing.rs` to automatically generate polyfill scripts for missing commands, enhancing the agent's resilience. - Updated the `mod.rs` file to include new modules and functionalities, improving the overall architecture of the agent harness. These changes significantly enhance the agent's ability to manage complex tasks and respond to interruptions effectively. * feat(agent): add context assembly module for orchestrator - Introduced a new `context_assembly.rs` module to handle the assembly of the bootstrap context for the orchestrator, integrating identity files, workspace state, and relevant memory. - Implemented functions to load archetype prompts and identity contexts, enhancing the orchestrator's ability to generate a comprehensive system prompt. - Added a `BootstrapContext` struct to encapsulate the assembled context, improving the organization and clarity of context management. - Updated `mod.rs` to include the new context assembly module, enhancing the overall architecture of the agent harness. These changes significantly improve the orchestrator's context management capabilities, enabling more effective task execution and user interaction. * style: apply cargo fmt to multi-agent harness modules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve merge conflict in config/mod.rs re-exports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review findings — security, correctness, observability Inline fixes: - executor: wire semaphore to enforce max_concurrent_agents cap - executor: placeholder sub-agents now return success=false - executor: halt DAG when level has failed tasks after retries - self_healing: remove overly broad "not found" pattern - session_queue: fix gc() race with acquire() via Arc::strong_count check - skills_agent.md: reference injected memory context, not memory_recall tool - init.rs: run EPISODIC_INIT_SQL during UnifiedMemory::new() - ask_clarification: make "question" param optional to match execute() default - insert_sql_record: return success=false for unimplemented stub - spawn_subagent: return success=false for unimplemented stub - run_linter: reject absolute paths and ".." in path parameter - run_tests: catch spawn/timeout errors as ToolResult, fix UTF-8 truncation - update_memory_md: add symlink escape protection, use async tokio::fs::write Nitpick fixes: - archivist: document timestamp offset intent - dag: add tracing to validate(), hoist id_map out of loop in execution_levels() - session_queue: add trace logging to acquire/gc - types: add serde(rename_all) to ReviewDecision, preserve sub-second Duration - ORCHESTRATOR.md: add escalation rule for Core handoff - read_diff: add debug logging, simplify base_str with Option::map - workspace_state: add debug logging at entry and exit - run_tests: add debug logging for runner selection and exit status Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 00c7b01 commit ed83cae

35 files changed

Lines changed: 4095 additions & 12 deletions
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
//! Agent archetypes — the 8 specialized roles in the multi-agent harness.
2+
//!
3+
//! Each archetype defines a default model tier, tool whitelist, sandbox mode,
4+
//! max iterations, and system prompt path. The Orchestrator uses these to
5+
//! construct ephemeral sub-agents via `AgentBuilder`.
6+
7+
use serde::{Deserialize, Serialize};
8+
9+
/// The 8 specialised agent roles in the multi-agent topology.
10+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11+
#[serde(rename_all = "snake_case")]
12+
pub enum AgentArchetype {
13+
/// Staff Engineer — routes, judges quality, synthesises. Never writes code.
14+
Orchestrator,
15+
/// Architect — breaks complex tasks into a DAG of subtasks with acceptance criteria.
16+
Planner,
17+
/// Sandboxed developer — writes/runs code, fixes bugs until tests pass.
18+
CodeExecutor,
19+
/// Skill tool specialist — executes QuickJS skill tools (Notion, Gmail, …).
20+
SkillsAgent,
21+
/// Self-healer — writes polyfill scripts when a required command is missing.
22+
ToolMaker,
23+
/// Web & doc crawler — reads real documentation, compresses to dense markdown.
24+
Researcher,
25+
/// Adversarial reviewer — reviews diffs against SOUL.md, flags vulnerabilities.
26+
Critic,
27+
/// Background librarian — extracts lessons, updates MEMORY.md, indexes to FTS5.
28+
Archivist,
29+
}
30+
31+
impl std::fmt::Display for AgentArchetype {
32+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33+
let name = match self {
34+
Self::Orchestrator => "orchestrator",
35+
Self::Planner => "planner",
36+
Self::CodeExecutor => "code_executor",
37+
Self::SkillsAgent => "skills_agent",
38+
Self::ToolMaker => "tool_maker",
39+
Self::Researcher => "researcher",
40+
Self::Critic => "critic",
41+
Self::Archivist => "archivist",
42+
};
43+
write!(f, "{name}")
44+
}
45+
}
46+
47+
impl AgentArchetype {
48+
/// Model hint passed to `RouterProvider` (prefixed with `"hint:"` at call site).
49+
pub fn default_model_hint(&self) -> &'static str {
50+
match self {
51+
Self::Orchestrator => "reasoning",
52+
Self::Planner => "reasoning",
53+
Self::CodeExecutor => "coding",
54+
Self::SkillsAgent => "agentic",
55+
Self::ToolMaker => "coding",
56+
Self::Researcher => "agentic",
57+
Self::Critic => "reasoning",
58+
// Archivist uses the cheapest available model (local preferred).
59+
Self::Archivist => "local",
60+
}
61+
}
62+
63+
/// Static tool-name whitelist for this archetype.
64+
///
65+
/// Returns `None` for `SkillsAgent` because its tools are dynamic
66+
/// (populated from `RuntimeEngine::all_tools()` at construction time).
67+
pub fn allowed_tools(&self) -> Option<&'static [&'static str]> {
68+
match self {
69+
Self::Orchestrator => Some(&[
70+
"spawn_subagent",
71+
"query_memory",
72+
"read_workspace_state",
73+
"ask_user_clarification",
74+
]),
75+
Self::Planner => Some(&["query_memory", "read_workspace_state", "file_read"]),
76+
Self::CodeExecutor => Some(&["shell", "file_read", "file_write", "git_operations"]),
77+
Self::SkillsAgent => None, // dynamic — all skill-registered tools + memory_recall
78+
Self::ToolMaker => Some(&["file_write", "shell"]),
79+
Self::Researcher => Some(&["http_request", "web_search", "file_read", "memory_recall"]),
80+
Self::Critic => Some(&["read_diff", "run_linter", "run_tests", "file_read"]),
81+
Self::Archivist => Some(&["update_memory_md", "insert_sql_record", "memory_store"]),
82+
}
83+
}
84+
85+
/// Maximum tool-call iterations for a single turn.
86+
pub fn default_max_iterations(&self) -> usize {
87+
match self {
88+
Self::Orchestrator => 15,
89+
Self::Planner => 5,
90+
Self::CodeExecutor => 10,
91+
Self::SkillsAgent => 10,
92+
Self::ToolMaker => 2,
93+
Self::Researcher => 8,
94+
Self::Critic => 5,
95+
Self::Archivist => 3,
96+
}
97+
}
98+
99+
/// Sandbox mode identifier consumed by `SecurityPolicy` construction.
100+
///
101+
/// * `"sandboxed"` — drop-sudo, restricted filesystem (Landlock / Bubblewrap).
102+
/// * `"read_only"` — only read operations allowed.
103+
/// * `"none"` — application-layer validation only.
104+
pub fn sandbox_mode(&self) -> &'static str {
105+
match self {
106+
Self::CodeExecutor | Self::ToolMaker => "sandboxed",
107+
Self::Critic | Self::Planner => "read_only",
108+
_ => "none",
109+
}
110+
}
111+
112+
/// Relative path (from `agent/prompts/`) to the archetype's system prompt.
113+
pub fn system_prompt_path(&self) -> &'static str {
114+
match self {
115+
Self::Orchestrator => "ORCHESTRATOR.md",
116+
Self::Planner => "PLANNER.md",
117+
Self::CodeExecutor => "archetypes/code_executor.md",
118+
Self::SkillsAgent => "archetypes/skills_agent.md",
119+
Self::ToolMaker => "archetypes/code_executor.md", // shares with CodeExecutor
120+
Self::Researcher => "archetypes/researcher.md",
121+
Self::Critic => "archetypes/critic.md",
122+
Self::Archivist => "archetypes/archivist.md",
123+
}
124+
}
125+
126+
/// Whether this archetype runs as a background task (fire-and-forget).
127+
pub fn is_background(&self) -> bool {
128+
matches!(self, Self::Archivist)
129+
}
130+
131+
/// Whether this archetype should receive learning hooks.
132+
pub fn has_learning_hooks(&self) -> bool {
133+
matches!(self, Self::Orchestrator | Self::Archivist)
134+
}
135+
136+
/// All archetype variants (useful for iteration / config defaults).
137+
pub fn all() -> &'static [AgentArchetype] {
138+
&[
139+
Self::Orchestrator,
140+
Self::Planner,
141+
Self::CodeExecutor,
142+
Self::SkillsAgent,
143+
Self::ToolMaker,
144+
Self::Researcher,
145+
Self::Critic,
146+
Self::Archivist,
147+
]
148+
}
149+
}
150+
151+
#[cfg(test)]
152+
mod tests {
153+
use super::*;
154+
155+
#[test]
156+
fn all_archetypes_have_model_hints() {
157+
for arch in AgentArchetype::all() {
158+
let hint = arch.default_model_hint();
159+
assert!(!hint.is_empty(), "{arch} has empty model hint");
160+
}
161+
}
162+
163+
#[test]
164+
fn skills_agent_has_dynamic_tools() {
165+
assert!(AgentArchetype::SkillsAgent.allowed_tools().is_none());
166+
}
167+
168+
#[test]
169+
fn code_executor_is_sandboxed() {
170+
assert_eq!(AgentArchetype::CodeExecutor.sandbox_mode(), "sandboxed");
171+
assert_eq!(AgentArchetype::ToolMaker.sandbox_mode(), "sandboxed");
172+
}
173+
174+
#[test]
175+
fn critic_is_read_only() {
176+
assert_eq!(AgentArchetype::Critic.sandbox_mode(), "read_only");
177+
}
178+
179+
#[test]
180+
fn orchestrator_never_has_code_tools() {
181+
let tools = AgentArchetype::Orchestrator.allowed_tools().unwrap();
182+
assert!(!tools.contains(&"shell"));
183+
assert!(!tools.contains(&"file_write"));
184+
}
185+
186+
#[test]
187+
fn display_roundtrip() {
188+
for arch in AgentArchetype::all() {
189+
let s = arch.to_string();
190+
assert!(!s.is_empty());
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)