From 9f32510c7353056ffb8c6bcb0341b3caf1e79cfc Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 14:43:23 +0530 Subject: [PATCH 01/15] chore(limits): update prompt budget limits for orchestrator and use_skill Reduce the prompt budget limits for the orchestrator and use_skill entries to reflect updated cost measurements, bringing them in line with current usage patterns. Auto-committed-on: macbook --- scripts/prompt-budget.limits | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/prompt-budget.limits b/scripts/prompt-budget.limits index 04c77eead0..ae6719dc14 100644 --- a/scripts/prompt-budget.limits +++ b/scripts/prompt-budget.limits @@ -227,7 +227,7 @@ trigger_triage:7422:0 workflow_builder:76386:28987 summarizer:7236:0 tools_agent:5114:59064 -orchestrator:9169:20455 +orchestrator:9169:20434 code_executor:11340:13028 crypto_agent:10877:10454 task_manager_agent:4416:7602 @@ -333,7 +333,7 @@ tool:save_workflow:1957 tool:spawn_parallel_agents:1839 tool:todo:590 tool:search_tool_catalog:1695 -tool:use_skill:1732 +tool:use_skill:1711 # One action-dispatched memory surface replaces the separately registered # memory operations while keeping read/write/forget routing explicit. tool:memory:3937 From 15468b8078edfb3457f3c1785d12eeeafb4708cd Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:04:36 +0530 Subject: [PATCH 02/15] fix(todo): convert argument errors to tool errors instead of fatal harness errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bad arguments sent by the model, such as the retired `{"cards": …}` shape, were causing fatal harness errors that killed the entire run. This change wraps the tool dispatch so that any argument parsing failure returns a tool error the model can correct, rather than propagating as an `Err` that terminates execution. The `parse_items` helper is extracted to keep the logic testable, and the test suite is updated to verify that all forms of bad input produce recoverable tool errors. Auto-committed-on: macbook --- crates/openhuman-core/src/agent/tools/todo.rs | 68 +++++++++++++------ .../src/agent/tools/todo_tests.rs | 26 +++---- 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/crates/openhuman-core/src/agent/tools/todo.rs b/crates/openhuman-core/src/agent/tools/todo.rs index aef5846d35..375cfa821f 100644 --- a/crates/openhuman-core/src/agent/tools/todo.rs +++ b/crates/openhuman-core/src/agent/tools/todo.rs @@ -43,9 +43,21 @@ impl ToolDispatch<(), crate::agent::tinyagents::host::OpenHumanRunContext> for T parent: &RunContext, ) -> anyhow::Result { let context = ToolExecutionContext::from_run_context(parent, _call_id.clone()); - TodoTool::new() + // A dispatch `Err` is fatal to the whole run in the harness + // ("canonical execution errors remain fatal"), so nothing about a bad + // argument may escape as one: it goes back to the model as a tool + // error it can correct. A turn died this way when a model sent the + // retired `{"cards": …}` shape. + match TodoTool::new() .execute_with_parent_context(arguments, parent.data.parent.clone(), Some(&context)) .await + { + Ok(result) => Ok(result), + Err(error) => { + tracing::warn!(%error, "[tool][todo] rejected call"); + Ok(ToolResult::error(format!("todo failed: {error}"))) + } + } } } @@ -138,25 +150,20 @@ impl TodoTool { tracing::debug!(session_id = ?scope.session_id(), "[tool][todo] dispatch"); let result = match args.get("todos") { - None | Some(serde_json::Value::Null) => ops::list(&scope).await, - Some(raw) => { - let items: Vec = serde_json::from_value(raw.clone()) - .map_err(|e| anyhow::anyhow!("invalid `todos`: {e}"))?; - let mut cards = Vec::with_capacity(items.len()); - for item in items { - let content = item.content.trim(); - if content.is_empty() { - anyhow::bail!("every todo needs non-empty `content`"); - } - let mut card = TaskBoardCard::new(content); - card.status = match item.status.as_deref() { - None => TaskCardStatus::Todo, - Some(raw) => ops::parse_status(raw).map_err(anyhow::Error::msg)?, - }; - cards.push(card); - } - ops::replace(&scope, cards).await - } + None | Some(serde_json::Value::Null) => match args.as_object() { + // A write that used some other key (`cards`, `items`, …) is a + // shape error to report, not a request to read the list. + Some(map) if !map.is_empty() => Err(format!( + "unknown arguments {:?}: pass `todos` (the full list of \ + {{content, status}}), or no arguments to read the list", + map.keys().collect::>() + )), + _ => ops::list(&scope).await, + }, + Some(raw) => match parse_items(raw) { + Ok(cards) => ops::replace(&scope, cards).await, + Err(error) => Err(error), + }, }; match result { @@ -183,6 +190,27 @@ impl TodoTool { } } +/// Turn the model's `todos` array into store cards; every problem is a +/// message for the model, never a harness error. +fn parse_items(raw: &serde_json::Value) -> Result, String> { + let items: Vec = + serde_json::from_value(raw.clone()).map_err(|e| format!("invalid `todos`: {e}"))?; + let mut cards = Vec::with_capacity(items.len()); + for item in items { + let content = item.content.trim(); + if content.is_empty() { + return Err("every todo needs non-empty `content`".to_string()); + } + let mut card = TaskBoardCard::new(content); + card.status = match item.status.as_deref() { + None => TaskCardStatus::Todo, + Some(raw) => ops::parse_status(raw)?, + }; + cards.push(card); + } + Ok(cards) +} + /// The three states the model is told about. Store states the list can no /// longer produce (`ready`, `awaiting_approval`, `rejected`, `blocked`) fold /// into the nearest one so an old thread still reads sensibly. diff --git a/crates/openhuman-core/src/agent/tools/todo_tests.rs b/crates/openhuman-core/src/agent/tools/todo_tests.rs index 7c926b9ae2..1a705d295c 100644 --- a/crates/openhuman-core/src/agent/tools/todo_tests.rs +++ b/crates/openhuman-core/src/agent/tools/todo_tests.rs @@ -77,20 +77,22 @@ async fn two_in_progress_items_are_rejected() { reset_scratch().await; } +/// Bad input is a tool error the model can correct, never an `Err`: a +/// dispatch `Err` is fatal to the whole run in the harness, and a turn died +/// exactly that way when a model sent the retired `{"cards": …}` shape. #[tokio::test] -async fn empty_content_and_unknown_status_are_errors() { +async fn bad_input_is_a_tool_error_not_a_harness_error() { let tool = TodoTool::new(); - let err = tool - .execute(json!({ "todos": [{ "content": " ", "status": "pending" }] })) - .await - .unwrap_err(); - assert!(err.to_string().contains("content"), "{err}"); - - let err = tool - .execute(json!({ "todos": [{ "content": "x", "status": "someday" }] })) - .await - .unwrap_err(); - assert!(err.to_string().contains("invalid status"), "{err}"); + for (args, expect) in [ + (json!({ "todos": [{ "content": " ", "status": "pending" }] }), "content"), + (json!({ "todos": [{ "content": "x", "status": "someday" }] }), "invalid status"), + (json!({ "todos": "not a list" }), "invalid `todos`"), + (json!({ "cards": [{ "content": "x", "status": "todo" }] }), "pass `todos`"), + ] { + let result = tool.execute(args.clone()).await.expect("never an Err: {args}"); + assert!(result.is_error, "{args}"); + assert!(result.output().contains(expect), "{args}: {}", result.output()); + } } #[test] From bc968f7379efdf0203d2ffc9d439c8010649d745 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:17:26 +0530 Subject: [PATCH 03/15] chore(AGENTS.md): add ownership rule for change placement Added a new guideline to the AGENTS.md file that clarifies where changes should be placed, emphasizing that modifications should go in the repository that owns the component rather than where it is easiest to land. This rule helps maintain clear ownership boundaries and prevents workarounds from accumulating in the host repository when the proper fix belongs upstream. Auto-committed-on: macbook --- AGENTS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 7d92468763..8d9251c130 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -277,6 +277,16 @@ progress events. - Use the `tinytools` copy vendored through `vendor/tinyagents/`; a second path creates incompatible Rust types. - Keep conversions mechanical. Policy decisions belong in OpenHuman. +- **Put a change in the repo that owns it, not where it is easiest to land.** + Tool-call parsing, grammars, the `Tool` trait and generic tool types go to + `vendor/tinyagents/vendor/tinytools`; the agent loop, dialects, prompt + cache layout, run policy, progress events and generic harness tools (the + session todo list, goals, delegation graph) go to `vendor/tinyagents` + (`tinyagents-harness` / `tinyagents-graph`); OpenHuman keeps only the host + adapters (scope, dispatch, approvals, progress projection). Open the + upstream PR in that repo first, then move the gitlink here. A host-side + workaround for a harness or parser bug is a stopgap, not a fix: file or + fix it upstream in the same PR. - `openhuman_embed::Runtime` → `Agent` is the public library API: one runtime per process (features, services, backend URL, TinyHumans API key), then any number of independently configured agents on it (`AgentSpec`: provider, From 8f93020665333e59b422d4f10d9945b788409dd8 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:21:28 +0530 Subject: [PATCH 04/15] chore(deps): update tinyagents subproject commit Updated the pinned commit for the tinyagents vendored dependency to incorporate upstream fixes or improvements. Auto-committed-on: macbook --- vendor/tinyagents | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/tinyagents b/vendor/tinyagents index 0bc4ec443b..385ffab927 160000 --- a/vendor/tinyagents +++ b/vendor/tinyagents @@ -1 +1 @@ -Subproject commit 0bc4ec443bdbd87170ea014b0a7e79991348394b +Subproject commit 385ffab92765a5d05e2df9257af8bb07ecd820bd From 85684ac89f18274df5a4eaba210bf0f0a077632e Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:22:02 +0530 Subject: [PATCH 05/15] refactor(todos): delegate todo tool logic to TinyAgents' session_list The host-side todo tool and ops module are simplified to delegate all list management, validation, and rendering to TinyAgents' `session_list` module. The ops module now only maps a scope onto a store key, while the tool adapter wraps the TinyAgents tool and passes the process-wide store. This removes duplicated parsing, serialization, and status mapping logic, reducing the risk of shape errors causing fatal dispatch failures. Auto-committed-on: macbook --- crates/openhuman-core/src/agent/todos/ops.rs | 59 +++---- crates/openhuman-core/src/agent/tools/todo.rs | 144 +++--------------- 2 files changed, 37 insertions(+), 166 deletions(-) diff --git a/crates/openhuman-core/src/agent/todos/ops.rs b/crates/openhuman-core/src/agent/todos/ops.rs index 22859aa3bd..c501110bbc 100644 --- a/crates/openhuman-core/src/agent/todos/ops.rs +++ b/crates/openhuman-core/src/agent/todos/ops.rs @@ -1,27 +1,19 @@ -//! OpenHuman host adapter over [`tinyagents_graph::todos`]. +//! OpenHuman host adapter over TinyAgents' `todos::session_list`. //! //! A todo list is scoped to one agent session ([`TodoScope::Session`]) or, //! when a tool runs with no session at all, to a scratch list -//! ([`TodoScope::Scratch`]). Both live in the one in-process store; the -//! normalisation and rendering are TinyAgents'. The whole-list `replace` is -//! the only write the `todo` tool needs; `clear` is for tests and cleanup. +//! ([`TodoScope::Scratch`]). Both live in the one in-process [`store`]; +//! validation, the whole-list write and rendering are TinyAgents'. This file +//! only maps a scope onto a store key. `clear` is for tests and cleanup. -use serde::{Deserialize, Serialize}; -use tinyagents_graph::todos::store as todos; +use std::sync::Arc; + +use tinyagents_graph::todos::session_list; +use tinyagents_harness::store::Store; use crate::agent::tinyagents::todos::{session_todos_store, SCRATCH_SESSION_ID}; -use crate::agent::todos::types::normalize_cards_for_wire; pub use crate::agent::todos::types::{TaskBoardCard, TaskCardStatus}; - -pub use tinyagents_graph::todos::{parse_status, render_markdown}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TodosSnapshot { - pub session_id: Option, - pub cards: Vec, - pub markdown: String, -} +pub use tinyagents_graph::todos::TodosSnapshot; #[derive(Debug, Clone, PartialEq, Eq)] pub enum TodoScope { @@ -37,43 +29,30 @@ impl TodoScope { } } - fn key(&self) -> &str { + /// The store key this scope's list lives under. + pub fn key(&self) -> &str { self.session_id().unwrap_or(SCRATCH_SESSION_ID) } } -fn snapshot(scope: &TodoScope, value: tinyagents_graph::todos::TodosSnapshot) -> TodosSnapshot { - TodosSnapshot { - session_id: scope.session_id().map(str::to_owned), - cards: value.cards, - markdown: value.markdown, - } -} - -fn finish( - scope: &TodoScope, - result: tinyagents_harness::error::Result, -) -> Result { - let mut value = result.map_err(|error| error.to_string())?; - normalize_cards_for_wire(&mut value.cards); - Ok(snapshot(scope, value)) +/// The process-wide store every session's list lives in. +pub fn store() -> Arc { + session_todos_store() } pub async fn replace(scope: &TodoScope, cards: Vec) -> Result { - let store = session_todos_store(); - finish(scope, todos::replace(&store, scope.key(), cards).await) + session_list::write(&store(), scope.key(), cards) + .await + .map_err(|error| error.to_string()) } pub async fn clear(scope: &TodoScope) -> Result { - let store = session_todos_store(); - finish(scope, todos::clear(&store, scope.key()).await) + replace(scope, Vec::new()).await } pub async fn list(scope: &TodoScope) -> Result { - let store = session_todos_store(); - todos::list(&store, scope.key()) + session_list::read(&store(), scope.key()) .await - .map(|value| snapshot(scope, value)) .map_err(|error| error.to_string()) } diff --git a/crates/openhuman-core/src/agent/tools/todo.rs b/crates/openhuman-core/src/agent/tools/todo.rs index 375cfa821f..b1fa6bb0ef 100644 --- a/crates/openhuman-core/src/agent/tools/todo.rs +++ b/crates/openhuman-core/src/agent/tools/todo.rs @@ -1,25 +1,25 @@ //! `todo` — the session's todo list, the way Claude Code and Codex have it. //! -//! One call writes the whole list: `{"todos": [{"content", "status"}]}`. -//! There is no per-card CRUD, no approval gate, no evidence, no plan; the -//! list is a progress checklist the model rewrites as it works. It is scoped -//! to the agent session the turn runs in (in memory, for the life of the -//! process) via [`crate::agent::todos::ops`]; without a session (a bare -//! `execute` in a test) it falls back to a scratch list. Calling with no -//! `todos` returns the current list. +//! The tool itself is TinyAgents' `todos::session_list` (schema, argument +//! validation, the whole-list write, markdown). This file is only the host +//! adapter: it decides **which** list a call is about — the agent session the +//! turn runs in, in memory for the life of the process — and registers the +//! harness dispatch. Nothing here may turn a bad argument into an `Err`: a +//! dispatch `Err` is fatal to the run, and a turn died that way when a model +//! sent the retired `{"cards": …}` shape to the previous host-side copy. use crate::agent::harness::fork_context::ParentExecutionContext; use crate::agent::todos::ops::{self, TodoScope}; -use crate::agent::todos::types::{TaskBoardCard, TaskCardStatus}; use async_trait::async_trait; -use serde::Deserialize; -use serde_json::json; use std::sync::Arc; +use tinyagents_graph::todos::session_list; use tinyagents_harness::context::RunContext; use tinyagents_harness::tool::{ToolDispatch, ToolExecutionContext}; use tinytools::{PermissionLevel, Tool, ToolCallOptions, ToolResult, ToolRunContext}; -pub struct TodoTool; +pub struct TodoTool { + inner: session_list::SessionTodoTool, +} pub(crate) struct TodoToolDispatch { tool: Arc, @@ -43,11 +43,6 @@ impl ToolDispatch<(), crate::agent::tinyagents::host::OpenHumanRunContext> for T parent: &RunContext, ) -> anyhow::Result { let context = ToolExecutionContext::from_run_context(parent, _call_id.clone()); - // A dispatch `Err` is fatal to the whole run in the harness - // ("canonical execution errors remain fatal"), so nothing about a bad - // argument may escape as one: it goes back to the model as a tool - // error it can correct. A turn died this way when a model sent the - // retired `{"cards": …}` shape. match TodoTool::new() .execute_with_parent_context(arguments, parent.data.parent.clone(), Some(&context)) .await @@ -63,7 +58,9 @@ impl ToolDispatch<(), crate::agent::tinyagents::host::OpenHumanRunContext> for T impl TodoTool { pub fn new() -> Self { - Self + Self { + inner: session_list::SessionTodoTool::new(ops::store()), + } } } @@ -73,50 +70,18 @@ impl Default for TodoTool { } } -/// One item as the model writes it. `status` accepts the Claude-style -/// `pending` / `in_progress` / `completed` plus the older `todo` / `done` -/// spellings the store already parses. -#[derive(Deserialize)] -struct TodoItem { - content: String, - #[serde(default)] - status: Option, -} - #[async_trait] impl Tool for TodoTool { fn name(&self) -> &str { - "todo" + self.inner.name() } fn description(&self) -> &str { - "Your todo list for this conversation. Pass the complete list every time; it \ - replaces what was there. Use it for work with 3+ steps: write the steps up front, \ - keep exactly one `in_progress`, mark each `completed` the moment it is done. Omit \ - `todos` to read the current list." + self.inner.description() } fn parameters_schema(&self) -> serde_json::Value { - json!({ - "type": "object", - "properties": { - "todos": { - "type": "array", - "description": "The full list, in order.", - "items": { - "type": "object", - "properties": { - "content": { "type": "string" }, - "status": { - "type": "string", - "enum": ["pending", "in_progress", "completed"] - } - }, - "required": ["content", "status"] - } - } - } - }) + self.inner.parameters_schema() } fn permission_level(&self) -> PermissionLevel { @@ -148,80 +113,7 @@ impl TodoTool { ) -> anyhow::Result { let scope = current_scope(parent.as_ref(), tool_context); tracing::debug!(session_id = ?scope.session_id(), "[tool][todo] dispatch"); - - let result = match args.get("todos") { - None | Some(serde_json::Value::Null) => match args.as_object() { - // A write that used some other key (`cards`, `items`, …) is a - // shape error to report, not a request to read the list. - Some(map) if !map.is_empty() => Err(format!( - "unknown arguments {:?}: pass `todos` (the full list of \ - {{content, status}}), or no arguments to read the list", - map.keys().collect::>() - )), - _ => ops::list(&scope).await, - }, - Some(raw) => match parse_items(raw) { - Ok(cards) => ops::replace(&scope, cards).await, - Err(error) => Err(error), - }, - }; - - match result { - Ok(snap) => { - let todos: Vec = snap - .cards - .iter() - .map(|card| { - json!({ - "content": card.title, - "status": wire_status(card.status), - }) - }) - .collect(); - let payload = json!({ - "sessionId": snap.session_id, - "todos": todos, - "markdown": snap.markdown, - }); - Ok(ToolResult::success(payload.to_string())) - } - Err(err) => Ok(ToolResult::error(err)), - } - } -} - -/// Turn the model's `todos` array into store cards; every problem is a -/// message for the model, never a harness error. -fn parse_items(raw: &serde_json::Value) -> Result, String> { - let items: Vec = - serde_json::from_value(raw.clone()).map_err(|e| format!("invalid `todos`: {e}"))?; - let mut cards = Vec::with_capacity(items.len()); - for item in items { - let content = item.content.trim(); - if content.is_empty() { - return Err("every todo needs non-empty `content`".to_string()); - } - let mut card = TaskBoardCard::new(content); - card.status = match item.status.as_deref() { - None => TaskCardStatus::Todo, - Some(raw) => ops::parse_status(raw)?, - }; - cards.push(card); - } - Ok(cards) -} - -/// The three states the model is told about. Store states the list can no -/// longer produce (`ready`, `awaiting_approval`, `rejected`, `blocked`) fold -/// into the nearest one so an old thread still reads sensibly. -fn wire_status(status: TaskCardStatus) -> &'static str { - match status { - TaskCardStatus::InProgress => "in_progress", - TaskCardStatus::Done | TaskCardStatus::Rejected => "completed", - TaskCardStatus::Todo - | TaskCardStatus::Ready - | TaskCardStatus::AwaitingApproval - | TaskCardStatus::Blocked => "pending", + Ok(session_list::call(&ops::store(), scope.key(), &args).await?) } } From 6b23a83021d169e0475877a205a29026780d69a8 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:23:20 +0530 Subject: [PATCH 06/15] chore(todos): remove unused wire-format normalization and stale assertion Removes the `normalize_timestamp_for_wire` and `normalize_cards_for_wire` functions from the todos types module, along with their `chrono` dependency, as they are no longer needed. Also drops a stale assertion in the test that checked for a session ID that is no longer guaranteed. Auto-committed-on: macbook --- .../openhuman-core/src/agent/todos/types.rs | 25 ++----------------- .../src/agent/tools/todo_tests.rs | 1 - 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/crates/openhuman-core/src/agent/todos/types.rs b/crates/openhuman-core/src/agent/todos/types.rs index 42284a5250..5a68fee050 100644 --- a/crates/openhuman-core/src/agent/todos/types.rs +++ b/crates/openhuman-core/src/agent/todos/types.rs @@ -1,24 +1,3 @@ -//! TinyAgents todo types and OpenHuman wire-format normalization. +//! TinyAgents todo types, re-exported for OpenHuman callers. -use chrono::{TimeZone, Utc}; - -pub use tinyagents_graph::todos::{TaskApprovalMode, TaskBoard, TaskBoardCard, TaskCardStatus}; - -pub(crate) fn normalize_timestamp_for_wire(value: &str) -> String { - if chrono::DateTime::parse_from_rfc3339(value).is_ok() { - return value.to_owned(); - } - if let Ok(updated_at_ms) = value.parse::() { - if let Some(updated_at) = Utc.timestamp_millis_opt(updated_at_ms).single() { - return updated_at.to_rfc3339(); - } - } - tracing::warn!(updated_at = %value, "invalid todo timestamp; using current time"); - Utc::now().to_rfc3339() -} - -pub(crate) fn normalize_cards_for_wire(cards: &mut [TaskBoardCard]) { - for card in cards { - card.updated_at = normalize_timestamp_for_wire(&card.updated_at); - } -} +pub use tinyagents_graph::todos::{TaskBoardCard, TaskCardStatus}; diff --git a/crates/openhuman-core/src/agent/tools/todo_tests.rs b/crates/openhuman-core/src/agent/tools/todo_tests.rs index 1a705d295c..2b2b1672a8 100644 --- a/crates/openhuman-core/src/agent/tools/todo_tests.rs +++ b/crates/openhuman-core/src/agent/tools/todo_tests.rs @@ -181,6 +181,5 @@ async fn sessions_do_not_see_each_other_and_a_list_survives_across_turns() { let a_again = crate::agent::todos::ops::list(&a).await.unwrap(); assert_eq!(a_again.cards.len(), 1, "a later turn of the same session reads it back"); - assert_eq!(a_again.session_id.as_deref(), Some("sess-a")); assert!(crate::agent::todos::ops::list(&b).await.unwrap().cards.is_empty()); } From ae309ce6b8cd5de3846de31d86884c62e856069f Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:26:05 +0530 Subject: [PATCH 07/15] fix(tests): add serde_json::json import to todo tests The test module now imports the `json!` macro from `serde_json` alongside the existing `Value` import, enabling test code to construct JSON literals more concisely. Auto-committed-on: macbook --- crates/openhuman-core/src/agent/tools/todo_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/openhuman-core/src/agent/tools/todo_tests.rs b/crates/openhuman-core/src/agent/tools/todo_tests.rs index 2b2b1672a8..580dd4ca5b 100644 --- a/crates/openhuman-core/src/agent/tools/todo_tests.rs +++ b/crates/openhuman-core/src/agent/tools/todo_tests.rs @@ -1,5 +1,5 @@ use super::*; -use serde_json::Value; +use serde_json::{json, Value}; /// Serialize tests that share the process-global scratch store. Same lock /// as `todos::ops` — otherwise the two test modules race under `cargo test`'s From 7b5ef551ee63167b7cd592ff63cc2bff5bda70a1 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:28:10 +0530 Subject: [PATCH 08/15] test(todo): add missing import for TaskBoardCard and TaskCardStatus The test file was missing an import for `TaskBoardCard` and `TaskCardStatus` from the todos ops module, which caused compilation errors when these types were referenced in test code. Adding the import resolves the build failure. Auto-committed-on: macbook --- crates/openhuman-core/src/agent/tools/todo_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/openhuman-core/src/agent/tools/todo_tests.rs b/crates/openhuman-core/src/agent/tools/todo_tests.rs index 580dd4ca5b..4ec45c21a8 100644 --- a/crates/openhuman-core/src/agent/tools/todo_tests.rs +++ b/crates/openhuman-core/src/agent/tools/todo_tests.rs @@ -1,4 +1,5 @@ use super::*; +use crate::agent::todos::ops::{TaskBoardCard, TaskCardStatus}; use serde_json::{json, Value}; /// Serialize tests that share the process-global scratch store. Same lock From 1d5d8323b7bbb42bd457c51e96c5c4608470c087 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:30:18 +0530 Subject: [PATCH 09/15] fix(prompts): reword tool ownership language for clarity and accuracy Updated the grounding section across all prompt sources to clarify that tools are those given for the current turn rather than those listed in the prompt, and added guidance to check the tool list before claiming a capability is missing. This prevents agents from incorrectly refusing tasks when tools are provided dynamically at runtime rather than being hardcoded in the prompt text. Auto-committed-on: macbook --- .../src/agent/prompts/mod_tests_builder_sections_tests.rs | 2 +- .../src/agent/prompts/mod_tests_subagent_render_tests.rs | 2 +- crates/openhuman-core/src/agent/prompts/sections.rs | 2 +- .../src/agent/registry/agents/orchestrator/prompt.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/openhuman-core/src/agent/prompts/mod_tests_builder_sections_tests.rs b/crates/openhuman-core/src/agent/prompts/mod_tests_builder_sections_tests.rs index 92e4eecb0a..980fd90f01 100644 --- a/crates/openhuman-core/src/agent/prompts/mod_tests_builder_sections_tests.rs +++ b/crates/openhuman-core/src/agent/prompts/mod_tests_builder_sections_tests.rs @@ -57,7 +57,7 @@ fn grounding_contract_appended_to_every_build_path() { // A distinctive clause from GROUNDING_BODY — present regardless of which // builder produced the prompt (single source of truth, central append). - let marker = "Your tools are exactly the ones listed in this prompt"; + let marker = "Your tools are exactly the ones you have been given for this turn"; // 1. Static default chain. let defaults = SystemPromptBuilder::with_defaults().build(&ctx).unwrap(); diff --git a/crates/openhuman-core/src/agent/prompts/mod_tests_subagent_render_tests.rs b/crates/openhuman-core/src/agent/prompts/mod_tests_subagent_render_tests.rs index 9249fec5a9..dffffb9336 100644 --- a/crates/openhuman-core/src/agent/prompts/mod_tests_subagent_render_tests.rs +++ b/crates/openhuman-core/src/agent/prompts/mod_tests_subagent_render_tests.rs @@ -58,7 +58,7 @@ fn render_subagent_system_prompt_renders_workspace_tail() { // sub-agent renderer — same source const, so it can never drift from // `GroundingSection` / the central `build()` append. assert!(rendered.contains("## Grounding and tool use")); - assert!(rendered.contains("Your tools are exactly the ones listed in this prompt")); + assert!(rendered.contains("Your tools are exactly the ones you have been given for this turn")); assert!(rendered.contains("Preserve numeric evidence exactly")); let _ = std::fs::remove_dir_all(workspace); diff --git a/crates/openhuman-core/src/agent/prompts/sections.rs b/crates/openhuman-core/src/agent/prompts/sections.rs index 9cdf145ec3..30617f221a 100644 --- a/crates/openhuman-core/src/agent/prompts/sections.rs +++ b/crates/openhuman-core/src/agent/prompts/sections.rs @@ -460,7 +460,7 @@ impl PromptSection for SafetySection { pub const GROUNDING_HEADING: &str = "Grounding and tool use"; pub const GROUNDING_BODY: &str = "## Grounding and tool use\n\n\ - - Your tools are exactly the ones listed in this prompt. You can only act through them. If a capability is not one of your tools, say so plainly rather than pretending it exists.\n\ + - Your tools are exactly the ones you have been given for this turn, whether they arrive as a tool list or are described in this prompt. You can only act through them. Check that list before saying you lack a capability, and if it is not there, say so plainly rather than pretending it exists.\n\ - Never invent tool names, arguments, ids, slugs, file paths, URLs, chain ids, addresses, quotes, metrics, or any other value. If you do not have it from a tool result or the user, ask for it or look it up with a tool.\n\ - Preserve numeric evidence exactly. For numbers, counts, sizes, dates, timestamps, durations, currencies, percentages, quotas, and ids, copy the exact value from the observed tool result, user message, or cited memory into your answer.\n\ - Do not round, convert units, rewrite relative times, or recalculate numeric values unless the user asks and you show the calculation from observed values. If sources disagree, name the discrepancy instead of choosing a plausible value.\n\ diff --git a/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt.md b/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt.md index 981b61fe38..c35e9bdc33 100644 --- a/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt.md +++ b/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt.md @@ -4,7 +4,7 @@ Take the first branch that applies: 1. **Answerable without tools**: reply. Small talk, simple Q&A, general knowledge. 1b. **Needs a capability you do not see listed**: call `tool_search` with the intent in plain words before delegating or declining. Your list is a core set; one clear action on a connected service (send this message, create that issue) is a search-then-call, not a delegation. -2. **Needs a connected service's own data or actions** (inbox, messages, calendar, docs, tickets, "send/check X"): call `delegate_to_integrations_agent` with the `toolkit` from **Connected Integrations**. Use the live service even when memory could plausibly answer. A service being connected is not a reason to touch it: general knowledge, web/news lookups, headlines, date/time and math never delegate here. Not connected? Raise a connect card with `composio_connect`: the list shows what is connected, not what is connectable, so never refuse from it or send the user to settings, and never paste OAuth URLs. If the connect call reports the toolkit unavailable, relay its message; that is the only honest refusal. +2. **Needs a connected service's own data or actions** (inbox, messages, calendar, docs, tickets, "send/check X"): call `delegate_to_integrations_agent` with the `toolkit` from **Connected Integrations**. Use the live service even when memory could plausibly answer. A service being connected is not a reason to touch it: general knowledge, web/news lookups, headlines, date/time, math, and anything public on the web (a public repository, a product page, docs) never delegate here; those are `web_fetch` / `web_search_tool` / `research` work. Delegate to a toolkit only for the user's own account data or actions on it. Not connected? Raise a connect card with `composio_connect`: the list shows what is connected, not what is connectable, so never refuse from it or send the user to settings, and never paste OAuth URLs. If the connect call reports the toolkit unavailable, relay its message; that is the only honest refusal. 3. **Solvable with a direct tool**: do it yourself. `web_search_tool` and `web_fetch` for a fact or a page, `memory_recall` and `memory_store` for the user's own facts, `shell` plus `apply_patch` for repository work. Keep code work end-to-end: edit and verify in the same turn; never delegate merely because a task touches a repository. 4. **Needs a specialist**: the specialists you can call are in your tool list with their own descriptions. **Capabilities not in your tool list** names the ones a skill holds; reach those through `use_skill`. Workers return only their result; carry out any `## Handoff Plan` they return yourself, under the approval gate. 5. **Distill every delegated reply**: keep what answers the question, drop the worker's notes. Never paste a sub-agent's response verbatim. @@ -26,7 +26,7 @@ Three or more steps? Track them on `todo` cards. Don't stop with a plan: execute ## Grounding and tool use -- Your tools are the ones listed in this prompt plus whatever `tool_search` returns. Before saying a capability does not exist, search once; if nothing comes back, say so. +- Your tools are the ones you have been given for this turn (the tool list, however it reaches you) plus whatever `tool_search` returns. Read that list before claiming a capability is missing: `web_search_tool` and `web_fetch` are usually in it. If it is not there, search once; if nothing comes back, say so. - Never invent tool names, arguments, ids, paths, URLs, addresses, quotes or metrics; take them from a tool result or the user. - Preserve numeric evidence exactly: copy numbers, dates, durations, currencies and ids as observed; don't round or recompute unless asked, and then show the working. - A sub-agent's summary is claims: check it against its `Evidence used`, `Actions taken` and `Failed tool calls`. Do not introduce facts its evidence does not support. Output marked truncated, oversized, partial or unavailable is not complete: fetch more or say so. From c98f9e932401004d617ff8395cb22dfa23e56c1d Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:32:20 +0530 Subject: [PATCH 10/15] test(orchestrator): add assertions preventing tool-list confusion in native dialect The test for evidence-aware synthesis now verifies that the prompt does not tell a model its tools are "listed in this prompt", which caused the model to ignore `web_search_tool` when it was present in its actual tool list. New assertions confirm the prompt instead references the tools by name and clarifies the scope of web delegation. Auto-committed-on: macbook --- .../src/agent/registry/agents/orchestrator/prompt_tests.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt_tests.rs b/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt_tests.rs index f04ae523eb..bb8678f03e 100644 --- a/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt_tests.rs +++ b/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt_tests.rs @@ -555,6 +555,12 @@ fn build_includes_evidence_aware_synthesis_contract() { assert!(body.contains("Preserve numeric evidence exactly")); assert!(body.contains("plus whatever `tool_search` returns")); assert!(body.contains("call `tool_search` with the intent in plain words")); + // Under the native dialect no tool is "listed in this prompt"; a model told + // that its tools are the listed ones concluded it had no web search while + // `web_search_tool` sat in its tool list (thread-7e52b, 2026-09-22). + assert!(!body.contains("listed in this prompt"), "{body}"); + assert!(body.contains("`web_search_tool` and `web_fetch` are usually in it")); + assert!(body.contains("anything public on the web (a public repository, a product page, docs) never delegate here")); } #[test] From 2ae4ba3c85770f85bc28fa65744295b5cca448ba Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:36:22 +0530 Subject: [PATCH 11/15] chore(limits): update prompt budgets and scope-gate assertion Update the prompt-budget limits for all agents after a recent prompt change, and adjust the orchestrator's scope-gate test assertion to match the new wording that now includes public-web resources in the delegation exclusion list. Auto-committed-on: macbook --- .../agents/orchestrator/prompt_tests.rs | 2 +- scripts/prompt-budget.limits | 64 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt_tests.rs b/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt_tests.rs index bb8678f03e..9859eeea87 100644 --- a/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt_tests.rs +++ b/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt_tests.rs @@ -348,7 +348,7 @@ fn build_scope_gates_integrations_delegation() { // delegation-guide clause. let no_integrations = build(&ctx_with(&[])).unwrap(); assert!( - no_integrations.contains("general knowledge, web/news lookups, headlines, date/time and math never delegate here"), + no_integrations.contains("general knowledge, web/news lookups, headlines, date/time, math, and anything public on the web (a public repository, a product page, docs) never delegate here"), "Step-2 scope gate must keep general/web/date asks off integrations delegation" ); assert!( diff --git a/scripts/prompt-budget.limits b/scripts/prompt-budget.limits index ae6719dc14..193783b2c0 100644 --- a/scripts/prompt-budget.limits +++ b/scripts/prompt-budget.limits @@ -222,38 +222,38 @@ # without their generated budget update. The morning briefing's # fixed prefix is 3 B larger; all other recorded ceilings stay put. -morning_briefing:10654:59064 -trigger_triage:7422:0 -workflow_builder:76386:28987 -summarizer:7236:0 -tools_agent:5114:59064 -orchestrator:9169:20434 -code_executor:11340:13028 -crypto_agent:10877:10454 -task_manager_agent:4416:7602 -planner:7714:5306 -skill_creator:5349:11280 -flow_discovery:8407:8228 -profile_memory_agent:5400:11010 -settings_agent:4606:9652 -context_scout:8737:5438 -skill_executor:7674:5469 -scheduler_agent:7758:5144 -agent_memory:8116:5423 -skill_setup:5252:5693 -trigger_reactor:6446:5606 -mcp_agent:7032:2569 -flow_memory_agent:7411:2534 -tool_maker:4414:4543 -presentation_agent:4676:4265 -video_agent:5144:1106 -help:6627:952 -image_agent:5188:1106 -goals_agent:5111:1191 -vision_agent:5056:1106 -archivist:4311:1686 -researcher:5485:816 -critic:4405:695 +morning_briefing:10769:59064 +trigger_triage:7537:0 +workflow_builder:76501:28987 +summarizer:7351:0 +tools_agent:5229:59064 +orchestrator:9518:20434 +code_executor:11455:13028 +crypto_agent:10992:10454 +task_manager_agent:4531:7602 +planner:7829:5306 +skill_creator:5464:11280 +flow_discovery:8522:8228 +profile_memory_agent:5515:11010 +settings_agent:4721:9652 +context_scout:8852:5438 +skill_executor:7789:5469 +scheduler_agent:7873:5144 +agent_memory:8231:5423 +skill_setup:5367:5693 +trigger_reactor:6561:5606 +mcp_agent:7147:2569 +flow_memory_agent:7526:2534 +tool_maker:4529:4543 +presentation_agent:4791:4265 +video_agent:5259:1106 +help:6742:952 +image_agent:5303:1106 +goals_agent:5226:1191 +vision_agent:5171:1106 +archivist:4426:1686 +researcher:5600:816 +critic:4520:695 # ── Per-tool schema ratchet ────────────────────────────────────────────── # From abd316360e0d33a21d0c717e4e55b20a95d7a924 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:40:09 +0530 Subject: [PATCH 12/15] chore: files changed crates/openhuman-core/src/agent/todos/ops.rs,crates/openhuman-core/src/agent/to Auto-committed-on: macbook --- crates/openhuman-core/src/agent/todos/ops.rs | 5 +- .../src/agent/tools/todo_tests.rs | 61 +++++++++++++++---- .../src/integrations/task_sources/store.rs | 5 +- .../integrations/task_sources/store_tests.rs | 9 +-- 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/crates/openhuman-core/src/agent/todos/ops.rs b/crates/openhuman-core/src/agent/todos/ops.rs index c501110bbc..229865d335 100644 --- a/crates/openhuman-core/src/agent/todos/ops.rs +++ b/crates/openhuman-core/src/agent/todos/ops.rs @@ -40,7 +40,10 @@ pub fn store() -> Arc { session_todos_store() } -pub async fn replace(scope: &TodoScope, cards: Vec) -> Result { +pub async fn replace( + scope: &TodoScope, + cards: Vec, +) -> Result { session_list::write(&store(), scope.key(), cards) .await .map_err(|error| error.to_string()) diff --git a/crates/openhuman-core/src/agent/tools/todo_tests.rs b/crates/openhuman-core/src/agent/tools/todo_tests.rs index 4ec45c21a8..f84bbb82f9 100644 --- a/crates/openhuman-core/src/agent/tools/todo_tests.rs +++ b/crates/openhuman-core/src/agent/tools/todo_tests.rs @@ -85,14 +85,30 @@ async fn two_in_progress_items_are_rejected() { async fn bad_input_is_a_tool_error_not_a_harness_error() { let tool = TodoTool::new(); for (args, expect) in [ - (json!({ "todos": [{ "content": " ", "status": "pending" }] }), "content"), - (json!({ "todos": [{ "content": "x", "status": "someday" }] }), "invalid status"), + ( + json!({ "todos": [{ "content": " ", "status": "pending" }] }), + "content", + ), + ( + json!({ "todos": [{ "content": "x", "status": "someday" }] }), + "invalid status", + ), (json!({ "todos": "not a list" }), "invalid `todos`"), - (json!({ "cards": [{ "content": "x", "status": "todo" }] }), "pass `todos`"), + ( + json!({ "cards": [{ "content": "x", "status": "todo" }] }), + "pass `todos`", + ), ] { - let result = tool.execute(args.clone()).await.expect("never an Err: {args}"); + let result = tool + .execute(args.clone()) + .await + .expect("never an Err: {args}"); assert!(result.is_error, "{args}"); - assert!(result.output().contains(expect), "{args}: {}", result.output()); + assert!( + result.output().contains(expect), + "{args}: {}", + result.output() + ); } } @@ -102,14 +118,21 @@ fn schema_is_the_claude_shape() { let schema = tool.parameters_schema(); let props = &schema["properties"]; assert!(props.get("todos").is_some()); - assert_eq!(props.as_object().unwrap().len(), 1, "no per-card ops: {props}"); + assert_eq!( + props.as_object().unwrap().len(), + 1, + "no per-card ops: {props}" + ); assert_eq!( props["todos"]["items"]["properties"]["status"]["enum"], json!(["pending", "in_progress", "completed"]) ); let desc = tool.description(); assert!(desc.contains("3+ steps"), "missing when-to-use guidance"); - assert!(desc.contains("one `in_progress`"), "missing single-in_progress rule"); + assert!( + desc.contains("one `in_progress`"), + "missing single-in_progress rule" + ); assert!( !desc.contains("board"), "the tool must not describe itself as a board" @@ -171,16 +194,30 @@ fn every_agent_binds_to_its_own_session() { #[tokio::test] async fn sessions_do_not_see_each_other_and_a_list_survives_across_turns() { - let a = TodoScope::Session { id: "sess-a".into() }; - let b = TodoScope::Session { id: "sess-b".into() }; + let a = TodoScope::Session { + id: "sess-a".into(), + }; + let b = TodoScope::Session { + id: "sess-b".into(), + }; crate::agent::todos::ops::clear(&a).await.unwrap(); crate::agent::todos::ops::clear(&b).await.unwrap(); let mut card = TaskBoardCard::new("only in a"); card.status = TaskCardStatus::InProgress; - crate::agent::todos::ops::replace(&a, vec![card]).await.unwrap(); + crate::agent::todos::ops::replace(&a, vec![card]) + .await + .unwrap(); let a_again = crate::agent::todos::ops::list(&a).await.unwrap(); - assert_eq!(a_again.cards.len(), 1, "a later turn of the same session reads it back"); - assert!(crate::agent::todos::ops::list(&b).await.unwrap().cards.is_empty()); + assert_eq!( + a_again.cards.len(), + 1, + "a later turn of the same session reads it back" + ); + assert!(crate::agent::todos::ops::list(&b) + .await + .unwrap() + .cards + .is_empty()); } diff --git a/crates/openhuman-core/src/integrations/task_sources/store.rs b/crates/openhuman-core/src/integrations/task_sources/store.rs index 8c2ebfc499..ce8fd614e7 100644 --- a/crates/openhuman-core/src/integrations/task_sources/store.rs +++ b/crates/openhuman-core/src/integrations/task_sources/store.rs @@ -295,9 +295,8 @@ pub fn mark_ingested(config: &Config, source_id: &str, task: &NormalizedTask) -> /// brand-new one in its logs. pub fn was_ingested(config: &Config, source_id: &str, external_id: &str) -> Result { with_connection(config, |conn| { - let mut stmt = conn.prepare( - "SELECT 1 FROM ingested_tasks WHERE source_id = ?1 AND external_id = ?2", - )?; + let mut stmt = + conn.prepare("SELECT 1 FROM ingested_tasks WHERE source_id = ?1 AND external_id = ?2")?; let mut rows = stmt.query(params![source_id, external_id])?; Ok(rows.next()?.is_some()) }) diff --git a/crates/openhuman-core/src/integrations/task_sources/store_tests.rs b/crates/openhuman-core/src/integrations/task_sources/store_tests.rs index 18a9ad0b61..c11b3ebf36 100644 --- a/crates/openhuman-core/src/integrations/task_sources/store_tests.rs +++ b/crates/openhuman-core/src/integrations/task_sources/store_tests.rs @@ -163,8 +163,7 @@ fn remove_deletes_and_cascades_ingested() { 25, ) .unwrap(); - mark_ingested(&config, &src.id, &sample_task("1", "A", "2025-01-01")) - .unwrap(); + mark_ingested(&config, &src.id, &sample_task("1", "A", "2025-01-01")).unwrap(); remove_source(&config, &src.id).unwrap(); assert!(get_source(&config, &src.id).is_err()); @@ -268,10 +267,8 @@ fn list_ingested_orders_newest_first() { ) .unwrap(); - mark_ingested(&config, &src.id, &sample_task("1", "first", "2025-01-01")) - .unwrap(); - mark_ingested(&config, &src.id, &sample_task("2", "second", "2025-01-02")) - .unwrap(); + mark_ingested(&config, &src.id, &sample_task("1", "first", "2025-01-01")).unwrap(); + mark_ingested(&config, &src.id, &sample_task("2", "second", "2025-01-02")).unwrap(); let listed = list_ingested(&config, &src.id, 10).unwrap(); assert_eq!(listed.len(), 2); // Newest ingested_at first; "2" was inserted last. From 8e46d1e2d0e91794774d9065b7e3019858a16841 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 15:55:48 +0530 Subject: [PATCH 13/15] chore(deps): update tinyagents subproject commit Update the pinned commit for the tinyagents vendored dependency to incorporate upstream fixes and improvements. Auto-committed-on: macbook --- vendor/tinyagents | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/tinyagents b/vendor/tinyagents index 385ffab927..e028fa7466 160000 --- a/vendor/tinyagents +++ b/vendor/tinyagents @@ -1 +1 @@ -Subproject commit 385ffab92765a5d05e2df9257af8bb07ecd820bd +Subproject commit e028fa746609b98df044add8409d76224a84bdbd From c49c6a16194eb4c01c701a99ac676348dcd9fd6a Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 16:00:28 +0530 Subject: [PATCH 14/15] fix(tests): correct expected error message for invalid status Updated the test assertion to match the actual error message returned by the validation logic, ensuring the test correctly verifies the behavior for invalid todo status values. Auto-committed-on: macbook --- crates/openhuman-core/src/agent/tools/todo_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/openhuman-core/src/agent/tools/todo_tests.rs b/crates/openhuman-core/src/agent/tools/todo_tests.rs index f84bbb82f9..a23249e654 100644 --- a/crates/openhuman-core/src/agent/tools/todo_tests.rs +++ b/crates/openhuman-core/src/agent/tools/todo_tests.rs @@ -91,7 +91,7 @@ async fn bad_input_is_a_tool_error_not_a_harness_error() { ), ( json!({ "todos": [{ "content": "x", "status": "someday" }] }), - "invalid status", + "status must be", ), (json!({ "todos": "not a list" }), "invalid `todos`"), ( From ba77093f466745c5534b82d23825e9b0b530a0ae Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 22 Sep 2026 16:24:54 +0530 Subject: [PATCH 15/15] fix(orchestrator): a todo write is bookkeeping; the same response carries the next step's call --- .../agent/registry/agents/orchestrator/prompt.md | 2 +- scripts/prompt-budget.limits | 14 +++++++------- vendor/tinyagents | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt.md b/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt.md index c35e9bdc33..b3d4a41fe2 100644 --- a/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt.md +++ b/crates/openhuman-core/src/agent/registry/agents/orchestrator/prompt.md @@ -9,7 +9,7 @@ Take the first branch that applies: 4. **Needs a specialist**: the specialists you can call are in your tool list with their own descriptions. **Capabilities not in your tool list** names the ones a skill holds; reach those through `use_skill`. Workers return only their result; carry out any `## Handoff Plan` they return yourself, under the approval gate. 5. **Distill every delegated reply**: keep what answers the question, drop the worker's notes. Never paste a sub-agent's response verbatim. -Live or time-sensitive asks (weather, forecasts, prices, recent news, "use live data") get answered now: one quick fact direct, anything broader via `research`. Don't stop at a lead-in; make the tool call in the same message. +Live or time-sensitive asks (weather, forecasts, prices, recent news, "use live data") get answered now: one quick fact direct, anything broader via `research`. Don't stop at a lead-in; make the tool call in the same message. A `todo` write is bookkeeping, not progress: the response that updates the list also carries the call that does the next item, and an item is `completed` only once its result is in the conversation. Before searching, check **Connected MCP Servers**: if one can answer, hand it to `use_mcp_server`. ## Sub-agents diff --git a/scripts/prompt-budget.limits b/scripts/prompt-budget.limits index 193783b2c0..4ede93c5ad 100644 --- a/scripts/prompt-budget.limits +++ b/scripts/prompt-budget.limits @@ -222,17 +222,17 @@ # without their generated budget update. The morning briefing's # fixed prefix is 3 B larger; all other recorded ceilings stay put. -morning_briefing:10769:59064 +morning_briefing:10769:59334 trigger_triage:7537:0 workflow_builder:76501:28987 summarizer:7351:0 -tools_agent:5229:59064 -orchestrator:9518:20434 -code_executor:11455:13028 +tools_agent:5229:59334 +orchestrator:9717:20704 +code_executor:11455:13298 crypto_agent:10992:10454 task_manager_agent:4531:7602 -planner:7829:5306 -skill_creator:5464:11280 +planner:7829:5576 +skill_creator:5464:11550 flow_discovery:8522:8228 profile_memory_agent:5515:11010 settings_agent:4721:9652 @@ -331,7 +331,7 @@ tool:suggest_workflows:2445 tool:spawn_async_subagent:1556 tool:save_workflow:1957 tool:spawn_parallel_agents:1839 -tool:todo:590 +tool:todo:860 tool:search_tool_catalog:1695 tool:use_skill:1711 # One action-dispatched memory surface replaces the separately registered diff --git a/vendor/tinyagents b/vendor/tinyagents index e028fa7466..3c9ba00cea 160000 --- a/vendor/tinyagents +++ b/vendor/tinyagents @@ -1 +1 @@ -Subproject commit e028fa746609b98df044add8409d76224a84bdbd +Subproject commit 3c9ba00cea1581352977f8c5b7167c1747fa7c8d