Skip to content

feat(agent): TinyAgents migration wave 2 — microcompact upstream, session shadow reads, budget dedupe, replay RPC - #4483

Merged
senamakel merged 8 commits into
tinyhumansai:mainfrom
senamakel:feat/tinyagents-wave2
Jul 4, 2026
Merged

feat(agent): TinyAgents migration wave 2 — microcompact upstream, session shadow reads, budget dedupe, replay RPC#4483
senamakel merged 8 commits into
tinyhumansai:mainfrom
senamakel:feat/tinyagents-wave2

Conversation

@senamakel

Copy link
Copy Markdown
Member

Summary

  • TinyAgents migration wave 2 — four adapter-first slices continuing the harness migration (follows the merged wave-1 PR feat(agent): tinyagents 1.5 migration wave — vendored SDK, dual-write sessions, goals/todos shadows, journals, middleware dedupe #4473). No user-visible behavior change; every slice keeps the legacy path authoritative and only adds crate-backed shadows / read-only surfaces.
  • W2-microcompact — upstreamed the in-house MicrocompactMiddleware into the vendored tinyagents crate (tinyhumansai/tinyagents@feat/microcompact-middleware) and swapped OpenHuman onto it; local copy + impl deleted, behavior byte-identical (placeholder-configured, events off).
  • W2-shadow-reads — store-backed session shadow reader beside the legacy transcript reader; logs [session_shadow_read] divergence; legacy stays authoritative (flag agent.session_shadow_reads, default OFF + env kill switch).
  • W2-budget-dedupe — single-owner UsageRecorded recording (dedupe guard) + observe-only crate BudgetMiddleware; local CostBudgetMiddleware demoted to a [budget_shadow] divergence logger, still authoritative for enforcement; flip criteria documented.
  • W2-replay-rpc — read-only openhuman.agent_run_events (paged), agent_run_status, agent_runs_active controllers over the C4 journal/status seams, wired via the controller registry.

Problem

Wave 1 landed the vendored SDK, session dual-writes, goals/todos shadows, journals, and middleware dedupe, but left several convergence items open: the local microcompact middleware had no crate equivalent (blocking its deletion), session reads were still 100% legacy with no parity signal, cost accounting could double-count once a crate budget middleware was added, and the restart-stable journal/status seams (C4) had no RPC surface for the desktop to replay/inspect runs.

Solution

Adapter-first throughout — legacy authoritative, crate features shadow and log divergence, deletions gated on proven parity:

  • Microcompact: implemented a generic MicrocompactMiddleware in the crate (caller-supplied placeholder, opt-in Compressed event, idempotent tool-body clearing) with the ported OpenHuman tests as the byte-for-byte parity contract. Pushed to tinyhumansai/tinyagents before bumping the submodule gitlink. OpenHuman constructs it with CLEARED_PLACEHOLDER and events off → identical behavior; the local struct/impl are deleted and the retained OpenHuman tests assert parity against the crate type.
  • Shadow reads: on the read path the session is also read back from {workspace}/tinyagents_store and compared to the legacy render, logging a compact (no-PII) divergence summary. Gated OFF by default with a pure env kill switch, mirroring the wave-1 dual-write pattern.
  • Budget dedupe: the per-run event bridge now records a model call's usage exactly once (keyed on the run-scoped iteration cursor), so an observe-only crate BudgetMiddleware (empty BudgetLimits, never enforces) can be installed without double-counting. The local gate is demoted to a shadow that logs [budget_shadow] divergence but still enforces. Three concrete flip criteria are documented at the registration site and in the deletion ledger.
  • Replay RPC: three read-only controllers project the crate AgentObservation/HarnessRunStatus serde shapes directly (guaranteed round-trip stable, never prompt/tool/payload text), with paging (next_offset), capped limits, and [rpc] logging. Registered via src/core/all.rs per the controller-migration checklist.

Submission Checklist

If a section does not apply to this change, mark the item as N/A with a one-line reason. Do not delete items.

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/pr-ci.yml. Focused Rust module tests cover every new/changed flow (microcompact parity, shadow round-trip + kill switch, usage dedupe, replay paging/status/active); the coverage-gate job verifies the merged number.
  • Coverage matrix updated — added/removed/renamed feature rows in docs/TEST-COVERAGE-MATRIX.md reflect this change — N/A: no new user-facing feature; internal harness shadows + read-only replay RPC over existing persisted data.
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: no release-cut surface changed (internal harness shadows + read-only RPC, all off/additive by default).
  • Linked issue closed via Closes #NNN in the ## Related section — N/A: continuation work tracked by the migration plan docs, no single issue.

Impact

  • Runtime/platform: Rust core only (desktop/CLI). No frontend, mobile, or web changes.
  • Behavior: none by default. Shadow reads OFF; crate budget middleware observe-only; local enforcement unchanged; microcompact byte-identical; replay controllers are read-only and additive.
  • Security: replay controllers are strictly read-only over already-persisted data, project no prompt/tool/payload text, and bypass no approval/security/sandbox/workspace boundary. Divergence logs carry counts/ids only, never PII.
  • Migration/compat: submodule gitlink advances to tinyhumansai/tinyagents@ac73382 (feature branch, pushed). Fresh clones/worktrees must git submodule update --init vendor/tinyagents.

Related

  • Closes: N/A (continuation of the TinyAgents full-migration plan; see docs/tinyagents-full-migration-plan/)
  • Follow-up PR(s)/TODOs: flip crate BudgetMiddleware to enforcing owner once the 3 documented criteria hold; flip session reads to authoritative once [session_shadow_read] logs are divergence-clean (unlocks the 04.2 phase-4 legacy-session deletions).

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: senamakel:feat/tinyagents-wave2
  • Commit SHA: 0a4023a1d (+ vendored tinyhumansai/tinyagents@ac73382)

Validation Run

  • pnpm --filter openhuman-app format:checkN/A: no app/ (frontend) files changed.
  • pnpm typecheckN/A: no TypeScript changed.
  • Focused tests: RUST_MIN_STACK=16777216 GGML_NATIVE=OFF cargo test --lib -- --test-threads=1 microcompact shadow_read run_events run_status runs_active duplicate_usage unobserved_turn (+ crate cargo test --lib microcompact in vendor/tinyagents).
  • Rust fmt/check (if changed): cargo fmt applied (core + crate); cargo check green.
  • Tauri fmt/check (if changed): N/A: no app/src-tauri files changed.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: none by default (all slices gated OFF / observe-only / byte-identical / read-only).
  • User-visible effect: none until shadows are flipped in a later PR.

Parity Contract

  • Legacy behavior preserved: microcompact byte-identical (parity tests); session legacy reader authoritative; local cost gate authoritative; replay is read-only.
  • Guard/fallback/dispatch parity checks: shadow-read flag OFF default + env kill switch; usage dedupe guard keyed on run-scoped iteration; budget shadow logs divergence without changing enforcement.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: this PR
  • Resolution (closed/superseded/updated): N/A

senamakel added 7 commits July 3, 2026 23:34
…iddleware

Deletes the in-house MicrocompactMiddleware (struct + impl) and constructs the
crate `tinyagents::harness::middleware::MicrocompactMiddleware`, upstreamed to
tinyagents (branch feat/microcompact-middleware, commit ac73382; gitlink bumped
here). Constructed with OpenHuman's CLEARED_PLACEHOLDER and events off, so
behavior is byte-identical to the deleted local version. The retained OpenHuman
tests assert that parity against the crate type.

Claude-Session: https://claude.ai/code/session_01Bgv25hEkC6nP3DYdat91PV
…gence (04.2 phase 2)

Adds a store-backed shadow reader beside the legacy transcript reader: on the
read path the session is also read back from the crate store and compared,
logging `[session_shadow_read]` divergence. Legacy stays authoritative. Gated by
config flag `agent.session_shadow_reads` (default OFF) with
`OPENHUMAN_SESSION_SHADOW_READS` as a pure kill switch.

Claude-Session: https://claude.ai/code/session_01Bgv25hEkC6nP3DYdat91PV
…dgetMiddleware observe-only

Establishes a single owner for UsageRecorded recording via a dedupe guard, installs
the crate BudgetMiddleware in observe-only mode, and demotes the local
CostBudgetMiddleware to a divergence-logging shadow (`[budget_shadow]`). Local
enforcement stays authoritative; flip criteria documented.

Claude-Session: https://claude.ai/code/session_01Bgv25hEkC6nP3DYdat91PV
…ournal seams

Adds read-only controllers openhuman.agent_run_events (paged via next_offset),
agent_run_status, and agent_runs_active over the C4 journal/status seams, wired via
the controller registry in src/core/all.rs.

Claude-Session: https://claude.ai/code/session_01Bgv25hEkC6nP3DYdat91PV
Ticks the microcompact-middleware upstream-extraction row (deleted locally,
now the crate type), records 04.2 phase-2 shadow-reads landing as the gate for
the phase-4 session deletions, and updates the handoff to reflect all four
wave-2 slices done on feat/tinyagents-wave2 with next-steps for wave 3.

Claude-Session: https://claude.ai/code/session_01Bgv25hEkC6nP3DYdat91PV
`HarnessEventJournal as _` was unused in the replay ops test module (rustc
`unused_imports`); the fan-out seam is exercised via `StoreEventJournal` /
`JournalSink` directly.

Claude-Session: https://claude.ai/code/session_01Bgv25hEkC6nP3DYdat91PV
rustfmt-only reflow of the wave-2 additions the slice agents left unformatted.

Claude-Session: https://claude.ai/code/session_01Bgv25hEkC6nP3DYdat91PV
@senamakel
senamakel requested a review from a team July 4, 2026 00:47
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 66260417-9e55-4f40-8ebc-faa4d84cdc2d

📥 Commits

Reviewing files that changed from the base of the PR and between e6bb4bf and f1045a3.

📒 Files selected for processing (15)
  • docs/tinyagents-full-migration-plan/99-deletion-ledger.md
  • docs/tinyagents-full-migration-plan/HANDOFF-2026-07-03.md
  • src/core/all.rs
  • src/openhuman/agent/harness/session/turn/session_io.rs
  • src/openhuman/config/schema/agent.rs
  • src/openhuman/session_import/live.rs
  • src/openhuman/session_import/live_tests.rs
  • src/openhuman/tinyagents/middleware.rs
  • src/openhuman/tinyagents/mod.rs
  • src/openhuman/tinyagents/observability.rs
  • src/openhuman/tinyagents/replay/mod.rs
  • src/openhuman/tinyagents/replay/ops.rs
  • src/openhuman/tinyagents/replay/schemas.rs
  • src/openhuman/tinyagents/tests.rs
  • vendor/tinyagents

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a4023a1d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/all.rs
Comment on lines +136 to +137
controllers
.extend(crate::openhuman::tinyagents::replay::all_agent_replay_registered_controllers());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep replay controllers out of agent-facing registry

When these desktop replay controllers are added to build_registered_controllers(), they are included in the agent-facing schema/tool catalog; this file already has build_internal_only_controllers() for RPC-callable renderer-only surfaces. In a run with active IDs from agent_runs_active, an agent can call agent_run_events and receive raw AgentObservation records, including event variants such as model deltas/tool-call fragments and unknown-tool arguments from the journaled EventSink. Register these under the internal-only registry, or return a deliberately sanitized DTO if they must be agent-facing.

Useful? React with 👍 / 👎.

Comment on lines +64 to +66
// Read one extra record to detect whether a further page exists without a
// second store round-trip.
let mut events = journal.read_from(run_id, offset).await.map_err(|e| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid loading the full event stream for each page

For runs with a large journal, the page limit does not bound the read: read_from(run_id, offset) materializes every observation after offset before this function truncates to effective_limit. A reconnect with limit=200 on a long run can still read and deserialize the whole stream, causing latency/memory spikes and defeating the RPC's page cap; use a bounded read/iterator or stop after limit + 1 records.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant