Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
ccaf50b
test(worktree): add serialization contract test for WorktreeStatus
senamakel Aug 21, 2026
0ea6206
chore(deps): update tinyagents subproject commit
senamakel Aug 21, 2026
0e6f4ab
chore: files changed src/openhuman/agent/orchestration/worktree.rs
senamakel Aug 21, 2026
6182e84
test: remove redundant `sanitize_run_id` test
senamakel Aug 21, 2026
8ead3da
fix(orchestration): rename GitWorktreeIsolation to OpenHumanWorktreeI…
senamakel Aug 21, 2026
85742d8
chore: files changed src/openhuman/agent/harness/subagent_runner/ops/…
senamakel Aug 21, 2026
73c7056
chore(tinyagents): remove unused subagent_graph module
senamakel Aug 21, 2026
c67acf1
feat(orchestration): document why worktree_path is None for inherited…
senamakel Aug 21, 2026
5d7cbb7
feat(orchestration): expose dispatch mode on prepared parallel tasks
senamakel Aug 21, 2026
c1110a0
test(spawn-parallel-agents): add dispatch-mode and claim-conflict tests
senamakel Aug 21, 2026
090e62e
test(spawn_parallel_agents): extract shared test helpers for parent c…
senamakel Aug 21, 2026
39c9564
refactor(orchestration): extract ownership path parsing into shared u…
senamakel Aug 21, 2026
acf3fa5
refactor(orchestration): split parallel task admission into two passes
senamakel Aug 21, 2026
76a5ff9
fix(orchestration): extend parallel graph imports for workspace claims
senamakel Aug 21, 2026
78d4c6c
refactor(orchestration): replace custom task store enum with registry
senamakel Aug 21, 2026
5a49dd1
refactor(orchestration): simplify task store type usage in tests
senamakel Aug 21, 2026
a05dc81
refactor(orchestration): delegate orphan reconciliation to shared lib…
senamakel Aug 21, 2026
f14ab52
feat(tinyagents): re-export TaskStoreRegistry and related functions
senamakel Aug 21, 2026
78b39fd
chore(orchestration): remove unused import of OrchestrationControlOut…
senamakel Aug 21, 2026
386392a
fix: remove unused import of `JsonlTaskStore` in orchestration module
senamakel Aug 21, 2026
a7d3aef
refactor(extract_tool): replace buffer_unordered with map_reduce for …
senamakel Aug 21, 2026
716cfdd
fix(extract_tool): replace unused import with required one
senamakel Aug 21, 2026
85700d3
chore(docs): update module-level comments for migration plan clarity
senamakel Aug 21, 2026
e9506c5
docs(tinyagents-drift-ledger): add WP-5 parallel fan-out and worktree…
senamakel Aug 21, 2026
216ae27
chore: reorder imports and fix formatting across orchestration and wo…
senamakel Aug 21, 2026
1ca0cc2
fix(orchestration): distinguish isolated from read-only workspace claims
senamakel Aug 21, 2026
91e6be7
feat(orchestration): replace tinyagents parallel primitives with dire…
senamakel Aug 21, 2026
943acbe
feat(orchestration): delegate shared-workspace arbitration to tinyagents
senamakel Aug 21, 2026
f5f80ef
chore(tinyagents): remove unused subagent pipeline graph scaffold
senamakel Aug 21, 2026
988a6a0
refactor(orchestration): extract orphaned sub-agent reason into a fun…
senamakel Aug 21, 2026
ca5e6b2
fix(docs): correct store selection ownership in drift ledger
senamakel Aug 21, 2026
d63b2a7
Merge main into ta-subagents, resolve tinyagents submodule
senamakel Aug 21, 2026
009a536
docs(tinyagents-drift-ledger): update status entries for worktree and…
senamakel Aug 21, 2026
0f86c90
fix(running_subagents): correct log source identifiers in orphaned ta…
senamakel Aug 21, 2026
7034498
test(orchestration): add test for invalid ownership path rejection
senamakel Aug 21, 2026
bfe27a8
chore(deps): update tinyagents to 2.1.1 and downgrade two Windows dep…
senamakel Aug 21, 2026
5e8b0f9
chore(deps): update Cargo.lock to downgrade several Windows bindings
senamakel Aug 22, 2026
98797eb
fix(subagent-runner): remove reason from hook-denied log
senamakel Aug 22, 2026
ea57996
Merge remote-tracking branch 'refs/remotes/upstream/main' into pr/5667
senamakel Aug 22, 2026
dbf0c74
test(raw_coverage): add tool_specs field to test prompt dumps
senamakel Aug 22, 2026
cf7e5af
test(raw_coverage): add tool_specs field to test expectation
senamakel Aug 22, 2026
1854404
fix(tests): update assertion for delegate tool description change
senamakel Aug 22, 2026
84b9e83
Merge commit '8c03315b' into pr/5667
senamakel Aug 22, 2026
207e18a
chore(tests): remove redundant meet settings test block
senamakel Aug 22, 2026
38b25cb
test: remove meet settings from expected controller schemas
senamakel Aug 22, 2026
80ab584
test(domain_modules_e2e): remove meet settings round-trip assertions
senamakel Aug 22, 2026
d084b84
Merge remote-tracking branch 'upstream/main' into pr/5667
senamakel Aug 22, 2026
4ef4891
chore: files changed tests/config_auth_app_state_connectivity_e2e.rs
senamakel Aug 22, 2026
d4f555a
Merge remote-tracking branch 'upstream/main' into pr/5667
senamakel Aug 22, 2026
e665a88
Merge upstream/main into pr/5667
senamakel Aug 22, 2026
d511fe4
fix: correct test assertion for spawn_parallel_agents
senamakel Aug 22, 2026
7cb3f21
fix(tests): correct spawn_parallel_agents test to use proper agent ID
senamakel Aug 22, 2026
5454975
fix(tests): correct spawn_parallel_agents test to use proper agent ID
senamakel Aug 22, 2026
0001629
fix(tests): update raw coverage test for agent round 26
senamakel Aug 22, 2026
61ea90b
chore(ci): update coverage presence allowlist
senamakel Aug 22, 2026
c2ae687
chore(ci): add coverage presence allowlist
senamakel Aug 22, 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
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 15 additions & 15 deletions app/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 19 additions & 1 deletion docs/tinyagents-drift-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,30 @@ this section as investigation history.
| WP2-3 | `model_council/` ensemble | **CLOSED / DELETED** | The generic fan-out already used `tinyagents::graph::parallel::map_reduce`; upstream subsequently removed the unreachable product council and registry surfaces during dead-code cleanup. |
| WP2-4 | `tool_status/` failure classification | **HOST-OWNED** | The types are serialized into OpenHuman threads/UI and the classifier consumes OpenHuman security markers, product retry categories, and user-facing remediation copy. TinyAgents owns raw tool outcomes; the host mapping is deliberately downstream product policy. |

