docs(agent): sweep retired-loop names out of current-behavior comments (#4249)

Phase 10: every code comment / domain README that still described
run_turn_engine, run_tool_call_loop, or harness/tool_loop.rs as the CURRENT
implementation now points at the live tinyagents surfaces
(run_turn_via_tinyagents_shared, RepeatedToolFailureMiddleware,
ApprovalSecurityMiddleware, the steering forwarder, SharedToolAdapter /
ToolPolicyMiddleware). Intentionally-historical parity notes kept verbatim.
Comments only — zero behavior changes.

Notable finding recorded in tokenjuice/README.md: compact_tool_output lost its
only production caller with the retired tool_loop; re-wiring it as an
after_tool middleware is an open spec follow-up.
This commit is contained in:
Steven Enamakel
2026-07-01 13:21:51 -07:00
parent fcf9eb721c
commit f5a6b5196c
25 changed files with 115 additions and 77 deletions
+33 -9
View File
@@ -3,14 +3,17 @@
Status: draft migration backlog
TinyAgents source reviewed: `tinyhumansai/tinyagents` `origin/main` at
`8f226f1`, crate version `1.1.0`.
`8f226f1`, crate version `1.1.0`. Current OpenHuman dependency in this checkout
is `tinyagents = "1.2"`.
OpenHuman already depends on `tinyagents = "1.1"` and already routes the live
OpenHuman already depends on `tinyagents = "1.2"` and already routes the live
agent turn through `src/openhuman/tinyagents/`. This spec is not a proposal to
add TinyAgents. It is a todo list for moving the rest of OpenHuman's generic
agent runtime behavior onto TinyAgents primitives while keeping OpenHuman-owned
product semantics in OpenHuman.
Current inventory snapshot: [`tinyagents-harness-migration-audit.md`](tinyagents-harness-migration-audit.md).
## Goal
Use TinyAgents as the generic runtime for:
@@ -73,7 +76,7 @@ OpenHuman Rust core:
Already done or partially done:
- `Cargo.toml` pins `tinyagents = "1.1"` with default features only.
- `Cargo.toml` pins `tinyagents = "1.2"` with default features only.
- `src/openhuman/tinyagents/mod.rs` registers OpenHuman `Provider` and `Tool`
adapters on `tinyagents::harness::runtime::AgentHarness`.
- `ProviderModel` maps OpenHuman `ChatRequest`/`ChatResponse` into
@@ -222,7 +225,7 @@ OpenHuman needs restart-safe SQL/JSON ledgers.
`memory`, `subconscious`, etc.) can resolve to a TinyAgents model entry
while retaining OpenHuman provider strings and config compatibility.
- [ ] Translate OpenHuman provider capability data into TinyAgents model profiles.
- [~] Translate OpenHuman provider capability data into TinyAgents model profiles.
- OpenHuman files: `src/openhuman/inference/provider/traits.rs`,
`src/openhuman/inference/provider/factory.rs`,
`docs/inference-provider-catalog.md`.
@@ -231,6 +234,17 @@ OpenHuman needs restart-safe SQL/JSON ledgers.
- Acceptance: context window, tool calling, streaming, vision, structured
output, reasoning, local/cloud source, and provider-family metadata are
available before dispatch.
- **Partial:** every `ProviderModel` registered by the shared runner now
carries a crate `ModelProfile` built at construction from the provider's
canonical capability accessors — tool calling (+parallel), vision
(`modalities.image_in`), streaming, local/remote source — plus the
runner-threaded token limits (`with_context_window` → `max_input_tokens`,
output cap → `max_output_tokens`). `ChatModel::profile()` returns it, so
the crate's pre-dispatch validation and structured-output strategy see real
capabilities. Remaining: structured-output/JSON-schema/reasoning flags
(no OpenHuman capability source yet), release/status metadata, and a
registry-level model *catalog* (ties into the workload-route registry item
above).
- [ ] Move model fallback and retry policy to TinyAgents policy/middleware.
- OpenHuman files: `src/openhuman/inference/provider/reliable.rs`,
@@ -579,7 +593,7 @@ assessment, and migration coverage are complete.
- Candidate outcome: replace dozens of boilerplate default `graph.rs` files
with registry defaults, keeping files only for agents with custom graphs.
- [~] Audit stale architecture references to removed in-house graph/loop code.
- [x] Audit stale architecture references to removed in-house graph/loop code.
- Current files: `gitbooks/developing/architecture/agent-harness.md`,
`src/openhuman/context/README.md`.
- Candidate stale names: `src/openhuman/agent_graph/`, `GraphBlueprint`,
@@ -594,9 +608,16 @@ assessment, and migration coverage are complete.
section as HISTORICAL-removed (strong inline callout pointing at the live
tinyagents surfaces); fixed `context/README.md` "Used by" line that still
referenced the deleted `reduce_before_call`/`ProviderSummarizer`/
`SegmentRecapSummarizer`/`unified_compaction_enabled`. Remaining: a sweep of
code doc-comments across many `.rs` files that still name `run_turn_engine`,
`run_tool_call_loop`, `tool_loop.rs` (comments only — no behavior).
`SegmentRecapSummarizer`/`unified_compaction_enabled`. **Sweep completed:**
every code doc-comment that described `run_turn_engine`/`run_tool_call_loop`/
`tool_loop.rs` as *current* behavior (≈20 sites across 15 files: tools,
security, tokenjuice, triage, orchestration steering, task-local contexts,
cron, host_runtime, event-bus example, test-file headers) now points at the
live tinyagents surfaces; intentionally-historical "legacy X was removed /
parity with" notes were kept. Domain READMEs (tools, tokenjuice, approval)
fixed too — the tokenjuice one now records that `compact_tool_output` lost
its only production caller with the retired loop (re-wiring it as an
`after_tool` middleware is an open follow-up).
- [ ] Audit `src/openhuman/context/{pipeline,guard,microcompact}.rs`.
- Current role: context stats/session-memory bookkeeping plus older
@@ -649,7 +670,7 @@ assessment, and migration coverage are complete.
- Candidate outcome: document why each fanout stays as `join_all` or move it
to `run_parallel_fanout` / graph `Send`.
- [ ] Audit tool registry comments and docs that still describe retired
- [x] Audit tool registry comments and docs that still describe retired
direct-loop behavior.
- Current files: `src/openhuman/tools/traits.rs`,
`src/openhuman/tools/README.md`,
@@ -659,6 +680,9 @@ assessment, and migration coverage are complete.
- Candidate outcome: update comments to describe the TinyAgents execution
path and delete references to the retired serial `harness::tool_loop`
dispatcher once no code path uses it.
- **Done:** `tools/traits.rs` concurrency note and `tools/README.md` "Used
by" now describe the tinyagents execution path (`SharedToolAdapter` /
`ToolPolicyMiddleware`); `session/turn/tools.rs` had no stale references.
## Phase 11 - Testing And Conformance
+1 -1
View File
@@ -31,7 +31,7 @@
//! register_native_global::<AgentTurnRequest, AgentTurnResponse, _, _>(
//! "agent.run_turn",
//! |req| async move {
//! let text = run_tool_call_loop(/* ... */).await
//! let text = run_agent_turn(/* ... */).await
//! .map_err(|e| e.to_string())?;
//! Ok(AgentTurnResponse::new(text))
//! },
@@ -3,7 +3,9 @@
//! These tests cover paths that were missing from the existing `*_tests.rs`
//! co-located files as identified by a coverage gap analysis:
//!
//! 1. Full user→LLM→tool→result→final turn cycle with `run_tool_call_loop`.
//! 1. Full user→LLM→tool→result→final turn cycle — now covered by the
//! tinyagents route's tests (`src/openhuman/tinyagents/tests.rs`), which
//! exercise `run_turn_via_tinyagents_shared` end to end.
//! 2. `MaxIterationsExceeded` downcasts to the typed `AgentError` variant.
//! 3. `visible_tool_names` whitelist: tools outside the set are treated as unknown.
//! 4. `ContextGuard` surfaces `ContextExhausted` and aborts the loop.
+1 -1
View File
@@ -37,6 +37,6 @@ mod tests;
pub use types::{Agent, AgentBuilder};
// Re-export the duplicate-tool-spec guard for sibling harness modules
// (`tool_loop`, `subagent_runner`) so all three provider call sites
// (`session::runtime`, `subagent_runner`) so all provider call sites
// share one tested implementation.
pub(crate) use builder::dedup_visible_tool_specs;
@@ -109,16 +109,17 @@ pub async fn run_subagent(
// Unconditionally heap-allocate the entire run_subagent body so
// every caller doesn't have to carry this future's state inline.
// Tools that delegate run inside the parent agent's already-deep
// `run_turn_engine` poll, so the parent's stack would otherwise pile
// (parent engine state + dispatch_subagent state + run_subagent's
// wrapper state + run_typed_mode state + child engine state) onto
// tokio's 2 MiB worker stack and abort with "thread
// turn poll (the boxed tinyagents harness drive future in
// `run_turn_via_tinyagents_shared`), so the parent's stack would
// otherwise pile (parent turn state + dispatch_subagent state +
// run_subagent's wrapper state + run_typed_mode state + child turn
// state) onto tokio's 2 MiB worker stack and abort with "thread
// 'tokio-rt-worker' has overflowed its stack, fatal runtime error:
// stack overflow" — observed at `[subagent_runner] dispatching
// agent_id=researcher ...` in the `chat-harness-subagent` Playwright
// lane crash. The inner `Box::pin`s around `run_typed_mode` /
// `run_inner_loop` / child `run_turn_engine` further chunk the
// child's state so a single sub-agent run can't blow the stack either.
// lane crash. The inner `Box::pin`s around `run_typed_mode` and the
// child's tinyagents drive future further chunk the child's state so
// a single sub-agent run can't blow the stack either.
Box::pin(async move {
let parent = current_parent().ok_or(SubagentRunError::NoParentContext)?;
let task_id = options
@@ -561,10 +561,10 @@ async fn capped_no_progress_subagent_returns_incomplete_status() {
async fn run_queue_steer_lands_in_subagent_history() {
// End-to-end proof that flipping the subagent loop's run-queue arg from
// `None` to `Some(queue)` wires steering all the way through: a message
// pushed to the queue before the run is drained by the inner
// `run_turn_engine` and appears as a `[User steering message]:` user turn
// in the exact request sent to the provider. This is the mechanism behind
// the `steer_subagent` tool.
// pushed to the queue before the run is drained by the steering forwarder
// in the child's turn (`run_turn_via_tinyagents_shared`) and appears as a
// `[User steering message]:` user turn in the exact request sent to the
// provider. This is the mechanism behind the `steer_subagent` tool.
let provider = ScriptedProvider::new(vec![text_response("acknowledged")]);
let parent = make_parent(provider.clone(), vec![stub("file_read")]);
let def = make_def_named_tools(&[]);
@@ -1514,8 +1514,8 @@ fn nested_subagent_dispatch_runs_on_a_constrained_worker_stack() {
let outcome = outcome.expect(
"nested run_subagent must complete on a 1 MiB worker stack — \
a stack overflow here means the recursion boundary in \
`run_typed_mode` regressed (see `Box::pin` callsites around \
`run_inner_loop` and `run_turn_engine`).",
`run_typed_mode` regressed (see the `Box::pin` callsites around \
`run_typed_mode` and the child's tinyagents drive future).",
);
assert!(
outcome.output.contains("inner-final"),
@@ -13,9 +13,9 @@
//! rehydrates the image from the on-disk sidecar.
//!
//! Mirrors [`super::model_vision_context`]. Scoped around the orchestrator's
//! `run_turn_engine` call; [`current_turn_image_placeholders`] returns an empty
//! vec when no scope is active (CLI / direct invocation / tests) — strictly
//! additive.
//! turn future (`run_turn_via_tinyagents_shared`);
//! [`current_turn_image_placeholders`] returns an empty vec when no scope is
//! active (CLI / direct invocation / tests) — strictly additive.
tokio::task_local! {
/// Image-attachment placeholder tokens from the current turn's user message.
@@ -30,8 +30,8 @@ pub fn current_turn_image_placeholders() -> Vec<String> {
}
/// Run `future` with `placeholders` installed as the current turn's image
/// placeholders. Intended call site is around the orchestrator's
/// `run_turn_engine` invocation.
/// placeholders. Intended call site is around the orchestrator's turn
/// (`run_turn_via_tinyagents_shared`) invocation.
pub async fn with_current_turn_image_placeholders<F, R>(placeholders: Vec<String>, future: F) -> R
where
F: std::future::Future<Output = R>,
@@ -9,10 +9,11 @@
//! cost tracker.
//!
//! This module installs an [`Arc<Mutex<Vec<SubagentUsageEntry>>>`] as a
//! task-local around the parent's `run_turn_engine` call. Synchronous
//! delegations (`spawn_subagent`) run inline on the same tokio task, so the
//! sub-agent runner can [`record_subagent_usage`] its totals into the active
//! collector. After the engine returns, the parent [`drain`]s the collector to:
//! task-local around the parent's turn future (the
//! `run_turn_via_tinyagents_shared` drive). Synchronous delegations
//! (`spawn_subagent`) run inline on the same tokio task, so the sub-agent
//! runner can [`record_subagent_usage`] its totals into the active collector.
//! After the turn returns, the parent [`drain`]s the collector to:
//!
//! 1. fold child tokens + USD into the turn's cumulative meters, and
//! 2. attribute per-child spend for the `chat_done` breakdown (hover detail).
@@ -64,7 +65,8 @@ pub type TurnSubagentUsage = Arc<Mutex<Vec<SubagentUsageEntry>>>;
tokio::task_local! {
/// Active per-turn sub-agent usage collector, installed around the parent's
/// `run_turn_engine` call. Absent outside a turn scope.
/// turn future (`run_turn_via_tinyagents_shared`). Absent outside a turn
/// scope.
static TURN_SUBAGENT_USAGE: TurnSubagentUsage;
}
@@ -115,7 +117,7 @@ pub fn record_subagent_usage(task_id: &str, agent_id: &str, usage: SubagentUsage
/// Run `future` with a fresh sub-agent usage collector installed, returning both
/// the future's output and the gathered per-child entries. Intended call site is
/// around the parent agent's `run_turn_engine` invocation.
/// around the parent agent's turn (`run_turn_via_tinyagents_shared`) invocation.
pub async fn with_turn_collector<F, R>(future: F) -> (R, Vec<SubagentUsageEntry>)
where
F: std::future::Future<Output = R>,
+2 -1
View File
@@ -88,7 +88,8 @@ impl RuntimeAdapter for NativeRuntime {
// `pip install … | tail`) surfaces as a non-zero exit instead of
// being masked by the last stage's success. Without it the harness
// records the call as successful and the repeated-failure circuit
// breaker (see tool_loop.rs) never trips, so the agent loops on a
// breaker (`RepeatedToolFailureMiddleware`, tinyagents/middleware.rs)
// never trips, so the agent loops on a
// command that is silently failing. `/bin/sh` is dash on
// Debian/Ubuntu and rejects `set -o pipefail`, so this is gated on
// bash actually being present; otherwise we fall back to plain sh.
+5 -5
View File
@@ -20,13 +20,13 @@
//! are treated like retryable cloud failures: retry once, then fall
//! through to local / Deferred.
//!
//! ## Why `run_tool_call_loop` doesn't care about `tools_registry = []`
//! ## Why the turn path doesn't care about `tools_registry = []`
//!
//! The triage agent has `named = []` in its TOML (zero tools). The
//! `run_tool_call_loop` implementation in
//! `src/openhuman/agent/harness/tool_loop.rs` handles an empty registry
//! by just doing a plain `chat_with_history` under the hood — no tool
//! schemas are sent to the backend.
//! tinyagents-backed turn path (`run_turn_via_tinyagents_shared` in
//! `src/openhuman/tinyagents/mod.rs`) handles an empty registry by simply
//! sending no tool schemas to the backend — the turn degrades to a plain
//! chat completion.
use std::future::Future;
use std::sync::Arc;
+2 -2
View File
@@ -24,8 +24,8 @@ pub struct ResolvedProvider {
/// Provider name token — always `"openhuman"` (remote backend).
/// Kept for telemetry / observability compat with the previous two-path design.
pub provider_name: String,
/// Model identifier — the concrete string `run_tool_call_loop`
/// will hand to the provider.
/// Model identifier — the concrete string the turn
/// (`run_turn_via_tinyagents_shared`) will hand to the provider.
pub model: String,
/// Always `false` — local AI is never used for triage.
/// Preserved so existing telemetry subscribers that read this field do not
@@ -6,8 +6,9 @@
//! closes both gaps.
//!
//! Each running async sub-agent registers, keyed by its `task_id`, with:
//! - an `Arc<RunQueue>` — the same steering channel the inner `run_turn_engine`
//! drains at iteration boundaries, so `steer_subagent` can inject a message;
//! - an `Arc<RunQueue>` — the same steering channel the steering forwarder in
//! `run_turn_via_tinyagents_shared` drains mid-turn, so `steer_subagent` can
//! inject a message;
//! - a `watch::Receiver<SubagentStatus>` — so `wait_subagent` can block until the
//! child reaches a terminal status;
//! - an `AbortHandle` — kept for a future `close_agent` tool.
@@ -302,8 +303,9 @@ pub enum SteerError {
AlreadyDone,
}
/// Inject a message into a running sub-agent's steering queue. The child's
/// `run_turn_engine` drains it at the next iteration boundary.
/// Inject a message into a running sub-agent's steering queue. The steering
/// forwarder in the child's `run_turn_via_tinyagents_shared` turn drains it
/// mid-flight.
pub async fn steer(
task_id: &str,
parent_session: &str,
@@ -2,8 +2,9 @@
//!
//! Pairs with `spawn_async_subagent`: that tool returns a `task_id` for a child
//! running in the background. `steer_subagent` pushes a message into that child's
//! steering queue, which its inner `run_turn_engine` drains at the next iteration
//! boundary — so the parent can redirect or feed data to a running sub-agent
//! steering queue, which the steering forwarder in the child's turn
//! (`run_turn_via_tinyagents_shared`) drains mid-flight — so the parent can
//! redirect or feed data to a running sub-agent
//! without waiting for it to finish or restarting it. Mirrors Codex `send_input`.
use crate::openhuman::agent::harness::fork_context::current_parent;
+1 -1
View File
@@ -79,7 +79,7 @@ SQLite DB at `{workspace_dir}/approval/approval.db`, table `pending_approvals` (
- `src/core/jsonrpc.rs` — installs the global gate (`ApprovalGate::init_global`) at startup; wires the approval RPCs.
- `src/core/all.rs` — registers the controller schemas.
- `src/openhuman/agent/harness/tool_loop.rs` (+ subagent_runner) — routes external-effect tool calls through the gate before `execute()`.
- `src/openhuman/tinyagents/middleware.rs` (`ApprovalSecurityMiddleware`, a `wrap_tool` middleware on every turn path) — routes external-effect tool calls through the gate before `execute()` and records the terminal audit row.
- `src/openhuman/channels/providers/web.rs` — sets `APPROVAL_CHAT_CONTEXT`, hosts `ApprovalSurfaceSubscriber`, and routes typed yes/no replies to `approval_decide`.
- `src/openhuman/channels/proactive.rs`, `src/openhuman/agent/triage/escalation.rs`, `src/openhuman/tools/impl/system/install_tool.rs`, `src/openhuman/wallet/execution.rs` — interact with the gate / approval types.
+3 -2
View File
@@ -317,8 +317,9 @@ pub async fn execute_job_now(config: &Config, job: &CronJob) -> (bool, String) {
/// after a single JWT lapse, every retries-exhausted capture pointing at a
/// problem the user can only fix from the UI.
///
/// The right move is the same halt-on-first-occurrence pattern as
/// `agent::harness::tool_loop::BACKEND_USER_STATE_MARKER` (#3334): the
/// The right move is the same halt-on-first-occurrence pattern as the
/// legacy tool loop's `BACKEND_USER_STATE_MARKER` convention (#3334, the
/// loop itself was retired in the tinyagents migration, #4249): the
/// condition is global and retries can't recover it, so we stop after the
/// first attempt. Skipping the `report_error` call too is correct because
/// the existing classifier
@@ -83,7 +83,7 @@ mod tests {
/// Verbatim Anthropic BYO out-of-credits 400 body (Sentry TAURI-RUST-4MM).
/// Direct provider — "anthropic API error", not the managed "OpenHuman API
/// error". The classifier feeds the emit-site `classify_expected_error`
/// demotion, the agent `tool_loop` gate, the `web_errors` net, the
/// demotion, the agent turn's billing gate, the `web_errors` net, the
/// `is_budget_event` before_send filter, AND the cron billing-halt, so
/// pinning the exact wire body makes an Anthropic phrasing drift fail CI
/// rather than silently re-flood Sentry (3793 events leaked before this).
+4 -3
View File
@@ -10,9 +10,10 @@ use tokio::sync::OnceCell;
/// rejection: the identical `(tool, args)` call can never succeed in the
/// current tier (read-only blocking a write, a forbidden/credential path, a
/// disallowed high-risk or hidden-execution command, an off-allowlist command).
/// The agent harness ([`crate::openhuman::agent::harness::tool_loop`]) detects
/// this and halts on the **first verbatim repeat** rather than reiterating a
/// provably-futile call. Kept short and bracketed so it survives the
/// The agent harness's repeated-failure middleware
/// ([`crate::openhuman::tinyagents::middleware::RepeatedToolFailureMiddleware`])
/// detects this and halts on the **first verbatim repeat** rather than
/// reiterating a provably-futile call. Kept short and bracketed so it survives the
/// `Error: …` wrapping the tool layer adds and is easy to grep in logs.
pub const POLICY_BLOCKED_MARKER: &str = "[policy-blocked]";
+1 -1
View File
@@ -61,7 +61,7 @@ This module is **fully self-contained within `openhuman`** — it has no `use cr
## Used by
- `src/openhuman/agent/harness/tool_loop.rs` — the only consumer. Calls `crate::openhuman::tokenjuice::compact_tool_output(&call.name, Some(&call.arguments), &scrubbed, Some(0))` after a successful tool call (post credential-scrub) to compact tool output before it is fed back to the model.
- **Currently unwired on the live turn path.** The only production consumer of `compact_tool_output` was the retired in-house `agent/harness/tool_loop.rs` (removed in the tinyagents migration, issue #4249); the tinyagents route caps/summarizes tool output via `ToolOutputMiddleware` instead. Re-expressing tokenjuice compaction as an `after_tool` middleware on that path is the outstanding follow-up (see `docs/tinyagents-migration-spec.md`).
## Notes / gotchas
+5 -5
View File
@@ -94,8 +94,8 @@ fn state() -> &'static Mutex<State> {
}
tokio::task_local! {
/// The model actually running the current turn/sub-agent, scoped by the
/// agent loop around `run_turn_engine` (mirrors
/// The model actually running the current turn/sub-agent, scoped around
/// the tinyagents turn (`run_turn_via_tinyagents_shared`) (mirrors
/// [`crate::openhuman::agent::harness::model_vision_context`]). When set,
/// compaction savings are priced against *this* model instead of the
/// process-global configured default (issue #4122). Unset ⇒ fall back to
@@ -105,9 +105,9 @@ tokio::task_local! {
}
/// Run `future` with `model` installed as the per-turn attribution model used
/// to price compaction savings. Intended call site is around each
/// `run_turn_engine` invocation, alongside the other per-turn `*_context`
/// scopes (issue #4122).
/// to price compaction savings. Intended call site is around each turn's
/// `run_turn_via_tinyagents_shared` invocation, alongside the other per-turn
/// `*_context` scopes (issue #4122).
pub async fn with_turn_model<F, R>(model: String, future: F) -> R
where
F: std::future::Future<Output = R>,
+1 -1
View File
@@ -105,7 +105,7 @@ None. No `store.rs`; the module holds no persisted state. Tools that persist (me
## Used by
- `src/core/all.rs` — registers the `tools` RPC controllers + schemas.
- `openhuman::agent` harness (`session/builder`, `tool_loop`, `dispatcher`, `subagent_runner`, `agent/tools/*`) — primary consumer; builds and runs the registry, applies the `ToolPolicy`.
- `openhuman::agent` harness (`session/builder`, `dispatcher`, `subagent_runner`, `agent/tools/*`) and the `openhuman::tinyagents` seam (`SharedToolAdapter`, `ToolPolicyMiddleware`) — primary consumers; build the registry and execute/police tools on the tinyagents harness path.
- `openhuman::channels`, `openhuman::routing`, `openhuman::inference::provider` — build tool sets / clean schemas per provider.
- `openhuman::agent_tool_policy`, `openhuman::approval` — read tool metadata (category, external-effect) for policy/approval decisions.
- `openhuman::tool_registry`, `openhuman::runtime_node`, `openhuman::mcp_server` — registry/exposure consumers.
@@ -390,7 +390,8 @@ mod tests {
// The readonly block must carry the hard-reject marker so the agent
// harness recognizes it and halts on a verbatim repeat instead of
// grinding. Ties this tool's literal to the marker const — the
// const→detector half is covered by tool_loop's guard tests.
// const→detector half lives in `RepeatedToolFailureMiddleware` and is
// covered by its tests (`src/openhuman/tinyagents/middleware.rs`).
assert!(
result
.output()
@@ -14,10 +14,10 @@
//! (command / dependency not found) or a `126` (permission denied — often a
//! sandbox restriction) from a generic `1`, so it could not recognise an
//! un-retryable wall and re-ran the identical command. The harness
//! repeated-failure circuit breaker (`RepeatFailureGuard`, see
//! `agent/harness/tool_loop.rs`) still bounds that loop, but only after a
//! few wasted iterations and with a generic halt message, because the
//! root-cause signal had already been thrown away.
//! repeated-failure circuit breaker (`RepeatedToolFailureMiddleware`, see
//! `src/openhuman/tinyagents/middleware.rs`) still bounds that loop, but
//! only after a few wasted iterations and with a generic halt message,
//! because the root-cause signal had already been thrown away.
//!
//! This module surfaces the exit code AND both streams on failure, and appends a
//! short hint for the well-known dependency/sandbox exit codes so the agent can
+2 -1
View File
@@ -1506,7 +1506,8 @@ async fn all_tools_executes_stock_and_twilio_family_against_fake_backend() {
/// Every acting tool gates on `can_act()` and returns its own read-only refusal
/// string. Each of those must carry [`POLICY_BLOCKED_MARKER`] so the agent
/// harness recognizes the block as a hard reject and halts on a verbatim repeat
/// (see `agent::harness::tool_loop::hard_reject_kind`). This pins every tool's
/// (see the marker detection in
/// `tinyagents::middleware::RepeatedToolFailureMiddleware`). This pins every tool's
/// literal to the marker const — drift between them fails here rather than
/// silently letting the agent grind on a doomed call. Args are the minimum
/// needed to reach the `can_act()` check in each tool.
+6 -6
View File
@@ -348,12 +348,12 @@ pub trait Tool: Send + Sync {
/// `cat` invocations and reject parallel `npm install`s) — most
/// tools will ignore it.
///
/// **Wiring note:** the parallel dispatcher in
/// `harness::tool_loop` currently runs tool calls serially
/// regardless of this flag. Annotating tools is still load-
/// bearing: it lets the dispatch refactor land without
/// coordinating with every tool author. See the parallel-tool
/// dispatch follow-up issue.
/// **Wiring note:** the tinyagents harness loop (see
/// `crate::openhuman::tinyagents::tools`) currently executes tool
/// calls serially regardless of this flag. Annotating tools is
/// still load-bearing: it lets a parallel-dispatch refactor land
/// without coordinating with every tool author. See the
/// parallel-tool dispatch follow-up issue.
fn is_concurrency_safe(&self, _args: &serde_json::Value) -> bool {
false
}
@@ -44,7 +44,8 @@ use crate::openhuman::workflows::run_log;
// ── Mock LLM ─────────────────────────────────────────────────────────────
// Minimal scripted provider: pops queued ChatResponses in order. Mirrors the
// one in `agent/harness/tool_loop_tests.rs` (kept local so this file is
// scripted providers in other harness test files (e.g.
// `agent/harness/subagent_runner/ops_tests.rs`; kept local so this file is
// self-contained).
struct ScriptedProvider {
responses: Mutex<Vec<anyhow::Result<ChatResponse>>>,