Skip to content

fix(orchestration): reliable agent-to-agent DM replies (surface, correct content, resilience) — Closes #4583#4599

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/orchestration-outbound-reply-and-wake-resilience
Jul 6, 2026
Merged

fix(orchestration): reliable agent-to-agent DM replies (surface, correct content, resilience) — Closes #4583#4599
senamakel merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/orchestration-outbound-reply-and-wake-resilience

Conversation

@sanil-23

@sanil-23 sanil-23 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #4582. Fixes four defects in the tiny.place agent-to-agent orchestration DM loop, all reproduced + verified end-to-end on staging:

  • Silent drop — a wrapped Claude peer stamps message.line = 0 on every DM, so under fix(orchestration): key inbound DMs on the shared per-pair session id #4582's shared wrapper_session_id key the wake cursor pinned at 0 and every DM after the first was persisted + acked but never woke the graph (no reply). Now stamps a store-assigned monotonic per-(agent, session) ingest ordinal.
  • Wrong reply content — the front end sent run_single's trailing narration ("Done — sent to the session") instead of the reply_to_channel argument. Now captures + sends the decision-tool payload.
  • Replies invisible in the UI — outbound replies were never persisted/announced, so the orchestration chat window never showed them. Now persists role=owner + emits notify_orchestration_message.
  • Orphaned on transient failure — a transient send_dm error (e.g. staging HTTP 400: body must be encrypted ciphertext) failed the whole wake and left the message in permanent silence. Now retries the wake with backoff (resumes from the graph checkpoint).

Problem

#4582 keyed inbound sessions on the shared wrapper_session_id but the wake idempotence cursor still rides seq = env.message.line, which is 0 for every message from a wrapped Claude harness (its own #4583 guard only warned). Beyond that root cause, the outbound path had three independent gaps: the reply text sent was the model's post-tool narration, the reply was never recorded in the store the UI renders from, and any transient send/relay error orphaned the message because the cursor only advances on success and the wake is one-shot.

Solution

  • store::next_session_seq = MAX(seq)+1 per (agent, session); persist_message stamps it on both the session last_seq and the message seq, replacing the diagnostic WARN guard. Monotonic regardless of the wire line; also fixes the secondary cycle_id collision.
  • with_decision_capture (task-local) records the reply_to_channel / defer_to_orchestrator argument; frontend_instruct/frontend_compile prefer it over the raw turn text.
  • persist_outgoing_reply writes the agent's reply (role=owner, monotonic seq, no wake trigger) and fans out notify_orchestration_message, mirroring the inbound path and the send_master RPC.
  • schedule_wake retries invoke_orchestration_graph on error with 5s/15s/45s backoff, bailing if a newer wake supersedes it; the graph checkpoints per super-step so retries only re-attempt the failed tail (no repeated LLM work).

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategypersist_stamps_monotonic_ingest_seq_so_line_zero_dms_still_wake, decision_capture_surfaces_tool_payload_not_turn_narration, decision_capture_is_none_without_a_decision_tool; existing cursor/idempotence tests still pass (58/58 orchestration).
  • Diff coverage ≥ 80% — pure logic (next_session_seq, ingest stamping, decision capture) is unit-covered; the graph-runtime wiring (persist_outgoing_reply, notify, the retry loop) is behaviour-only and was verified live on staging. Will add targeted coverage if diff-cover flags it in CI.
  • N/A — Coverage matrix: behaviour/bugfix on an existing feature, no added/removed/renamed rows.
  • N/A — Feature IDs: bugfix, no matrix feature rows to list.
  • No new external network dependencies introduced.
  • N/A — Manual smoke: does not touch a release-cut surface (orchestration is Beta, gated by [orchestration]).
  • Linked issue closed via Closes #4583 in ## Related.