## WP-5 Parallel Fan-Out And Worktree Ownership Audit

| Surface | Status | Ownership / exit evidence |
| --- | --- | --- |
| `orchestration/worktree.rs` git plumbing — `BaseRef`, `WorktreeStatus`, `create`/`list`/`status`/`diff_summary`/`remove`, `detect_overlaps`, `sanitize_run_id` | **CLOSED / DELETED** | Near-verbatim twins of `tinyagents::harness::workspace::git` (`detect_overlaps` and `detect_worktree_overlaps` matched down to the `BTreeSet` dedupe; the two error enums carried identical message strings). Deleted and re-exported under the historical OpenHuman names, so RPC schemas, tools and tests are unchanged. 618 → 242 LOC. The RPC wire shape is pinned by `worktree_status_serializes_with_stable_camel_case_keys`, written and passing **before** the swap and again after; the crate carries the identical `sanitize_run_id` test already. |
| `OpenHumanWorktreeIsolation` (was `GitWorktreeIsolation`) | **HOST-OWNED** | Wraps the crate provider to add the two things that are OpenHuman's rather than every host's: the `openhuman.worktree:{agent}:{run_id}` policy-id convention, and `DomainEvent::Workspace{Prepared,Cleanup}` on the global bus. |
| `enforce_workspace_path` + `WorkspacePathError` | **HOST-OWNED (currently uncalled)** | Descriptor-policy gate that publishes `DomainEvent::WorkspaceViolation`. A repository-wide search finds no caller; it is retained because removing a public fail-closed gate is a separate judgement from this migration, and is recorded here so the next reader does not have to rediscover it. Split off `WorktreeError` (now the crate's git error) since it is about descriptor policy, not git. |
| Shared-workspace claim arbitration — `ownership_file_paths` path validation, `paths_overlap`, the serial-claim accumulator | **CLOSED / CRATE ADOPTED** | Upstreamed as `tinyagents::graph::parallel::claims` (`WorkspaceClaim`, `plan_shared_workspace_dispatch`, `parse_relative_claim_paths`, `paths_overlap`). `prepare_spawn_parallel_tasks_from_defs` is now two passes: OpenHuman policy admits or rejects, then one planner call arbitrates every admitted claim in input order. Behaviour parity is the gate, and `spawn_parallel_agents_tests.rs` passes **unedited** across the cutover; three assertions pinning the mixed-batch dispatch sequence, disjoint-ownership admission, and directory containment were added before it. |
| `files:` ownership syntax, rejection copy, `shared_workspace_write_capable_tools`, `definition_visible_tool_permissions`, `ParallelTaskRejectionKind`, `integrations_agent` toolkit rule, subagent allowlist | **HOST-OWNED** | Product policy and user-facing phrasing. The crate returns `ClaimConflict` as data specifically so the `isolation="worktree"` remedy sentence stays here; `shared_workspace_conflict_message` renders it. |
| `annotate_stale_parent_reads`, `overlap_warnings_for_results` | **HOST-OWNED** | The first reads OpenHuman `file_state` task-locals with no crate analogue; the second is `serde_json` shaping over the crate's overlap detector. Host projection over a crate primitive, not duplication. |
| `run_spawn_parallel_workers` serial-then-parallel execution | **HOST-OWNED (deliberate)** | The crate contributes the *plan*, not the scheduler. Today the host serializes the whole batch when any worker needs serializing; a crate-side executor running only the claimed workers serially would be a **narrower** guarantee than what ships. Keeping execution host-side keeps the diff to "who computed this decision", which is exhaustively unit-testable. |
| `tinyagents/subagent_graph.rs` no-op pipeline skeleton | **CLOSED / DELETED** | Six nodes whose bodies only pushed their own name onto a `Vec`, compiled and executed on **every** sub-agent spawn from `ops/runner.rs`. A scaffold for a per-phase cutover that WP-5 concluded should not happen (see below). Deleted with its call site and its `topology.rs` export. |
| `extract_tool.rs` bounded fan-out | **CLOSED / CRATE ADOPTED** | Hand-rolled `buffer_unordered(3)` plus a manual index-tag-and-re-sort, replaced by `map_reduce` with `FailurePolicy::BestEffort`, which returns outcomes in input order. The per-chunk provider error stays the fan-out's *item* rather than its error, so one failed chunk still drops with a warning instead of aborting its siblings. |
| `harness/subagent_runner/` (7,471 LOC) | **HOST-OWNED — do not relocate** | Its own module docs call it the OpenHuman *build pipeline*, and that is accurate: definition lookup, tier gating, archetype prompt assembly, toolkit filtering, sandbox narrowing, the deterministic memory fast path, Composio resolution, artifact offload, transcript persistence. The generic contract it would map onto already exists as `harness::host::HostCapabilities` (`ContextComposer` / `DefinitionRegistry` / `SecurityGate` / `ModelResolver`) and is unused by the host. The open question is therefore whether OpenHuman should *implement those traits*, not whether to move this code across the GPL boundary — a separate design-gated package. |

## WP-5 Detached Lifecycle Ownership Audit

| Surface | Status | Ownership / exit evidence |
| --- | --- | --- |
| Process-local detached task map, ownership checks, status receivers, cancellation tokens, abort handles, terminal sweep, steering lookup | **CLOSED / CRATE ADOPTED — tinyagents#75** | TinyAgents #75 merged as `d548657`; the canonical vendored pointer `4358efe` includes its `DetachedTaskRegistry`. OpenHuman calls the crate registry for snapshots, wait/timeout, owned and trusted steering, per-task/thread/global cancellation, and soft-cap cleanup. Distinct lock-poison errors are propagated by the crate. All 17 focused host tests pass. |
| Durable detached task lifecycle | **HOST PROJECTION ON CRATE STORE** | OpenHuman retains workspace-specific `JsonlTaskStore` selection and maps product `SubagentStatus` into crate `OrchestrationTaskStatus`; this is durable product projection, not executor ownership. |
| Durable detached task lifecycle | **HOST PROJECTION ON CRATE STORE** | OpenHuman maps product `SubagentStatus` into crate `OrchestrationTaskStatus` and owns the store's on-disk location; this is durable product projection, not executor ownership. (Store *selection* itself has since moved to the crate — see the `TaskStoreRegistry` row below, which supersedes the earlier wording of this one.) |
| Per-workspace store selection + durable→memory fallback (`DetachedTaskStore` enum, `TASK_STORES` map, `open_task_store`) | **CLOSED / CRATE ADOPTED** | Upstreamed as `TaskStoreRegistry` + `open_jsonl_task_store_or_memory`. Opening a second store over one append log gives two writers with independently replayed state, so the caching is part of the contract, not an optimization. The 12-arm delegating `TaskStore` impl is deleted; the fallback ladder is ported exactly (a `create_dir_all` failure degrades to memory just as an unreadable log does — a read-only workspace must stay able to spawn work). Lock poisoning is now typed (`TaskStoreRegistryError`) rather than `.expect()`ed. All 17 focused `running_subagents` tests pass unedited. |
| Boot-time orphan reconciliation state machine | **CLOSED / CRATE ADOPTED** | `reconcile_orphaned_tasks` owns which statuses are live and which terminal state each takes (`CancelRequested` → cancelled, other live → failed), and captures per-task transition failures rather than aborting — a record racing to terminal between listing and transition is expected. The reason is passed as a closure, so `"sub-agent orphaned by core restart (was \`running\`)"` and the `publish_subagent_failed` lifecycle event stay host-side. Already wired at `core/jsonrpc.rs`. |
| `REGISTRY_SOFT_CAP` (256) | **HOST-TUNED CRATE POLICY — no action** | The sweep is crate-owned: the constant is passed into `DetachedTaskRegistry::new`, which performs it. Only the number is OpenHuman's. Recorded because an earlier scoping pass mistook it for a host-side duplicate. |
| Detached task metadata, RPC/UI delivery, `RunQueue` fallback | **HOST-OWNED** | Agent/session/thread/workspace metadata, cancellation notices, background delivery, trusted desktop RPC, and compatibility steering are OpenHuman product surfaces. The fallback can shrink independently after live crate-steering parity, but does not block generic registry deletion. |

TinyAgents #75 is merged and the temporary integration gitlink has been
Expand Down
10 changes: 9 additions & 1 deletion scripts/ci/coverage-presence-allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@
# #5619 merged first, the lane now compiles the family, and the two lines were
# deleted rather than left to rot into "excluded on purpose".
#
# (empty)
# src/openhuman/runtime/client/disabled.rs is the `modules`-OFF stand-in for
# the runtime client facade (`#[cfg(not(feature = "modules"))] mod disabled;` in
# runtime/client/mod.rs). `modules` is in BOTH `[features] default` and
# product-features.txt, so the coverage lane (default + product-features.txt)
# always builds with the gate ON and the OFF-branch stub is never compiled — the
# same shape as the `stub.rs` structural category, but this file is named
# `disabled.rs` because it answers as a runtime that is "disabled", not as a
# generic facade stub.
src/openhuman/runtime/client/disabled.rs
90 changes: 51 additions & 39 deletions src/openhuman/agent/harness/subagent_runner/extract_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex as StdMutex};

