Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9836f67
style(core): apply cargo fmt to the unformatted merge base
senamakel Aug 9, 2026
f14819b
fix(memory): finish the core::event_bus to core::bus migration in gua…
senamakel Aug 9, 2026
9eeb3d5
refactor(memory): confine raw profile SQLite behind a typed ProfileStore
senamakel Aug 9, 2026
f6eb3e0
refactor(memory): route the tool_memory agent tools through the guard
senamakel Aug 9, 2026
faa54cc
refactor(memory): drop the memory_diff types re-export shim
senamakel Aug 9, 2026
025ad5b
refactor(memory): drop the goals GoalsDoc/GoalItem re-export shim
senamakel Aug 9, 2026
ed3d9bb
refactor(memory): collapse goals/store.rs onto the crate engine
senamakel Aug 9, 2026
7be8449
refactor(memory): drop the conversations type/function re-export shim
senamakel Aug 9, 2026
50ee067
fix(tool_memory): handle missing tool name in put operation
senamakel Aug 9, 2026
39ed30b
fix(tool-memory): restore put tool after accidental removal
senamakel Aug 9, 2026
5fb07ee
fix(tool_memory): correct module path for active_memory_client
senamakel Aug 9, 2026
5c9d9fd
fix(tool-memory): return stored rule from put tool
senamakel Aug 9, 2026
90f49c9
fix(profile_store): correct SQL query to use exact match instead of p…
senamakel Aug 9, 2026
f65e8ae
fix(profile_store): use LIKE for key matching in profile query
senamakel Aug 9, 2026
e13f7b1
chore(deps): update Cargo.lock for tinybus dependency
senamakel Aug 9, 2026
a5506c5
fix(profile_store): make for_tests available outside the crate
senamakel Aug 9, 2026
d5e9585
test(learning-phase4): wrap FacetCache with ProfileStore in integrati…
senamakel Aug 9, 2026
62067fa
chore: remove unused BUS imports from tests
senamakel Aug 9, 2026
17b7755
Revert "chore: remove unused BUS imports from tests"
senamakel Aug 9, 2026
fd3c88d
fix(tests): reorder import to match dependency order
senamakel Aug 9, 2026
c653135
Merge remote-tracking branch 'upstream/main' into memory-consolidate
senamakel Aug 10, 2026
b826009
Merge branch 'main' into memory-consolidate
senamakel Aug 10, 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
30 changes: 22 additions & 8 deletions docs/specs/memory-guard-allowlist.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ dead-string rot the ratchet exists to prevent.

## Scope

The lint scans `src/` for twelve patterns, keyed on `(file, pattern)` so the
The lint scans `src/` for thirteen patterns, keyed on `(file, pattern)` so the
failure message names the needle that tripped:

| Pattern | What it hands out |
| --- | --- |
| `active_memory_client(` | `MemoryClientRef` |
| `global::client_if_ready(` / `global::client(` | `MemoryClientRef` |
| `.memory_handle(` | raw `Arc<dyn Memory>` |
| `.profile_conn(` | raw `Arc<Mutex<rusqlite::Connection>>` |
| `.profile_conn(` | raw `Arc<Mutex<rusqlite::Connection>>` (one in-family site) |
| `.profile_store(` | a typed `ProfileStore` — confined, but still unguarded |
| `.get_document(` | `pub(crate)` read-one escape hatch |
| `EmbeddedMemoryProvider::new(` / `NullMemoryProvider::new(` | a driver, built outside `binding::for_workspace` |
| `MemoryClient::from_workspace_dir(` | a second engine on the same store |
Expand Down Expand Up @@ -62,6 +63,17 @@ store:
| `tool_memory::tool_rule_list` | `MemoryToolMemory::tool_rules` | `tool_memory_store(memory).list_rules(tool)` |
| `tool_memory::tool_rule_delete` | `MemoryToolMemory::delete_tool_rule` | `tool_memory_store(memory).delete_rule(tool, id)` |