Impact

  • Platform: desktop core (src/openhuman/orchestration/*) only; no Tauri/app/mobile/CLI changes. Gated behind [orchestration].enabled (Beta).
  • Behaviour: agent-to-agent DMs now (a) reliably wake the graph, (b) send the real answer, (c) show the reply in the chat window, and (d) self-heal on transient send failures.
  • Migration: self-migrating — existing sessions get a monotonic seq on their next DM; old line-based cursors are immediately exceeded (one clean wake).
  • Perf/security: no new deps; retries are checkpoint-resumed (bounded, no repeated LLM work); no secrets logged.

Related


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

Linear Issue

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

Commit & Branch

  • Branch: fix/orchestration-outbound-reply-and-wake-resilience
  • Commit SHA: b0c376df4, a06cedb43

Validation Run

  • pnpm --filter openhuman-app format:check — N/A (no app/TS changes); cargo fmt applied to changed files.
  • N/A — pnpm typecheck (no TypeScript changed).
  • Focused tests: cargo test --lib openhuman::orchestration:: → 58 passed, 0 failed.
  • Rust fmt/check: cargo fmt applied; orchestration lib compiles clean.
  • N/A — Tauri fmt/check (no app/src-tauri changes).

Validation Blocked

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

Behavior Changes

  • Intended behavior change: inbound agent-to-agent DMs reliably produce a reply, with the correct content, visible in the UI, resilient to transient send failures.
  • User-visible effect: the assistant now actually answers DMs from linked agents and the exchange appears in the tiny.place orchestration chat.

Summary by CodeRabbit

  • New Features

    • Front-end decision actions now preserve the exact selected response, improving what gets displayed and processed.
  • Bug Fixes

    • Replies sent from the app are now saved more reliably into chat history, even under concurrent or out-of-order inputs.
    • Improved orchestration wake behavior: ordering advances consistently, and failed orchestration runs are retried with backoff until superseded.
    • Hardens outbound message handling so the saved reply is persisted before reporting success.

sanil-23 added 2 commits July 7, 2026 00:39
… dropped

A wrapped Claude harness stamps `message.line = 0` on every DM, so after tinyhumansai#4582
keyed the session on the shared `wrapper_session_id` the wake idempotence cursor
(`latest_seq > cursor`) pinned at 0 after the first message — every later DM was
persisted + acked but silently skipped the graph (no reply). Replace the
diagnostic-only WARN guard with a store-assigned, strictly-increasing
per-`(agent, session)` ingest ordinal (`store::next_session_seq`), so every new
DM advances `last_seq` past the cursor and wakes the graph. Self-migrating and
also fixes the secondary `cycle_id` collision.

Refs tinyhumansai#4583
…ry on failure

Three outbound-path fixes on top of tinyhumansai#4582:

- Reply content: the front end's channel response used `run_single`'s trailing
  narration ("Done — sent to the session") and discarded the `reply_to_channel`
  argument (the real answer). Capture the decision-tool payload via
  `with_decision_capture` and send that instead (same for `defer_to_orchestrator`
  → reasoning instructions).

- Surface in UI: persist the agent's own outbound reply (`role = owner`) and emit
  `notify_orchestration_message`, so it appears in the orchestration chat window.
  Ingest only persisted inbound DMs and the UI live-refetches only on that socket
  event, so replies never showed.

- Resilience: a transient send/relay/provider error (e.g. a staging
  `HTTP 400: body must be encrypted ciphertext` on `send_dm`) made the node throw
  → the whole wake failed, the cursor stayed put, and the one-shot wake left the
  message orphaned in silence. Retry the wake with backoff (5s/15s/45s); it
  resumes from the graph checkpoint so retries only re-attempt the failed tail.

Refs tinyhumansai#4583
@sanil-23 sanil-23 requested a review from a team July 6, 2026 19:11
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR assigns monotonic ingest sequences inside the store, uses that sequence for session/message persistence, adds wake retry/backoff, persists outbound replies after successful sends, and captures decision tool payloads so frontend agent nodes can return tool-authored text.

Changes

Wake Cursor Sequencing Fix

Layer / File(s) Summary
Monotonic ingest sequence helper
src/openhuman/orchestration/store.rs
Adds busy-timeout setup, in_immediate_txn, next_session_seq, and a concurrency test for distinct contiguous sequence allocation.
Persist messages using store-assigned seq
src/openhuman/orchestration/ingest.rs
Replaces wire-provided sequencing with a store-assigned ingest ordinal inside an immediate transaction and updates the regression test for reset line = 0 inputs.

Estimated code review effort: 4 (Complex) | ~40 minutes

Wake Retry, Reply Persistence, and Decision Capture

Layer / File(s) Summary
Decision payload capture primitive
src/openhuman/orchestration/tools.rs
Adds task-local decision capture, records tool payloads for reply and defer actions, and adds async tests for capture behavior.
Frontend agent nodes use decision capture
src/openhuman/orchestration/ops.rs
Wraps frontend instruct/compile turns with decision capture and returns captured tool-provided text when available.
Wake scheduler retry/backoff
src/openhuman/orchestration/ops.rs
Retries wake invocation with fixed backoff and stops when a newer wake generation supersedes the current one.
Persist outgoing reply and wire into send_dm
src/openhuman/orchestration/ops.rs
Persists outbound replies after successful sends, notifies the bus, and maps Signal send errors through map_err.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Harness
  participant persist_message
  participant Store

  Harness->>persist_message: classified message (line may repeat/reset)
  persist_message->>Store: next_session_seq(agent_id, session_id)
  Store-->>persist_message: ingest_seq (MAX(seq)+1)
  persist_message->>Store: set OrchestrationMessage.seq = ingest_seq
  persist_message->>Store: set OrchestrationSession.last_seq = ingest_seq
Loading
sequenceDiagram
  participant send_dm
  participant Signal
  participant persist_outgoing_reply
  participant Store
  participant OrchestrationBus

  send_dm->>Signal: send message
  Signal-->>send_dm: success
  send_dm->>persist_outgoing_reply: reply text
  persist_outgoing_reply->>Store: write OrchestrationMessage
  persist_outgoing_reply->>OrchestrationBus: notify UI refetch
Loading

Possibly related PRs

Suggested labels: rust-core, agent, bug

Suggested reviewers: M3gA-Mind

Poem

A bunny hopped where seqs once lied,
then tucked them safe in store-grown pride.
Replies now land, and wakes retry,
with captured words from tool and sky.
Hop hop hooray, the cursor sings —
no silent drops, just orderly things. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also adds outbound reply persistence, decision-payload capture, and wake retry logic, which are outside the linked issue's wake-cursor fix. Move the reply-surface, decision-capture, and retry/backoff changes to separate PRs or link issues that explicitly require them.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the orchestration DM reliability fix and matches the main sequencing and reply-handling changes.
Linked Issues check ✅ Passed The PR replaces line-based sequencing with a store-assigned monotonic ingest seq and adds line=0 regression tests, addressing #4583's silent-drop bug.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 6, 2026

@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: a06cedb438

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/openhuman/orchestration/ops.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/orchestration/store.rs`:
- Around line 223-229: The `next_session_seq` and `insert_message` flow is
vulnerable to duplicate `seq` values because `persist_message` and
`persist_outgoing_reply` compute `MAX(seq)+1` and then insert using separate
calls on fresh connections from `with_connection`. Update the persistence path
so the read and write happen in a single SQLite transaction, and make the
transaction cover the `next_session_seq`/`insert_message` pair used by both
`persist_message` and `persist_outgoing_reply` to ensure concurrent writers
cannot race on the same `(agent_id, session_id)`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 859402ef-5fa1-4273-a69f-c19f71f30d28

📥 Commits

Reviewing files that changed from the base of the PR and between f492493 and a06cedb.

📒 Files selected for processing (4)
  • src/openhuman/orchestration/ingest.rs
  • src/openhuman/orchestration/ops.rs
  • src/openhuman/orchestration/store.rs
  • src/openhuman/orchestration/tools.rs

Comment thread src/openhuman/orchestration/store.rs
…mp wake last_seq

- Wrap `MAX(seq)+1` → `INSERT` in a `BEGIN IMMEDIATE` txn (`store::in_immediate_txn`)
  with a `busy_timeout`, so the drain's inbound persist and the graph's `send_dm`
  reply persist can't race on the same `(agent, session)` and write a duplicate
  `seq` (which would break the monotonic wake cursor). Adds a multi-thread
  concurrency test. [CodeRabbit]

- `persist_outgoing_reply` no longer advances `sessions.last_seq` for our own
  outbound reply — the wake cursor only tracks inbound seqs, so bumping it made
  `ingest_cursor_lag` / `orchestration.status` falsely report pending work until
  the next inbound DM. `upsert_session`'s `MAX(..)` clamp keeps the inbound
  `last_seq`; passing 0 refreshes `last_message_at` only. [Codex]

Refs tinyhumansai#4583

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/openhuman/orchestration/store.rs (1)

249-262: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Add an index for next_session_seq

next_session_seq filters messages by (agent_id, session_id) and then computes MAX(seq), but the only existing index is (agent_id, session_id, timestamp). Add an index that includes seq (for example, (agent_id, session_id, seq)) so this hot allocation stays cheap under write lock as sessions grow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/orchestration/store.rs` around lines 249 - 262,
`next_session_seq` currently scans `messages` by `agent_id` and `session_id` to
compute `MAX(seq)`, so add a supporting index that includes `seq` for this
lookup pattern. Update the schema/migration near the `messages` table indexes to
add an index on `agent_id, session_id, seq` so the `next_session_seq` query in
`store.rs` stays efficient as session history grows.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/openhuman/orchestration/store.rs`:
- Around line 249-262: `next_session_seq` currently scans `messages` by
`agent_id` and `session_id` to compute `MAX(seq)`, so add a supporting index
that includes `seq` for this lookup pattern. Update the schema/migration near
the `messages` table indexes to add an index on `agent_id, session_id, seq` so
the `next_session_seq` query in `store.rs` stays efficient as session history
grows.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d6fb7c32-f9bb-4ab3-a46e-f176e67564f9

📥 Commits

Reviewing files that changed from the base of the PR and between a06cedb and 2c47758.

📒 Files selected for processing (3)
  • src/openhuman/orchestration/ingest.rs
  • src/openhuman/orchestration/ops.rs
  • src/openhuman/orchestration/store.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/openhuman/orchestration/ingest.rs
  • src/openhuman/orchestration/ops.rs

@senamakel senamakel merged commit b47faa4 into tinyhumansai:main Jul 6, 2026
19 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

orchestration: wake cursor keyed on wrapper_session_id but seq=env.message.line → silent message drop

2 participants