use async_trait::async_trait;
use futures::stream::StreamExt;
use serde_json::{json, Value};
use tinyagents::graph::parallel::{map_reduce, FailurePolicy, ParallelOptions};

use super::handoff::{chunk_content, ResultHandoffCache, HANDOFF_MAX_ENTRIES};
use crate::openhuman::agent::harness::session::transcript::{
Expand Down Expand Up @@ -264,12 +264,12 @@ impl Tool for ExtractFromResultTool {
);

// Map stage: each chunk extracts items matching `query` from
// ITS OWN slice only. Dispatched with bounded concurrency —
// `buffer_unordered(MAP_CONCURRENCY)` keeps at most N calls in
// flight at any time. Fully parallel `join_all` was generating
// 504-gateway-timeout storms from the staging proxy when 7+
// concurrent calls piled onto the upstream; batching at 3
// trades some wall-clock time for reliability.
// ITS OWN slice only. Dispatched through the shared bounded fan-out
// (`tinyagents::graph::parallel::map_reduce`), which keeps at most
// `MAP_CONCURRENCY` calls in flight and hands results back in input
// order. Fully parallel `join_all` was generating 504-gateway-timeout
// storms from the staging proxy when 7+ concurrent calls piled onto the
// upstream; batching at 3 trades some wall-clock time for reliability.
const MAP_CONCURRENCY: usize = 3;
let total_chunks = chunks.len();

Expand All @@ -290,10 +290,9 @@ impl Tool for ExtractFromResultTool {
// bakes it into `chat`).
let model = self.model.clone();

// Consume `chunks` with `into_iter` so each async block owns
// its `String` — `buffer_unordered` polls the stream lazily
// and needs futures with no borrows into the enclosing scope.
let map_futures = chunks.into_iter().enumerate().map(|(i, chunk)| {
// Each chunk's future owns its `String`: the fan-out polls lazily and
// needs futures with no borrows into the enclosing scope.
let extract_chunk = move |i: usize, chunk: String| {
let chat = chat.clone();
let tool_name = cached.tool_name.clone();
let query = query.to_string();
Expand Down Expand Up @@ -348,39 +347,52 @@ impl Tool for ExtractFromResultTool {
&model,
);

(i, result)
// The per-chunk result is the fan-out's *item*, not its error:
// a failed chunk is dropped below with a warning, and must not
// abort its siblings.
Ok(result)
}
});

let mut map_results: Vec<(usize, _)> = futures::stream::iter(map_futures)
.buffer_unordered(MAP_CONCURRENCY)
.collect()
.await;
// `buffer_unordered` yields futures in completion order; restore
// original chunk order so the concatenated output matches the
// natural ordering of the underlying tool result (e.g. Notion's
// reverse-chrono page list).
map_results.sort_by_key(|(i, _)| *i);

let partials: Vec<String> = map_results
};

// `map_reduce` returns outcomes in input order, so the concatenated
// output keeps the natural ordering of the underlying tool result
// (e.g. Notion's reverse-chrono page list) with no re-sort here.
let outcome = map_reduce(
chunks,
ParallelOptions::default()
.with_max_concurrency(MAP_CONCURRENCY)
.with_failure_policy(FailurePolicy::BestEffort),
extract_chunk,
)
.await
.map_err(|e| anyhow::anyhow!(e.to_string()))?;

let partials: Vec<String> = outcome
.outcomes
.into_iter()
.filter_map(|(i, r)| match r {
Ok(text) => {
let trimmed = text.trim();
if trimmed.is_empty() {
.filter_map(|item| {
let i = item.index;
// `BestEffort` never fails an item at the fan-out level; the
// inner `Result` is the provider call's own outcome.
let r = item.result.unwrap_or_else(|e| Err(anyhow::anyhow!(e)));
match r {
Ok(text) => {
let trimmed = text.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
Err(e) => {
tracing::warn!(
chunk_idx = i,
error = %e,
"[extract_from_result] map-stage provider call failed; dropping partial"
);
None
} else {
Some(trimmed.to_string())
}
}
Err(e) => {
tracing::warn!(
chunk_idx = i,
error = %e,
"[extract_from_result] map-stage provider call failed; dropping partial"
);
None
}
})
.collect();

Expand Down
12 changes: 9 additions & 3 deletions src/openhuman/agent/harness/subagent_runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@
//! This module is the OpenHuman **build pipeline** around that TinyAgents
//! run: definition lookup/allowlists, archetype prompt assembly, toolkit
//! filtering, sandbox/action-root narrowing, checkpoint/handback, and
//! worker-thread transcript mirroring. Mapping it onto TinyAgents
//! `SubAgent`/`SubAgentSession`/subgraph primitives is tracked in WP-5 of
//! `docs/tinyagents-migration-plan-2026-07-22.md`.
//! worker-thread transcript mirroring.
//!
//! It **stays host-owned**. The generic contract it would map onto already
//! exists as `tinyagents::harness::host::HostCapabilities` — `ContextComposer`,
//! `DefinitionRegistry`, `SecurityGate`, `ModelResolver` are exactly the phases
//! named above. So the open question is not whether to relocate this pipeline
//! into the crate (that would push product policy across the GPL boundary) but
//! whether OpenHuman should implement those four traits and let the crate
//! drive. That is its own design-gated package, not part of WP-5.
//!
//! ## Layout
//!
Expand Down
Loading
Loading