Two **agent tools** followed the same route:

| Tool | Contract method | Note |
| --- | --- | --- |
| `memory_tools_list` | `MemoryToolMemory::tool_rules` | 1:1 — same rules, same order, same serialization. |
| `memory_tools_put` | `MemoryToolMemory::put_tool_rule` + `tool_rules` | The contract method returns unit while the tool answers with the *stored* rule, so the write is followed by a read-back on the id `ToolMemoryRule::new` generated before the write. Exact, not lossy: there is no server-assigned identity, and `tool_memory_namespace` normalises the caller's raw `tool_name` the same way the write did. A concurrent delete in that window errors rather than fabricating a rule. |

`memory_tools_put` therefore now refuses under the `readonly` autonomy tier
with `"memory guard: "`-prefixed text, and store-level validation errors arrive
as `MemoryError::Invalid` rather than as a raw string. Both are intended.

**Three deltas ride along, and they are the point of the milestone, not
accidents:**

Expand Down Expand Up @@ -133,7 +145,6 @@ are recorded here so M4c starts from the real set.
| `agent/harness/session/builder/factory.rs` | `.memory_handle()` → `Arc<dyn Memory>`. |
| `flows/tinyflows/memory_adapter.rs` | Returns `Arc<dyn Memory>` to satisfy a tinyflows engine trait. The contract has no `Arc<dyn Memory>` door. |
| `flows/bus.rs` | `resolve_memory() -> Option<Arc<dyn Memory>>`, and carries a `#[cfg(test)] memory_override` seam a guard would bypass. |
| `memory/tool_memory/tools/list.rs`, `tools/put.rs` | Agent tools building `ToolMemoryStore` from `memory_handle()`. Re-pointable in principle via `as_tool_memory()` — **deferred to M5**, which filters the tool surface by capability and would collide with a re-point made now. |
| `memory/ops/tool_memory.rs` (`open_store`) | Still needed by the four handlers left on the client. Shrank; did not disappear. |

### D. No contract method exists, or the wire shape would change
Expand All @@ -157,14 +168,17 @@ module).

## Honest scorecard

Four of the twenty-eight `active_memory_client()` call sites now route through
the guard. Eleven non-test `profile_conn()` sites and twelve non-test
`memory_handle()` sites still hand out raw handles. The defensible claim for M4
is therefore:
Six of the twenty-eight `active_memory_client()` call sites now route through
the guard — four RPC handlers plus the `memory_tools_list` / `memory_tools_put`
agent tools. Raw `profile_conn()` no longer leaves the memory family — but the ten
profile/facet call sites it fed are still unguarded, now through a typed
`ProfileStore`, and twelve non-test `memory_handle()` sites still hand out raw
handles. The defensible claim is therefore:

> Every memory RPC handler whose contract twin is a literal delegation now
> routes through the guard, and every remaining bypass is enumerated here with
> a reason and pinned by a drift guard.

"Impossible to skip by construction" is **not** true until `memory_handle()`
and `profile_conn()` are gone.
is gone and the profile/facet tables have a capability family to be guarded
against.
6 changes: 2 additions & 4 deletions src/openhuman/agent/harness/subagent_runner/ops/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ fn persist_failed_run(
}
}

