diff --git a/Cargo.lock b/Cargo.lock index 16dc21b540..00d4d3ac32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7204,7 +7204,7 @@ dependencies = [ [[package]] name = "tinyagents" -version = "2.1.0" +version = "2.1.1" dependencies = [ "async-trait", "bytes", @@ -7317,7 +7317,7 @@ source = "git+https://github.com/tinyhumansai/sdk.git?rev=3ee4123ba3b7a76c5f167d dependencies = [ "base64 0.22.1", "percent-encoding", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "thiserror 2.0.18", diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index cee8aea064..b6a7d25127 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -5601,6 +5601,7 @@ dependencies = [ "tinychannels", "tinycortex", "tinyflows", + "tinyhumans-sdk", "tinyjuice", "tinyplace", "tokio", @@ -8924,7 +8925,7 @@ dependencies = [ [[package]] name = "tinyagents" -version = "2.1.0" +version = "2.1.1" dependencies = [ "async-trait", "bytes", @@ -9025,6 +9026,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "tinyhumans-sdk" +version = "0.1.0" +source = "git+https://github.com/tinyhumansai/sdk.git?rev=3ee4123ba3b7a76c5f167d7bc2c72fca86671292#3ee4123ba3b7a76c5f167d7bc2c72fca86671292" +dependencies = [ + "base64 0.22.1", + "percent-encoding", + "reqwest 0.12.28", + "serde", + "serde_json", + "thiserror 2.0.18", + "url", +] + [[package]] name = "tinyjuice" version = "0.2.1" diff --git a/docs/specs/2026-07-28-agent-runtime-into-tinyagents-design.md b/docs/specs/2026-07-28-agent-runtime-into-tinyagents-design.md new file mode 100644 index 0000000000..bd73e893c1 --- /dev/null +++ b/docs/specs/2026-07-28-agent-runtime-into-tinyagents-design.md @@ -0,0 +1,348 @@ +# Moving `agent/` into TinyAgents + +**Status:** spec + plan. Direction decided by the maintainer on 2026-07-28 +after two rounds of pushback; this document plans the move rather than +re-arguing it. The open questions below are *how*, not *whether*. +**Scope:** relocate `src/openhuman/agent/` (152 files, 70,530 LOC) into +`vendor/tinyagents` as a generic agent runtime, with OpenHuman's coupling +expressed as trait injection. +**Supersedes:** the "permanent host" dispositions for `builder/`, `turn/`, +`runtime.rs`, and `types.rs` in +[`2026-07-28-agent-session-transcript-to-tinyagents-design.md`](2026-07-28-agent-session-transcript-to-tinyagents-design.md) +§6 (verified 2026-07-28: those four rows carry the literal "Permanent host" +verdict at §6), and the `agent/ remainder — STAYS` row in +[`../tinyagents-migration-plan-2026-07-22.md`](../tinyagents-migration-plan-2026-07-22.md) +§7. Both are reopened by this decision; the reopen rows are recorded in +[`../tinyagents-drift-ledger.md`](../tinyagents-drift-ledger.md) (section +"Agent-Relocation Reopen Rows (2026-07-28)"). + +> **Corrections (2026-07-28).** Every count below was re-derived from the +> worktree on 2026-07-28 with +> `rg -o 'crate::openhuman::::' src/openhuman/agent --no-filename | wc -l` +> (outbound) and a word-boundary `agent::` grep over `src/` + +> `app/src-tauri/src/` excluding `src/openhuman/agent/` (inbound). Changes from +> the first draft: outbound domain count 45 → **48**; inbound domain count +> 48 → **50** (47 counting code references only, excluding doc-comment-only +> mentions); `tinyagents` 91 → 90, `tools` 84 → 83, `context` 52 → 51, +> `approval` 10 → 9, `subconscious` 8 → 7, `web_chat` 6 → 5, `tool_status` +> 5 → 4, `memory_store` 41 → 39; `util` promoted out of the "≤ 4 refs" +> remainder bucket (it has 8) and `migrations` dropped from it (0 refs); +> inbound `agent::harness::*` 275 → 300, `agent::triage` 24 → 34, `agent::bus` +> 14 → 25; the Phase 4 exit figure ~2,000 → **547**; "~20–25k LOC stays" +> confirmed by measurement at **22,121 LOC**. The headline `152 files, +> 70,530 LOC` is exact and unchanged. No argument in this document changes as +> a result — every correction moves a number, not a conclusion, and the three +> inbound corrections all move *upward*, reinforcing §6's "inbound coupling is +> the real cost". + +--- + +## 1. Why this is feasible (the enabling fact) + +The earlier objection was dependency direction: `agent/` reaches 48 OpenHuman +domains, so relocating it appeared to force a GPL-redistributed crate to import +Composio, `SecurityPolicy`, and `memory_store`. + +That objection assumes the code moves *as written*. It does not have to, +because **the crate is already generic over a host-supplied state type**: + +```rust +pub struct AgentHarness { … } +pub trait Tool: Send + Sync { … } +pub trait ChatModel: Send + Sync { … } +pub trait Middleware: Send + Sync { … } +``` + +`State` is the injection vehicle. A relocated agent runtime does not import +`crate::openhuman::memory` — it is generic over a `State` that *provides* +memory, and OpenHuman supplies the impl. The crate already ships **18 extension +traits** on exactly this pattern (`ChatModel`, `Tool`, `ChatHistory`, `Store`, +`AppendStore`, `Summarizer`, `EmbeddingModel`, `VectorStore`, `ResponseCache`, +`WorkspaceIsolation`, `HarnessEventJournal`, `HarnessStatusStore`, +`EventListener`, `Middleware`, `ModelMiddleware`, `ToolMiddleware`, +`ModelBaseCall`, `ToolBaseCall`). This move adds ~10 more of the same kind. It +is not a new architecture; it is more of the one already in use. + +The GPL/crates.io concern also narrows correctly: the constraint is that no +*OpenHuman product logic* is published, not that no agent runtime is. Traits and +a generic loop are publishable; `provider_role_for`'s `subconscious` routing and +the `integrations_agent` dispatcher override are not — they become host impls. + +--- + +## 2. Ground truth: the coupling to invert + +### 2.1 Outbound — what `agent/` imports (48 domains) + +By reference count: + +| Refs | Domain | Becomes | +| ---: | --- | --- | +| 195 | `config` (`Config` 53, `AgentConfig` 45, `MemoryConfig` 28, `ContextConfig` 24, …) | **crate config structs**, populated host-side. The single largest blocker — see §4.1 | +| 90 | `tinyagents` (the seam) | dissolves — becomes internal | +| 83 | `tools` | existing `Tool` + `SharedToolAdapter` | +| 72 | `inference` | existing `ChatModel` + a `ModelResolver` trait | +| 57 + 39 + 16 + 11 + 5 + 3 | `memory`, `memory_store`, `memory_tree`, `agent_memory`, `memory_tools`, `memory_conversations` | **`MemoryProvider`** trait | +| 51 | `context` | **`ContextComposer`** trait | +| 34 + 22 | `profiles`, `agent_registry` | **`DefinitionRegistry`** trait | +| 28 | `composio` | host `Tool` impls — no new trait | +| 25 + 9 + 6 + 4 + 2 | `security`, `approval`, `agent_tool_policy`, `sandbox`, `prompt_injection` | **`SecurityGate`** trait | +| 22 + 1 | `skills`, `skill_runtime` | host `Tool` impls | +| 21 | `todos` | already crate `graph::todos` (parent spec DS-1) | +| 19 + 7 + 4 | `tokenjuice`, `cost`, `scheduler_gate` | **`BudgetGate`** trait | +| 11 | `learning` | **`LearningSink`** trait | +| 7 | `subconscious` | host impl behind `LearningSink` | +| 5 + 4 | `web_chat`, `channels` | **`ProgressSink`** trait | +| 4 | `tool_status` | **`ToolOutcomeClassifier`** trait | +| 5 | `thread_goals` | host impl behind `ContextComposer` | +| 5 | `embeddings` | existing `EmbeddingModel` | +| 5 | `agent_orchestration` | existing `graph::orchestration` | +| 3 | `agent_experience` | **`ExperienceStore`** trait | +| 8 | `util` | inlined generics — no trait | +| remainder (`app_state` 3, `session_db` 2, `file_state` 2, `task_sources` 2, `mcp_registry` 2, `session_import` 2, `tinycortex` 2, `tool_timeout` 2, `threads` 1, `credentials` 1, `cwd_jail` 1, `memory_goals` 1, `memory_sync` 1) | ≤ 3 refs each | host impls or inlined generics | + +**~10 new traits** cover 48 domains, because most domains reach `agent/` through +one of a few conceptual seams. + +### 2.2 Inbound — what imports `agent/` (50 domains) + +This is the half the earlier analysis under-weighted, and it is the larger risk. +50 domains under `src/openhuman/` name `agent::`; 47 of them do so from code +rather than a doc comment. By symbol: + +| Refs | Symbol | Note | +| ---: | --- | --- | +| 300 | `agent::harness::*` | the bulk; moves down | +| 58 | `agent::turn_origin` | product enum — **stays host** | +| 43 | `agent::messages` (`ChatMessage`) | durable DTO — **stays host** (WP-1) | +| 34 | `agent::triage` | product — **stays host** | +| 23 | `agent::progress` (`AgentProgress`) | UI contract — **stays host**, produced via `ProgressSink` | +| 16 | `agent::prompts` | `SOUL.md`/`IDENTITY.md` — **stays host** | +| 15 | `agent::host_runtime` | **stays host** by definition | +| 25 | `agent::bus` | event-bus glue — **stays host** | +| 12 | `agent::task_board` | already crate `graph::todos` | +| 13 | `agent::message_convert` | boundary adapter — **stays host** | +| 12 | `agent::hooks` | trait defs move; impls stay | +| 9 | `agent::error`, 10 `agent::cost`, 8 `agent::progress_tracing`, 7 `agent::tool_policy`, 7 `agent::pformat`, 7 `agent::task_dispatcher`, 4 `agent::stop_hooks` | mixed; see §3 | + +**Consequence:** `agent/` does not empty out. **22,121 LOC stays** (measured +2026-07-28 over the §3 "stays in OpenHuman" file list — inside the originally +estimated 20–25k band) as +the host adapter layer (`ChatMessage`, `AgentProgress`, `turn_origin`, prompts, +triage, bus, host_runtime, message_convert, the trait impls). The deliverable is +"the runtime moves down and OpenHuman keeps an adapter", not "the directory +disappears". + +### 2.3 Honest cost + +48 outbound domains to invert, 50 inbound consumers to repoint, ~29k LOC of +tests to migrate or re-home, a cross-repo change in two Cargo worlds, and an +on-disk/behavioural surface (transcript format, progress events, cost +accounting) that users depend on. **This is a multi-quarter program, not a +refactor.** §5 sequences it so every phase is independently valuable and the +program can be halted at any phase boundary without leaving the tree broken. + +--- + +## 3. Disposition + +### Moves into `tinyagents` (generic over `State`) + +| Host area | Prod LOC | Lands as | +| --- | ---: | --- | +| `harness/session/{runtime,types,builder}` — session lifecycle & assembly | ~3,700 | `harness::session` — `Session` + builder over capability traits | +| `harness/session/turn/*` — turn orchestration shell | ~4,476 | `harness::session::turn` — generic loop + `TurnPreparation` pipeline | +| `harness/subagent_runner/` | ~5,541 | merges into existing `harness::subagent` + `graph::orchestration` | +| `harness/session/transcript.rs` + `turn_checkpoint.rs` | ~2,100 | `harness::memory::JsonlChatHistory` (this is Option B of the transcript spec — **the move decision selects B**) | +| `harness/{parse,definition,definition_loader,tool_filter,required_output,graph,agent_graph,fork_context}.rs` | ~3,300 | `harness::{tool_calling, definition, graph}` — merges with #55/#57 | +| `harness/artifact_offload/`, `tool_result_artifacts/` | ~1,400 | `harness::artifacts` | +| `harness/run_queue/`, `harness/memory_context*.rs` | ~1,000 | `harness::runtime`, behind `MemoryProvider` | +| `task_dispatcher/`, `dispatcher.rs` (parse half), `pformat.rs`, `stop_hooks.rs`, `hooks.rs` (trait defs) | ~3,000 | `harness::{tool_calling, hooks}` | +| `progress_tracing/` | ~3,186 | deleted, not moved — crate observability already covers it (parent spec DS-5) | + +### Stays in OpenHuman as the adapter layer + +`messages.rs` (`ChatMessage`), `message_convert.rs`, `progress.rs` +(`AgentProgress`), `turn_origin.rs`, `prompts/`, `triage/`, `bus.rs`, +`host_runtime.rs`, `error.rs`, `cost.rs`, `tool_policy.rs`, `multimodal.rs`, +`agent/tools/`, `archivist/`, `schemas.rs`, plus **every impl of the ~10 new +traits**. Measured 22,121 LOC including tests (2026-07-28). + +--- + +## 4. The two decisions that gate everything + +### 4.1 Config (195 refs — the real blocker) + +`agent/` reads `Config`, `AgentConfig` (742-line schema), `MemoryConfig`, +`ContextConfig` directly. A generic runtime cannot import OpenHuman's config +schema. Options: + +- **A — Crate-owned config structs.** The crate defines `SessionConfig`, + `TurnConfig`, `ToolConfig`; OpenHuman maps its schema into them at build time. + Explicit, versionable, and mirrors how `MemoryConfig` is derived for TinyCortex + (`tinycortex/config.rs::memory_config_from`). **Recommended.** +- **B — `ConfigProvider` trait** with ~40 getters. Avoids a mapping layer but + turns every config read into a virtual call and makes the trait a dumping + ground. +- **C — Generic `State` carries config.** Least code, worst discoverability; + every crate-side read needs a bound. + +Pick A. It is the pattern the org already uses successfully one crate over. + +### 4.2 `ChatMessage` and the transcript format + +Moving the session runtime down forces the transcript decision to **Option B** +of the transcript spec: the `session_raw` JSONL format becomes crate-owned +public API, and `ChatMessage`'s durable fields must survive as crate `Message` + +a `raw` passthrough (the `ToolResult::raw` precedent). This is the change with +real user-visible risk — existing installs have live transcripts and resume must +keep working. Phase 2 exists solely to de-risk it. + +--- + +## 5. Phased plan + +Each phase is independently valuable and leaves the tree green. Stop-anywhere is +a hard requirement, not a nicety. + +**Execution status (2026-07-28): Phases 0 and 1 are COMPLETE. Phases 2–7 were +deliberately NOT executed.** The program is parked at the Phase 1/2 boundary, +which is a legitimate stopping point: nothing has moved, no host file changed, +and the crate carries only additive, inert seams. Phase 2 is the gate — it is +the only phase with on-disk transcript risk and requires a shadow-read soak +release before it can start, so it must be scheduled deliberately rather than +picked up as the "next" task. + +**Phase 0 — Ledger + trait catalogue (no code). ✅ COMPLETE (2026-07-28).** +Reopen the superseded rows (§ header). Write the ~10 trait signatures up as an +upstream design doc. Nothing moves until the trait catalogue is accepted +upstream — otherwise the first mover defines the seams by accident. +*Note:* the original wording said "an upstream RFC in `vendor/tinyagents/docs/`". +That violates the crate's documented convention — `vendor/tinyagents/docs/spec/README.md` +and `vendor/tinyagents/AGENTS.md` forbid standalone spec files dropped directly +in `docs/` and require a per-topic file under `docs/modules//` linked +from that module's `README.md`, capped at 500 lines. The catalogue therefore +landed as `vendor/tinyagents/docs/modules/harness/host.md`, not as an RFC file. +*Exit (met):* trait catalogue documented upstream in the crate's own format; +ledger rows reopened in `docs/tinyagents-drift-ledger.md`; the plan's ground-truth +numbers re-derived from the tree (see the Corrections note in the header). + +**Phase 1 — Land the traits upstream, empty. ✅ COMPLETE (2026-07-28).** +Add the traits + no-op/in-memory default impls to the crate. No host change. +*Exit (met):* the ten host-capability seams (`MemoryProvider`, `ContextComposer`, +`SecurityGate`, `BudgetGate`, `DefinitionRegistry`, `ExperienceStore`, +`LearningSink`, `ProgressSink`, `ToolOutcomeClassifier`, `ModelResolver`) plus +inert default impls are committed in `vendor/tinyagents` on branch +`agent-to-tinyagents`. **The submodule gitlink bump is a separate, later step and +is intentionally not part of the host commits for Phase 0/1** — the host tree is +unchanged apart from these docs. +*Design note carried into Phase 4:* the plan's §1 framing that `State` is "the +pattern already in use" is only two-thirds true. Of the 18 harness extension +traits, only 7 are generic over `State` (`ChatModel`, `Tool`, `Middleware`, +`ModelMiddleware`, `ToolMiddleware`, `ModelBaseCall`, `ToolBaseCall`); the 11 +*capability* traits closest to the new seams (`ChatHistory`, `Store`, +`AppendStore`, `Summarizer`, `EmbeddingModel`, `VectorStore`, `ResponseCache`, +`WorkspaceIsolation`, `HarnessEventJournal`, `HarnessStatusStore`, +`EventListener`) are plain non-generic object-safe traits. Every OpenHuman +construction and impl today is `AgentHarness<()>` / `impl Tool<()>` — `State` is +unexercised in the host. Whichever shape Phase 4 adopts, adopting a non-unit +`State` is a signature change across ~30 existing `<()>` impls and every +`invoke(&(), …)` call site, and that cost is not counted in §2.3. + +**Phase 2 — Transcript to crate `JsonlChatHistory` (transcript spec Option B).** +⛔ NOT STARTED — deliberately. Do this early and alone: it is the only phase with +on-disk risk. One release of shadow-read parity, mismatch logged never panicked, +legacy `DDMMYYYY/` and `read_transcript_legacy_md` paths covered. +*Exit:* resume works across upgrade on a real workspace; parity soak clean, **and +each of the three semantics named in the transcript spec's §3.1 gap table is +individually demonstrated over the crate-owned format: compaction-replacement +records, skippable interrupted partials, and the display-order (dual) read path.** +The transcript spec calls that gap table "the single most important constraint in +this document" — a generic `ChatHistory` impl that satisfies naive round-trip +parity but drops those three silently corrupts model context on any compacted +thread, so they are exit criteria in their own right, not sub-cases of "parity". + +**Phase 3 — Config mapping (§4.1 Option A).** ⛔ NOT STARTED. +Introduce crate config structs + a host `session_config_from(&Config)` mapper. +Repoint `agent/` internals to the crate structs *in place*, before moving. +*Exit:* zero `crate::openhuman::config::` references inside the code slated to +move. + +**Phase 4 — Implement the traits host-side, still in place.** ⛔ NOT STARTED. +`MemoryProvider`, `ContextComposer`, `SecurityGate`, `BudgetGate`, +`DefinitionRegistry`, `ExperienceStore`, `LearningSink`, `ProgressSink`, +`ToolOutcomeClassifier`, `ModelResolver`. `agent/` calls them instead of +reaching into domains directly. **This phase delivers most of the architectural +value with none of the relocation risk** — after it, `agent/`'s outbound +coupling is ~10 traits instead of 48 domains, and the program can legitimately +stop here. +*Exit:* `rg -o "crate::openhuman::" src/openhuman/agent/harness/session/` down +from its 2026-07-28 baseline of **547** occurrences to the adapter layer only. +(For scale: the whole of `harness/` is 932 and the whole of `agent/` is 1,287 — +an earlier draft of this plan cited ~2,000 for `session/`, which no scoping of +that grep reproduces.) + +**Phase 5 — Relocate, module family at a time.** ⛔ NOT STARTED. +Order by inbound coupling, lowest first: `artifact_offload` → `run_queue` → +`parse`/`tool_calling` (merges with DS-5b) → `subagent_runner` → `session/turn` +→ `session/{builder,runtime,types}`. Each family: move to +`vendor/tinyagents/src/harness/`, re-export from the host adapter for one +release, then repoint consumers. +*Exit per family:* crate tests green; host `cargo check` both worlds; the +family's tests live upstream. + +**Phase 6 — Collapse the seam and the adapter.** ⛔ NOT STARTED. +`src/openhuman/tinyagents/` dissolves into the host adapter layer. Delete the +compatibility re-exports. +*Exit:* `agent/` is the adapter layer only; parent spec's DS-0 re-export gate +allowlist is seam-free. + +**Phase 7 — Exit gate.** ⛔ NOT STARTED. +Full `scripts/test-rust-with-mock.sh`, `cargo test --all-features` in both +vendored crates, slim disabled build **and** `cargo test --lib +--no-default-features --features tokenjuice-treesitter core::`, `pnpm +rust:check`, deletion-ledger totals reconciled, architecture docs rewritten. + +--- + +## 6. Risks + +- **Inbound coupling is the real cost, not outbound.** 50 domains import + `agent::`. Phase 5's per-family re-export window is what keeps that tractable; + skipping it turns every family move into a 50-domain atomic commit. +- **On-disk transcript risk (Phase 2)** is the only user-visible data risk in + the program. It is deliberately isolated and sequenced first. +- **`AgentProgress` is a UI contract.** It stays host-side and is produced + through `ProgressSink`. If it drifts into the crate, the frontend timeline, + cost footer, and citation chips break in ways unit tests will not catch. +- **Trait-explosion.** Ten traits is the budget. If Phase 4 needs a fifteenth, + that is a signal a seam is wrong — re-open the RFC rather than adding it. +- **GPL/crates.io.** Publishable: traits, generic loop, tool-calling wire + formats. Not publishable: `provider_role_for`'s `subconscious` routing, the + `integrations_agent` override, OpenHuman prompt text, backend phrasing, key + material. Every relocated file needs this check. +- **≥ 80% diff-coverage gate** on a program of this size — Phases 4 and 5 touch + hundreds of files. Check `diff-cover` per slice. +- **Two Cargo worlds** — every crate bump regenerates root and + `app/src-tauri` lockfiles (#3877). +- **`RUST_MIN_STACK=16777216`** — the subagent runner's large futures already + overflow the default stack on Apple Silicon; Phase 5's subagent move is + exactly where that resurfaces. +- **`GGML_NATIVE=OFF`** for local root-crate builds. + +--- + +## 7. Summary + +| | | +| --- | --- | +| Decision | move `agent/` into `tinyagents` (maintainer call, 2026-07-28) | +| Enabler | the crate is already generic over `State`; 18 extension traits use the pattern today | +| Inversion | 48 outbound domains → **~10 capability traits** | +| Reality check | `agent/` does not empty — 22,121 LOC stays as the host adapter (`ChatMessage`, `AgentProgress`, prompts, triage, bus, trait impls) | +| Gating decisions | config mapping (§4.1 → Option A); transcript format goes crate-owned (§4.2 → transcript spec Option B) | +| Highest-value / lowest-risk phase | **Phase 4** — trait injection in place. Cuts coupling 45 → 10 without moving a file; a legitimate stopping point | +| Highest-risk phase | **Phase 2** — on-disk transcript format, isolated and sequenced first | +| Honest cost | multi-quarter program; every phase leaves the tree green and shippable | + diff --git a/docs/specs/2026-07-28-agent-session-transcript-to-tinyagents-design.md b/docs/specs/2026-07-28-agent-session-transcript-to-tinyagents-design.md new file mode 100644 index 0000000000..db53121ad6 --- /dev/null +++ b/docs/specs/2026-07-28-agent-session-transcript-to-tinyagents-design.md @@ -0,0 +1,496 @@ +# Agent session persistence → TinyAgents + +**Status:** spec + plan, awaiting a shape decision (§4). Written 2026-07-28 +against `main` (`ce6c3e9b5`). +**Scope:** `src/openhuman/agent/harness/session/` — specifically the durable +transcript layer. Everything else in `session/` is out of scope and stays. +**Superseded in part (2026-07-28):** the maintainer has since decided to move +`agent/` wholesale into TinyAgents — see +[`2026-07-28-agent-runtime-into-tinyagents-design.md`](2026-07-28-agent-runtime-into-tinyagents-design.md). +That decision reopens this document's §6 "permanent host" rows for `builder/`, +`turn/`, `runtime.rs`, and `types.rs`, and selects **Option B** in §4 (the +transcript format becomes crate-owned). The state map, crate gap table, and +§3.5 analysis below remain accurate and are the input to that program's +Phases 2–4. + +**Parent spec:** `2026-07-28-deshim-agent-inference-memory-seams-design.md` +(this document is the expansion of its DS-8). +**Related:** `docs/tinyagents-migration-plan-2026-07-22.md` (WP-1's +`ChatMessage` decision is the governing precedent), `99-deletion-ledger.md`. + +--- + +## 1. The question this answers + +Should `agent/harness/session/` become a `tinyagents::sessions` module? + +**No — not as a unit.** But its durable transcript layer is a parallel +implementation of an abstraction the crate already ships, and that part should +converge. This document draws the line precisely, because drawing it wrong in +either direction is expensive: too aggressive and a GPL crate ends up importing +Composio; too timid and OpenHuman keeps two conversation-history models forever. + +--- + +## 2. Current state + +### 2.1 Size and split + +`session/` is **18,142 LOC** across 17 files (~11,073 production, ~7,069 tests). +The transcript layer is: + +| File | Prod LOC | Tests | +| --- | ---: | ---: | +| `transcript.rs` | 1,997 | 1,384 (`transcript_tests.rs`) | +| `turn_checkpoint.rs` | 105 | — | +| `migration.rs` | 373 | 170 (`migration_tests.rs`) | +| **Total** | **2,475** | **1,554** | + +### 2.2 Why only these three + +Measured host-domain fan-out (`grep -o "crate::openhuman::[a-z_]*" | sort -u`): + +| File | Prod LOC | Host domains | In scope? | +| --- | ---: | ---: | --- | +| `transcript.rs` | 1,997 | **2** (`agent::messages::ChatMessage`, `inference::provider::ToolCall`) | **yes** | +| `turn_checkpoint.rs` | 105 | **2** (`ChatMessage`, `hooks::ToolCallRecord`) | **yes** | +| `migration.rs` | 373 | **0** (`anyhow`, `std::fs`, `std::path`) | **yes** | +| `builder/factory.rs` | 1,699 | 21 — composio, security, skills, subconscious, profiles, memory_store, memory_tools, agent_registry, agent_experience, agent_memory, embeddings, tokenjuice, learning, app_state, config, context, inference, tools, agent, tinyagents, … | no | +| `turn/core.rs` | 2,207 | 10 — mcp_registry, thread_goals, agent_orchestration, agent_experience, agent_memory, composio, memory, util, agent, tinyagents | no | +| `turn/session_io.rs` | 854 | 7 — learning, session_import, config, context, inference, agent, tinyagents | no | +| `runtime.rs` | 817 | 14 — channels, composio, prompt_injection, agent_tool_policy, skills, memory, … | no | +| `builder/setters.rs` | 666 | 10 | no | +| `turn/tools.rs` | 650 | 6 — composio, profiles, skills | no | +| `types.rs` | 477 | 11 | no | +| `turn/context.rs` | 343 | 7 — app_state, learning, memory | no | +| `turn/mod.rs`, `turn/graph.rs`, `builder/*`, `mod.rs`, `tool_progress.rs` | ~840 | 3–7 each | no (see §6) | + +**~8,600 of 11,073 production lines are host wiring.** `builder/factory.rs` +reaches 21 OpenHuman domains; its job is literally "assemble OpenHuman's product +surface into a harness". Moving it down inverts the dependency and violates the +port plan's standing GPL/crates.io rule: only genuinely generic code goes into a +publicly redistributed crate. + +### 2.3 What `transcript.rs` actually implements + +Storage layout: + +```text +{workspace}/session_raw/{stem}.jsonl ← source of truth (flat dir) +{workspace}/sessions/YYYY_MM_DD/{stem}.md ← human view, never read back +``` + +`stem` = `{unix_ts}_{agent_id}`, or `{parent_chain}__{unix_ts}_{agent_id}` for a +sub-agent — timestamp-first so a plain directory listing sorts by creation time +and `find_latest_transcript` is one directory scan. + +Semantics, in the order they matter: + +1. **Append-only event log.** `append_transcript_turn` never rewrites existing + lines. It classifies the new logical message set against what was persisted: + pure extension → append the tail; reduction/rewrite → append a single + `{"kind":"compaction","replacement":[…]}` record carrying the full reduced + set, leaving earlier turns on disk. +2. **Two read paths from one log.** `read_transcript` replays for *model + context* (compaction records replace the accumulator; `interrupted:true` + partials are skipped). `read_transcript_display` returns **every** record in + file order so the UI can render pre-compaction history, compaction markers, + and interrupted partials. +3. **Rewrite-free cumulative meta.** A fresh `{"_meta":{…}}` line is appended + each turn; readers take the **last** `_meta` as authoritative, with line 1 as + a valid fallback for older cores. +4. **Forward/backward compatibility by construction.** `MessageLine` carries + `#[serde(flatten)] _extra`; `MetaPayload` does not set + `deny_unknown_fields`; `_meta.version` is `TRANSCRIPT_SCHEMA_VERSION`. Old + cores skip unknown-kind lines instead of crashing. Legacy + `session_raw/DDMMYYYY/` layout still resolves for resume. +5. **Usage rollups.** `read_thread_usage_summary` → + `ThreadUsageSummary` + `SubagentArchetypeUsage`. + +Public surface: 26 items — `SessionTranscript`, `TranscriptMeta`, +`DisplaySessionTranscript`, `DisplayRecord`, `DisplayMessage`, +`CompactionMarker`, `TurnUsage`, `MessageUsage`, `ThreadUsageSummary`, +`SubagentArchetypeUsage`, plus `write_transcript`, `append_transcript_turn`, +`append_interrupted_partial`, `read_transcript`, `read_transcript_display`, +`read_transcript_legacy_md`, `read_thread_usage_summary`, +`find_root_transcript_for_thread(_in_dir)`, `resolve_keyed_transcript_path(_in_dir)`, +`resolve_new_transcript_path`, `find_latest_transcript(_in_subdir)`. + +### 2.4 Consumers + +**24 files across 5 domains** import `transcript::`: `agent`, `threads`, +`session_import`, `learning`, `migrations`. Notable non-agent callers: + +- `threads/transcript_view/project.rs` — the derived-view projection +- `threads/turn_state/mirror.rs` — `find_root_transcript_for_thread`, `append_interrupted_partial` +- `threads/ops.rs` — `read_thread_usage_summary` +- `session_import/{ops,convert,live}.rs` — external session import +- `learning/transcript_ingest/mod.rs` — lesson extraction +- `migrations/phase_out_profile_md.rs` + +This is the **transcript-derived-view architecture**: `session_raw` JSONL is the +source of truth, `turn_state` is a derived cache. Any change here is a change to +that contract, not just to one module. + +--- + +## 3. What the crate already ships + +`vendor/tinyagents` v2.1.0: + +| Crate capability | Module | Relevance | +| --- | --- | --- | +| `ChatHistory` trait — `messages(thread_id) -> Vec`, `append`, `replace`, `clear` | `harness::memory::types:48` | **The direct analogue.** Thread-scoped conversation history surviving across runs. | +| `InMemoryChatHistory`, `StoreChatHistory`, `ShortTermMemory` (trim policy), `MemoryScope` | `harness::memory` | Backends + short/long-term layering | +| `Store` — kv (`get`/`put`/`delete`/`list`) **plus an append-only stream API**: `append(stream, value) -> u64`, `read_from(stream, offset)`, `len(stream)` | `harness::store` | The stream half is a closer structural match to the JSONL log than `ChatHistory` is | +| `InMemoryStore`, `FileStore`, `StoreRegistry` | `harness::store` | Backends | +| `Checkpointer`, `FileCheckpointer`, `SqliteCheckpointer`, `DurabilityMode` | `graph::checkpoint` | Superstep state snapshots — **different concern**, not a transcript | + +**So OpenHuman is not missing a home; it has a second implementation.** That is +the whole argument for this work. + +### 3.1 Gap table — what crate `ChatHistory` cannot express today + +| OpenHuman semantic | Crate equivalent | Gap | +| --- | --- | --- | +| Append-only log, never rewrite | `Store::append`/`read_from` (stream API) | present on `Store`, **absent from `ChatHistory`** (`replace` is a bulk overwrite) | +| Compaction record with `replacement` set | — | **missing** | +| `interrupted:true` partials, skipped on model read | — | **missing** | +| Dual read paths (model-context replay vs display-order) | — | **missing** — `messages()` is single-view | +| Cumulative `_meta` totals appended per turn | — | **missing** | +| Schema versioning + unknown-line tolerance | — | **missing** (a policy, easily added) | +| `.md` companion render | — | **host** — product surface, never upstream | +| Stem naming, latest/resume discovery, legacy dir fallback | — | **host** — OpenHuman workspace layout | +| Thread/subagent usage rollups | `harness::usage`, `harness::cost` | partial; the archetype rollup is product | + +`ChatHistory` is a **strictly weaker interface** than what OpenHuman needs. A +naive "just implement `ChatHistory`" that routes reads through `messages()` +would silently drop compaction and interrupted-partial semantics — i.e. corrupt +the model context on any compacted thread. This is the single most important +constraint in this document. + +--- + +## 3.5 Re-examined: `builder/factory.rs` and `turn/core.rs` + +The §2.2 verdict ("21 imports, therefore host") describes the *wiring*, not the +*shape*. A shape can be generic even when every value flowing through it is +product-specific, so both files were re-read rather than dismissed on the import +count. Result: **the structure is already in the crate; the residue is genuinely +product — with two concrete exceptions worth acting on.** + +### 3.5.1 `Agent` is not a duplicate of `AgentHarness` + +`AgentHarness` (`harness/runtime/types.rs:259`) has **six** fields — +`models`, `tools`, `middleware`, `policy`, `tool_timeouts`, `response_cache` — +and a complete builder API: `new`, `register_model`, `set_default_model`, +`register_tool`, `push_middleware`, `push_model_middleware`, +`push_tool_middleware`, `with_policy`, `with_tool_timeout_settings`, +`with_response_cache`. The seam already drives exactly this API +(`tinyagents/mod.rs:1793–2302`). + +The host `Agent` (`session/types.rs:31`) has **40+ fields**, and the ones that +define it are not execution config at all: + +- **Per-turn product accumulators:** `last_memory_context`, + `last_turn_citations`, `last_turn_usage_totals`, `last_turn_hit_cap` — read + by web-channel delivery to render citation chips, token/cost meters, and to + distinguish "paused at the iteration cap" from "asked a question". +- **Host service handles:** `memory`, `shared_experience_memory`, + `memory_loader`, `tool_policy_session`, `workflows`. +- **Product policy:** `learning_enabled`, `explicit_preferences_enabled`, + `subagent_tool_ceiling_names`, `visible_tool_names`. +- **Event identity:** `event_session_id`, `event_channel`. + +`Agent` is OpenHuman's **session-state + turn-result object**; `AgentHarness` is +the crate's execution configuration. They are different things that both happen +to hold tools and a model. That single overlap is already bridged by +`SharedToolAdapter`, which the WP-4 decision fixed as a permanent boundary. And +`Agent` already holds a crate type directly — `workspace_descriptor: +tinyagents::harness::workspace::WorkspaceDescriptor` — which is what convergence +looks like in practice: adopt crate types field by field, don't relocate the +struct. + +**So there are not two builders competing.** There are two builders in series +(host `Agent::from_config` → seam → crate `AgentHarness`), building two +different objects. Moving `factory.rs` down would require the crate to import +Composio, OpenHuman `SecurityPolicy`, `memory_store`, `skills`, `profiles`, and +`subconscious` — the exact GPL/crates.io boundary violation the port plan +forbids. + +### 3.5.2 `turn/core.rs` is preparation, and the engine already left + +WP-3 established that `turn/core.rs` "performs OpenHuman turn preparation and +calls the TinyAgents session path" — it contains no turn engine. Reading it +confirms that: `impl Agent` starts at line 432, and everything above it is +product gating. What `turn()` actually does before delegating: + +- super-context gating hard-wired to `orchestrator` + `context_scout`, with an + explicit carve-out so background/cron/specialist turns don't spawn it; +- agent-experience retrieval and `prepend_experience_block`; +- memory recall citation collection; +- integration / MCP / skill announcement + retraction notes. + +None of that is framework-shaped. **But ~150 LOC of it is:** +`tool_records_from_conversation`, `stamp_tool_failures`, `parse_tool_call_id`, +`short_failure_detail`, `replace_last_assistant_reply` are pure message-list +manipulation over roles and tool-call ids. Those are candidates for crate +`harness::message` helpers — small, but genuinely generic. + +### 3.5.3 The one real duplicate: dispatcher-kind resolution + +`resolve_dispatcher_kind` (`factory.rs:1398`) picks Native / Xml / PFormat from +`supports_native`, plus an `integrations_agent` override. The three +`ToolDispatcher` impls then render and parse tool calls accordingly. + +**The crate already makes this decision.** `OpenAiModel` exposes +`with_native_tool_calling(bool)` and internally computes +`prompt_guided_tools = !self.profile.tool_calling && !request.tools.is_empty()` +(`providers/openai/transport.rs:951`), replaying calls and results as text when +prompt-guided — that is #55. So OpenHuman decides native-vs-prompt-guided at the +session-build layer, and the crate decides it again at the model layer. + +This is the **same subsystem** as the parent spec's DS-5b (`harness/parse.rs` +duplicating #55/#57 parsing). Dispatcher selection, tool-call rendering, and +tool-call parsing are one concern split across two layers and two owners. They +should be resolved together, and the answer is almost certainly: the crate owns +the native-vs-prompt-guided decision and both directions of the wire format; +OpenHuman keeps only the `integrations_agent` override as an explicit policy +input, and the durable `to_provider_messages` serialization. + +### 3.5.4 The generalizable conclusion + +The productive question is not *"which file moves down"* but *"which extension +point is missing upstream"*. `factory.rs` and `turn/core.rs` are large because +the crate offers registries and middleware but no typed **turn-preparation** +seam — so every product enrichment (experience, citations, announcements, +super-context) is hand-written into one `turn()` body instead of registered. + +If the crate grew a `ContextEnricher` / `TurnPreparation` pipeline — +ordered, fallible, each returning prompt fragments plus metadata — OpenHuman's +enrichment becomes registrations rather than a bespoke method, and the *shape* +moves down while the *wiring* stays up. That is worth designing (S6), and it is +what "enrich it as a library" should mean here. It is explicitly **not** a +licence to relocate 3,900 lines of host wiring. + +--- + +## 4. The decision (§4 is the gate — pick one before writing code) + +### Option A — Host backend behind the crate trait *(recommended)* + +Keep `ChatMessage`, the `session_raw` format, and every host-only helper exactly +as they are. Add `impl tinyagents::harness::memory::ChatHistory for +SessionTranscriptHistory`, converting at the boundary via the existing +`agent/message_convert.rs`. The harness talks to the crate trait; OpenHuman owns +the format. Host-only semantics (display read, compaction markers, usage +rollups, path resolution) stay on the concrete type, reached directly by the 24 +consumers that need them. + +- **On-disk change:** none. **Migration risk:** none. +- **Removes:** ~400 LOC of parallel abstraction, plus the conceptual duplicate. +- **Cost:** low. Reversible. +- **Weakness:** the crate trait is only used on the narrow runtime path; most of + `transcript.rs` stays. Honest framing: this fixes *"two abstractions"*, not + *"two implementations"*. + +### Option B — Upstream a generic append-only history backend + +Add `JsonlChatHistory` to the crate beside `StoreChatHistory`, and extend +`ChatHistory` (or add a `ReplayableChatHistory` supertrait) with the three +missing generic semantics: compaction-replacement records, skippable partials, +and a display-order read. `ChatMessage`'s durable fields survive as crate +`Message` + a `raw` passthrough, mirroring how the tool-model decision preserves +`ToolResult::raw`. + +- **Removes:** ~2,100 host LOC. +- **Cost:** high — a trait extension in a published GPL crate, a durable format + becoming public API, and an on-disk parity soak. +- **Only justified if** a second host will use it. Compaction-aware append-only + transcripts are genuinely generic agent-framework machinery, so this is + defensible — but it is a crate-roadmap decision, not a cleanup. + +### Option C — Declare host-owned, close the question + +Record `transcript.rs` in the deletion ledger as HOST-OWNED (durable on-disk +format + product read paths), same disposition as `tool_status` and +`namespace_store`. Costs nothing, keeps two abstractions. + +**Recommendation: A now, B only if the crate roadmap wants a durable transcript +primitive.** A is cheap, reversible, and removes the thing that actually +confuses readers. B should not be smuggled in as refactoring. + +--- + +## 5. Plan (Option A) + +Every slice: failing-before/passing-after test, small validated commit on a +feature branch, `atomic-commit` with explicit paths. + +### S0 — Ledger + decision record (no code) + +Add a ledger row for `harness/session/` recording the §2.2 split and the §4 +choice. Without this, a future audit re-opens "why didn't session move?" — which +has already happened twice in this migration. + +**Exit:** row present; this doc linked from the parent spec's DS-8. + +### S1 — `migration.rs` disposition + +Zero host imports, but it migrates *OpenHuman's* directory layout +(`session_raw/DDMMYYYY/` → flat). Generic code for a host-specific format. + +Expected outcome: **stays host**, recorded with that reason. Do the 10-minute +check rather than assuming; if it turns out to be a general "flatten a +date-bucketed log dir" utility with no OpenHuman naming, it can go down. + +**Exit:** one ledger row, either way. + +### S2 — Extract the trait-shaped surface + +Introduce `SessionTranscriptHistory { workspace_dir, stem }` in +`session/transcript_history.rs`, wrapping the existing free functions. No +behaviour change; purely a handle where a trait impl can live. + +**Exit:** `cargo check` + existing `transcript_tests.rs` green, untouched. + +### S3 — Implement crate `ChatHistory` + +```rust +impl tinyagents::harness::memory::ChatHistory for SessionTranscriptHistory { + async fn messages(&self, thread_id: &str) -> Result>; // read_transcript (model-context replay) → message_convert + async fn append(&self, thread_id: &str, message: Message) -> Result<()>; + async fn replace(&self, thread_id: &str, messages: Vec) -> Result<()>; + async fn clear(&self, thread_id: &str) -> Result<()>; +} +``` + +Hard requirements, each with its own test: + +- `messages()` MUST route through the **model-context** replay path, so + compaction records replace the accumulator and `interrupted` partials are + skipped. A test must construct a compacted transcript and assert + `messages()` == `read_transcript()`, not the raw line set. +- `replace()` MUST map onto the compaction-record path + (`append_transcript_turn`'s reduction branch), **not** a file rewrite. The + append-only invariant is the format's core property; a trait default that + clears-then-appends would destroy history. +- `clear()` semantics must be decided explicitly — truncate vs. start a new + stem. Whichever, write it in the doc comment. + +**Exit:** compaction and interrupted-partial round-trip tests green; the +byte-identity assertion against the pre-change reader passes. + +### S4 — Route the harness through the trait + +The turn path takes `Arc` instead of calling transcript free +functions. The 24 consumers that need display records, usage rollups, or path +resolution keep using the concrete type — that is correct, not debt. + +**Exit:** `agent_harness_e2e` + `scripts/test-rust-with-mock.sh` green; +`threads/transcript_view` projection output unchanged (golden test). + +### S5 — Shadow soak, then remove the parallel path + +One release with both paths live and a read-side comparison logged on mismatch +(never panic — a mismatch on a user's real transcript must degrade, not crash). +Then delete the redundant abstraction and update the ledger. + +**Exit:** ledger row terminal; `docs/` transcript-derived-view note restated in +terms of the crate trait. + +--- + +### S6 — Follow-ups from the §3.5 re-examination + +Independent of S0–S5; each is separately shippable and none requires the +transcript decision. + +1. **Unify dispatcher selection with the model layer** (§3.5.3). Merge with the + parent spec's DS-5b — dispatcher choice, tool-call rendering, and tool-call + parsing are one concern. Deliverable: the crate owns native-vs-prompt-guided; + OpenHuman passes the `integrations_agent` override as policy and keeps + `to_provider_messages` for the durable envelope. Est. host LOC removed when + combined with DS-5b: **~2,400**. +2. **Upstream the message-list helpers** (§3.5.2): `tool_records_from_conversation`, + `stamp_tool_failures`, `parse_tool_call_id`, `short_failure_detail`, + `replace_last_assistant_reply` → crate `harness::message`. ~150 LOC. Small, + uncontroversial, do it alongside DS-5b's parity-test port. +3. **Design a crate turn-preparation seam** (§3.5.4). A `ContextEnricher` / + `TurnPreparation` pipeline — ordered, fallible, returning prompt fragments + + metadata — so product enrichment registers instead of being hand-written into + `turn()`. This is a **crate roadmap proposal, not a refactor**: write the + design, get it accepted upstream, then migrate OpenHuman's four enrichers + (super-context, agent-experience, recall citations, announcements) onto it. + Do not start by moving code. +4. **Continue adopting crate types field-by-field on `Agent`**, following the + `workspace_descriptor: tinyagents::harness::workspace::WorkspaceDescriptor` + precedent. This is how `Agent` converges without ever relocating — record + each adopted field in the ledger. + +**Exit:** items 1–2 landed; item 3 is an accepted-or-rejected upstream design +doc, not an open question; item 4 has a standing ledger section. + +## 6. Explicitly out of scope + +Recorded so a later audit does not re-litigate: + +- `builder/` (1,699 + 666 + 96 + 55) — 21-domain host wiring, and it builds a + *different object* than the crate's `AgentHarness` (§3.5.1). **Permanent + host**, minus the dispatcher-selection carve-out in S6.1. +- `turn/core.rs`, `turn/tools.rs`, `turn/context.rs`, `turn/session_io.rs`, + `turn/mod.rs`, `turn/graph.rs` — product turn preparation; the engine already + left in WP-3 (§3.5.2). **Permanent host**, minus the ~150 LOC of message-list + helpers in S6.2 and whatever S6.3's preparation seam later absorbs. +- `runtime.rs`, `types.rs` — `AgentSession` is a bag of host handles. **Permanent host.** +- `tool_progress.rs` (256) — already the C4 Step-5 deletion target; belongs to + the progress-tracing workstream (parent spec DS-5), not here. +- `ChatMessage` itself — WP-1 settled this: it is the versioned on-disk record, + and replacing it with crate `Message` changes existing users' data. Under + Option A it does not move. Only Option B reopens it. + +--- + +## 7. Risks + +- **Silent model-context corruption is the top risk.** If `messages()` is wired + to the display read path (or to a naive line replay), every compacted thread + feeds the model duplicated pre-compaction history. It will not throw; it will + degrade answers and inflate token cost. S3's compaction test is the gate. +- **`replace()`'s trait default is dangerous here.** The crate's default clears + then re-appends, which the crate's own docs flag as non-atomic. Against an + append-only durable log it is worse than non-atomic — it is destructive. + Override it; never inherit it. +- **On-disk compatibility.** Existing installs have live `session_raw` files, + including legacy `DDMMYYYY/` dirs. Resume must keep working across the change; + the `read_transcript_legacy_md` path and the flat/dated fallback both need + coverage in the soak. +- **Blast radius beyond `agent/`.** 24 files across `threads`, `session_import`, + `learning`, `migrations`. The `threads/turn_state` derived-view contract is + the fragile one. +- **≥ 80% diff-coverage merge gate.** S2/S4 touch many call sites; check + `diff-cover` locally before pushing rather than discovering it in CI. +- **Two Cargo worlds** — any vendored bump (only under Option B) regenerates + root **and** `app/src-tauri` lockfiles. +- **`GGML_NATIVE=OFF`** for local root-crate `cargo` runs on Apple Silicon. +- **GPL/crates.io boundary** — under Option B, the `session_raw` format becomes + public API of a redistributed crate. Nothing product-specific (agent ids, + OpenHuman path conventions, `.md` rendering) may cross. + +--- + +## 8. Summary + +| | | +| --- | --- | +| Proposed | move `harness/session/` → `tinyagents::sessions` | +| Verdict | **rejected as a unit** — ~8,600 of 11,073 prod LOC is host wiring; `builder/factory.rs` alone imports 21 OpenHuman domains | +| In scope | `transcript.rs` (1,997), `turn_checkpoint.rs` (105), `migration.rs` (373) — ≤ 2 host imports each | +| Key finding | the crate already ships `harness::memory::ChatHistory` + `harness::store` stream API; OpenHuman has a **second implementation**, not a missing home | +| Key constraint | crate `ChatHistory` cannot express compaction records, interrupted partials, or dual read paths — a naive impl corrupts model context | +| Recommendation | **Option A** — host backend behind the crate trait; ~400 LOC, zero on-disk change, reversible | +| Escalation | **Option B** (upstream `JsonlChatHistory`, ~2,100 LOC) only as a deliberate crate-roadmap decision | +| `builder/factory.rs` re-check (§3.5.1) | stays — builds `Agent` (40+ fields of product session state), not `AgentHarness` (6 fields of execution config); one real carve-out: dispatcher selection duplicates crate `with_native_tool_calling` | +| `turn/core.rs` re-check (§3.5.2) | stays — the engine left in WP-3; residue is product enrichment. ~150 LOC of message-list helpers are upstreamable | +| The generalizable ask | the missing artifact is a crate **turn-preparation seam** (S6.3), not a relocated file — move the shape down, keep the wiring up | + diff --git a/docs/tinyagents-drift-ledger.md b/docs/tinyagents-drift-ledger.md index 4c8023689f..4902acafd0 100644 --- a/docs/tinyagents-drift-ledger.md +++ b/docs/tinyagents-drift-ledger.md @@ -261,6 +261,23 @@ error emitted before the cap. | Phase 5 - workflow/team generic slices | Validation/scheduling slice evaluation | **NOT STARTED** | | Phase 6 - cleanup and docs | Transitional shim deletion and architecture docs | **NOT STARTED** | +## Agent-Relocation Reopen Rows (2026-07-28) + +Opened by the maintainer decision of 2026-07-28 to move `src/openhuman/agent/` +into `vendor/tinyagents` as a generic agent runtime, with OpenHuman coupling +expressed as trait injection. Governing plan: +[`specs/2026-07-28-agent-runtime-into-tinyagents-design.md`](specs/2026-07-28-agent-runtime-into-tinyagents-design.md) +(formerly filed as `plan-agents.md`; renamed to the directory convention so both +it and the transcript spec cross-link cleanly). These rows reopen dispositions +that earlier documents had closed as permanently host-owned. + +| # | Area | Status | Evidence / action | +| --- | --- | --- | --- | +| AR-1 | `agent/harness/session/{builder/, turn/, runtime.rs, types.rs}` — recorded as **Permanent host** in `specs/2026-07-28-agent-session-transcript-to-tinyagents-design.md` §6 | **REOPENED — DRIFT -> tinyagents PR** | The 2026-07-28 relocation decision puts session lifecycle/assembly (~3,700 LOC) and the turn orchestration shell (~4,476 LOC) in scope as `harness::session` / `harness::session::turn` generic over host capability traits. §6 of the transcript spec keeps its analysis but no longer holds its verdict for these four areas. Blocked behind Phase 3 (config mapping) and Phase 4 (trait injection in place); relocation itself is Phase 5. | +| AR-2 | `agent/` remainder — recorded as **STAYS (product/host)** in `tinyagents-migration-plan-2026-07-22.md` §7 | **REOPENED — narrowed, not reversed** | `agent/` is 152 files / 70,530 LOC (measured 2026-07-28). The runtime half moves; a measured **22,121 LOC** stays as the host adapter layer (`ChatMessage`, `message_convert`, `AgentProgress`, `turn_origin`, `prompts/`, `triage/`, `bus.rs`, `host_runtime.rs`, `agent/tools/`, `archivist/`, plus every impl of the ~10 new capability traits). The §7 row is annotated in place. | +| AR-3 | Transcript format ownership — the transcript spec §4 recommended **Option A** (host-owned format) | **REOPENED — Option B selected** | Moving the session runtime down forces the `session_raw` JSONL format to become crate-owned public API (`harness::memory::JsonlChatHistory`). Recorded in the transcript spec's own header and in the plan §4.2. The §3.1 gap table (compaction-replacement records, skippable interrupted partials, dual/display-order read path, cumulative `_meta`) is a binding acceptance constraint on that implementation — it is plan Phase 2, the program's only on-disk/user-visible data risk, and is **not started**. | +| AR-4 | Phase 0 + Phase 1 of the relocation plan | **CLOSED (2026-07-28)** | Phase 0: host-capability trait catalogue documented upstream as `vendor/tinyagents/docs/modules/harness/host.md` (the plan's original "RFC in `docs/`" wording was corrected — the crate's `AGENTS.md` / `docs/spec/README.md` forbid standalone spec files there); plan counts re-derived from the tree and corrected in the plan header. Phase 1: the ten seams + inert default impls committed in `vendor/tinyagents` on branch `agent-to-tinyagents` (`e3f06d7`). **Phases 2–7 deliberately not executed**; the submodule gitlink bump is a separate later step. | + ## Closing Procedure 1. For a **DRIFT -> tinyagents PR** row, branch inside `vendor/tinyagents`, port diff --git a/docs/tinyagents-migration-plan-2026-07-22.md b/docs/tinyagents-migration-plan-2026-07-22.md index c98b2b6763..e4d3d2362c 100644 --- a/docs/tinyagents-migration-plan-2026-07-22.md +++ b/docs/tinyagents-migration-plan-2026-07-22.md @@ -483,7 +483,7 @@ changes remain gated on explicit approval of that proposal. | `tinyagents/middleware.rs` generic middlewares | UPSTREAM case-by-case (WP-5) | | `tinyagents/` remainder (seam) | STAYS, shrinks | | `agent/` legacy `run_turn_engine` + escape hatches | ALREADY DELETED; WP-3 corrected the stale audit and runner documentation | -| `agent/` remainder, `agent_registry/`, `agent_experience/`, `agent_memory/`, `agent_tool_policy/`, `agentbox/`, `orchestration/`, `tool_registry/` | STAYS (product/host) | +| `agent/` remainder, `agent_registry/`, `agent_experience/`, `agent_memory/`, `agent_tool_policy/`, `agentbox/`, `orchestration/`, `tool_registry/` | **REOPENED 2026-07-28** (was: STAYS (product/host)). The maintainer decided on 2026-07-28 to relocate `src/openhuman/agent/` into `vendor/tinyagents` as a generic runtime with OpenHuman coupling expressed as trait injection — see [`specs/2026-07-28-agent-runtime-into-tinyagents-design.md`](specs/2026-07-28-agent-runtime-into-tinyagents-design.md). ~22.1k LOC still stays host-side as the adapter layer, so this row is narrowed rather than reversed; the runtime half is now in scope. Tracked as rows AR-1..AR-3 in [`tinyagents-drift-ledger.md`](tinyagents-drift-ledger.md). | | `routing/` | DELETED; #4783 crate router already owned the only live path (WP-2) | | `tool_timeout/` | HOST-OWNED: config/env state + hard-deadline enforcement; crate timeout is metadata only (WP-2 closed) | | `tool_status/` classification | HOST-OWNED: OpenHuman UI/security/recovery taxonomy (WP-2 closed) | diff --git a/vendor/tinyagents b/vendor/tinyagents index 37815400eb..f210efae74 160000 --- a/vendor/tinyagents +++ b/vendor/tinyagents @@ -1 +1 @@ -Subproject commit 37815400eb035f74e20b8a1e4da3c8b2a0b49a1e +Subproject commit f210efae74784c918af4d79b76d4af2b529350bc