/// Append a worker-thread [`StoredMessage`](crate::openhuman::memory::conversations::ConversationMessage)
/// Append a worker-thread [`StoredMessage`](tinycortex::memory::conversations::ConversationMessage)
/// with the restored legacy [`SubagentObserver`] metadata (#4466): `scope`,
/// `agent_id`, `task_id`, plus the per-message `iteration`, `final`, `mode`, and
/// (for assistant tool rounds / tool results) `tool_calls` / `tool_call_id` /
Expand All @@ -750,9 +750,7 @@ fn append_worker_message(
sender: &str,
metadata: serde_json::Value,
) {
use crate::openhuman::memory::conversations::{
append_message, ConversationMessage as StoredMessage,
};
use tinycortex::memory::conversations::{append_message, ConversationMessage as StoredMessage};
let mut extra = serde_json::json!({
"scope": "worker_thread",
"agent_id": agent_id,
Expand Down
2 changes: 1 addition & 1 deletion src/openhuman/agent/learning/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Namespace `learning` (wired into `src/core/all.rs`; 11 controllers). Methods:
| `learning.forget_facet` | Mark `Dropped` + `user_state = Forgotten` (blocks re-promotion). |
| `learning.reset_cache` | Delete all `Auto` rows, preserve `Pinned`. |

All handlers go through the memory client's `profile_conn()` and a `FacetCache`; `linkedin_enrichment` / `save_profile` load config via `config::rpc::load_config_with_timeout`.
All handlers go through the memory client's `profile_store()` and a `FacetCache`; `linkedin_enrichment` / `save_profile` load config via `config::rpc::load_config_with_timeout`.

## Agent tools

Expand Down
35 changes: 16 additions & 19 deletions src/openhuman/agent/learning/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,33 @@
//! The stability detector uses this to persist the result of each rebuild cycle.
//! Prompt sections use [`FacetCache::list_active`] to read the ambient cache.

use parking_lot::Mutex;
use rusqlite::Connection;
use std::sync::Arc;

use crate::openhuman::agent::learning::candidate::FacetClass;
use crate::openhuman::memory::store::profile::{self, ProfileFacet, UserState};
use crate::openhuman::memory::store::profile::{ProfileFacet, UserState};
use crate::openhuman::memory::store::ProfileStore;

/// Thin wrapper around the `user_profile` table.
///
/// All methods delegate to the standalone helpers in
/// `memory_store::namespace_store::profile`. This type exists so callers
/// (stability detector, prompt sections, RPCs) share a single typed
/// entry-point that can be constructed from any `Arc<Mutex<Connection>>`.
/// A learning-side newtype over [`ProfileStore`], which owns the SQL. This
/// type exists because the class↔key vocabulary below (`FacetClass`) is agent
/// domain knowledge that must not move into the memory family; everything
/// else forwards straight to the store.
pub struct FacetCache {
conn: Arc<Mutex<Connection>>,
store: ProfileStore,
}

impl FacetCache {
pub fn new(conn: Arc<Mutex<Connection>>) -> Self {
Self { conn }
pub fn new(store: ProfileStore) -> Self {
Self { store }
}

/// List all facets with `state = 'active'`, ordered by stability descending.
pub fn list_active(&self) -> anyhow::Result<Vec<ProfileFacet>> {
profile::profile_select_active(&self.conn)
self.store.list_active()
}

/// List all facets (all states), ordered by stability descending.
pub fn list_all(&self) -> anyhow::Result<Vec<ProfileFacet>> {
profile::profile_select_all(&self.conn)
self.store.list_all()
}

/// List active facets belonging to a specific class.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique likely

Update the class-filter list method to use store

The class-filter method between list_all and get is not in the diff, so it is unchanged. Its body previously used self.conn (the only field the old struct had), but the struct now has store: ProfileStore and no conn field. The unchanged method therefore references a field that no longer exists and will not compile. It needs to be converted to self.store.list_active_by_class(...) (or the equivalent ProfileStore method) like the siblings above and below it.

[RULE] untrusted-repo-rules ·

Expand All @@ -50,31 +47,31 @@ impl FacetCache {

/// Fetch a single facet by its full key (e.g. `"style/verbosity"`).
pub fn get(&self, key: &str) -> anyhow::Result<Option<ProfileFacet>> {
profile::profile_get_by_key(&self.conn, key)
self.store.get(key)
}

/// Upsert a fully-formed facet row (rebuild path).
pub fn upsert(&self, facet: &ProfileFacet) -> anyhow::Result<()> {
profile::profile_upsert_full(&self.conn, facet)
self.store.upsert_full(facet)
}

/// Override the `user_state` of a facet.
///
/// Returns `Ok(true)` if a row was found and updated.
pub fn set_user_state(&self, key: &str, user_state: UserState) -> anyhow::Result<bool> {
profile::profile_set_user_state(&self.conn, key, user_state)
self.store.set_user_state(key, user_state)
}

/// Delete a facet by key. Returns `true` if a row was removed.
pub fn delete(&self, key: &str) -> anyhow::Result<bool> {
profile::profile_delete_by_key(&self.conn, key)
self.store.delete(key)
}

/// Delete all `Dropped`-state facets whose stability is below `threshold`.
///
/// Pinned facets are never deleted. Returns the number of rows removed.
pub fn drop_below_threshold(&self, threshold: f64) -> anyhow::Result<usize> {
profile::profile_delete_below_threshold(&self.conn, threshold)
self.store.drop_below_threshold(threshold)
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/openhuman/agent/learning/cache_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use crate::openhuman::memory::store::profile::{
fn make_cache() -> FacetCache {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch(PROFILE_INIT_SQL).unwrap();
FacetCache::new(Arc::new(Mutex::new(conn)))
FacetCache::new(crate::openhuman::memory::store::ProfileStore::for_tests(
Arc::new(Mutex::new(conn)),
))
}

fn stub_facet(id: &str, key: &str, value: &str, state: FacetState, stability: f64) -> ProfileFacet {
Expand Down
4 changes: 3 additions & 1 deletion src/openhuman/agent/learning/profile_md_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ mod tests {
use tempfile::TempDir;

fn make_cache(conn: Arc<Mutex<Connection>>) -> Arc<FacetCache> {
Arc::new(FacetCache::new(conn))
Arc::new(FacetCache::new(
crate::openhuman::memory::store::ProfileStore::for_tests(conn),
))
}

fn insert_facet(
Expand Down
8 changes: 6 additions & 2 deletions src/openhuman/agent/learning/prompt_sections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,9 @@ mod tests {

let conn = Connection::open_in_memory().unwrap();
conn.execute_batch(PROFILE_INIT_SQL).unwrap();
let cache = FacetCache::new(Arc::new(Mutex::new(conn)));
let cache = FacetCache::new(crate::openhuman::memory::store::ProfileStore::for_tests(
Arc::new(Mutex::new(conn)),
));

let make_facet = |id: &str, key: &str, value: &str, stab: f64| ProfileFacet {
facet_id: id.into(),
Expand Down Expand Up @@ -467,7 +469,9 @@ mod tests {

let conn = Connection::open_in_memory().unwrap();
conn.execute_batch(PROFILE_INIT_SQL).unwrap();
let cache = FacetCache::new(Arc::new(Mutex::new(conn)));
let cache = FacetCache::new(crate::openhuman::memory::store::ProfileStore::for_tests(
Arc::new(Mutex::new(conn)),
));

let result = load_learned_from_cache(&cache);
assert!(result.is_empty());
Expand Down
4 changes: 3 additions & 1 deletion src/openhuman/agent/learning/prompt_sections_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use crate::openhuman::memory::store::profile::{
fn open_cache() -> FacetCache {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch(PROFILE_INIT_SQL).unwrap();
FacetCache::new(Arc::new(Mutex::new(conn)))
FacetCache::new(crate::openhuman::memory::store::ProfileStore::for_tests(
Arc::new(Mutex::new(conn)),
))
}

fn make_active(id: &str, key: &str, value: &str, stability: f64) -> ProfileFacet {
Expand Down
8 changes: 3 additions & 5 deletions src/openhuman/agent/learning/schemas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,8 +660,7 @@ fn handle_rebuild_cache(_params: Map<String, Value>) -> ControllerFuture {

let client = crate::openhuman::memory::global::client_if_ready()
.ok_or_else(|| "memory client not ready".to_string())?;
let conn = client.profile_conn();
let cache = FacetCache::new(conn);
let cache = FacetCache::new(client.profile_store());
let detector = StabilityDetector::new(cache);

let now = SystemTime::now()
Expand Down Expand Up @@ -698,8 +697,7 @@ fn handle_cache_stats(_params: Map<String, Value>) -> ControllerFuture {

let client = crate::openhuman::memory::global::client_if_ready()
.ok_or_else(|| "memory client not ready".to_string())?;
let conn = client.profile_conn();
let cache = FacetCache::new(conn);
let cache = FacetCache::new(client.profile_store());

let all_facets = cache
.list_all()
Expand Down Expand Up @@ -760,7 +758,7 @@ fn get_cache() -> Result<crate::openhuman::agent::learning::cache::FacetCache, S
let client = crate::openhuman::memory::global::client_if_ready()
.ok_or_else(|| "memory client not ready".to_string())?;
Ok(crate::openhuman::agent::learning::cache::FacetCache::new(
client.profile_conn(),
client.profile_store(),
))
}

Expand Down
4 changes: 3 additions & 1 deletion src/openhuman/agent/learning/stability_detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,9 @@ mod tests {
fn make_detector() -> StabilityDetector {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch(PROFILE_INIT_SQL).unwrap();
let cache = FacetCache::new(Arc::new(Mutex::new(conn)));
let cache = FacetCache::new(crate::openhuman::memory::store::ProfileStore::for_tests(
Arc::new(Mutex::new(conn)),
));
// Use a private buffer so tests don't interfere with the global singleton.
let buffer: &'static Buffer = Box::leak(Box::new(Buffer::new(256)));
StabilityDetector { cache, buffer }
Expand Down
5 changes: 3 additions & 2 deletions src/openhuman/agent/learning/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ fn register_with_client(
use crate::openhuman::agent::learning::scheduler::register_event_trigger;
use crate::openhuman::agent::learning::StabilityDetector;
use std::sync::Arc;
let cache = FacetCache::new(client.profile_conn());
let cache = FacetCache::new(client.profile_store());
let detector = Arc::new(StabilityDetector::new(cache));
// Also spawn the periodic rebuild loop (30-minute cadence).
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
Expand Down Expand Up @@ -148,7 +148,7 @@ fn register_with_client(
use crate::openhuman::agent::learning::cache::FacetCache;
use crate::openhuman::agent::learning::ProfileMdRenderer;
use std::sync::Arc;
let cache = Arc::new(FacetCache::new(client.profile_conn()));
let cache = Arc::new(FacetCache::new(client.profile_store()));
let renderer = Arc::new(ProfileMdRenderer::new(cache, workspace_dir.to_path_buf()));
let handle = ProfileMdRenderer::subscribe(renderer);
if handle.is_some() {
Expand All @@ -175,6 +175,7 @@ mod tests {
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempDir;
use tinybus::EventBus;

/// Build a real `MemoryClient` against a fresh temp workspace. The temp dir
/// is returned so callers keep it alive for the client's lifetime.
Expand Down
2 changes: 1 addition & 1 deletion src/openhuman/agent/learning/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::openhuman::tools::traits::{PermissionLevel, Tool, ToolResult};
fn get_cache() -> anyhow::Result<FacetCache> {
let client = crate::openhuman::memory::global::client_if_ready()
.ok_or_else(|| anyhow::anyhow!("memory client not ready"))?;
Ok(FacetCache::new(client.profile_conn()))
Ok(FacetCache::new(client.profile_store()))
}

/// Compose the full facet key from a class string + key suffix.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ use crate::openhuman::agent::orchestration::subagent_sessions::{
SubagentSessionUpsert,
};
use crate::openhuman::agent::progress::AgentProgress;
use crate::openhuman::memory::conversations::{self as conversations, ConversationMessage};
use crate::openhuman::tools::traits::{PermissionLevel, Tool, ToolCallOptions, ToolResult};
use async_trait::async_trait;
use serde_json::json;
use tinyagents::harness::tool::ToolExecutionContext;
use tinycortex::memory::conversations::{self as conversations, ConversationMessage};

pub struct SpawnAsyncSubagentTool;

Expand Down Expand Up @@ -1253,7 +1253,7 @@ mod tests {

#[test]
fn attach_workflow_proposal_persists_thread_message_and_extends_summary() {
use crate::openhuman::memory::conversations::CreateConversationThread;
use tinycortex::memory::conversations::CreateConversationThread;
let temp = tempfile::tempdir().expect("tempdir");
conversations::ensure_thread(
temp.path().to_path_buf(),
Expand Down
6 changes: 3 additions & 3 deletions src/openhuman/agent/orchestration/tools/spawn_subagent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ use crate::openhuman::agent::harness::subagent_runner::{
run_subagent, SubagentRunOptions, SubagentRunOutcome, SubagentRunStatus,
};
use crate::openhuman::agent::progress::AgentProgress;
use crate::openhuman::memory::conversations::{
self as conversations, ConversationMessage, CreateConversationThread,
};
use crate::openhuman::tools::traits::{PermissionLevel, Tool, ToolCallOptions, ToolResult};
use async_trait::async_trait;
use serde_json::json;
use std::path::PathBuf;
use tinyagents::harness::tool::ToolExecutionContext;
use tinycortex::memory::conversations::{
self as conversations, ConversationMessage, CreateConversationThread,
};

/// Spawns a sub-agent of the requested type to handle a delegated task.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
use crate::openhuman::agent::harness::definition::AgentDefinitionRegistry;
use crate::openhuman::agent::harness::fork_context::current_parent;
use crate::openhuman::agent::harness::subagent_runner::{run_subagent, SubagentRunOptions};
use crate::openhuman::memory::conversations::{self as conversations};
use crate::openhuman::tools::traits::{PermissionLevel, Tool, ToolCallOptions, ToolResult};
use async_trait::async_trait;
use serde_json::json;
use tinyagents::harness::tool::ToolExecutionContext;
use tinycortex::memory::conversations;

/// Spawns a sub-agent in a dedicated worker thread.
pub struct SpawnWorkerThreadTool;
Expand Down Expand Up @@ -307,10 +307,10 @@ mod tests {
use super::*;
use crate::openhuman::agent::harness::fork_context::with_parent_context;
use crate::openhuman::agent::harness::ParentExecutionContext;
use crate::openhuman::memory::conversations::CreateConversationThread;
use std::path::PathBuf;
use std::sync::Arc;
use tempfile::TempDir;
use tinycortex::memory::conversations::CreateConversationThread;

struct MockMemory;
#[async_trait]
Expand Down
2 changes: 1 addition & 1 deletion src/openhuman/agent/orchestration/tools/tools_e2e_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::openhuman::agent::context::prompt::{ConnectedIntegration, ToolCallFor
use crate::openhuman::agent::harness::definition::AgentDefinitionRegistry;
use crate::openhuman::agent::harness::{with_parent_context, ParentExecutionContext};
use crate::openhuman::agent::messages::ChatMessage;
use crate::openhuman::memory::conversations;
use crate::openhuman::memory::{Memory, MemoryCategory, MemoryEntry, NamespaceSummary, RecallOpts};
use crate::openhuman::tools::Tool;
use async_trait::async_trait;
Expand All @@ -15,6 +14,7 @@ use std::path::Path;
use std::sync::Arc;
use tinyagents::harness::message::Message;
use tinyagents::harness::model::{ChatModel, ModelProfile, ModelRequest, ModelResponse};
use tinycortex::memory::conversations;

const SPAWN_SUBAGENT_CANARY: &str = "tool-e2e-spawn-subagent-canary";
const ARCHETYPE_DELEGATION_CANARY: &str = "tool-e2e-archetype-delegation-canary";
Expand Down
Loading
Loading