Skip to content

feat(orchestration): surface inbound relay DMs via mailbox poll-drain + fix Signal card key#4564

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
sanil-23:feat/orchestration-dm-poll-drain
Jul 5, 2026
Merged

feat(orchestration): surface inbound relay DMs via mailbox poll-drain + fix Signal card key#4564
senamakel merged 3 commits into
tinyhumansai:mainfrom
sanil-23:feat/orchestration-dm-poll-drain

Conversation

@sanil-23

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

Copy link
Copy Markdown
Contributor

Summary

  • Surface inbound relay DMs. Add a 15s orchestration poll-drain of the tiny.place /messages mailbox so DMs from paired agents actually reach the wake graph — previously they never surfaced.
  • Fix the Signal directory card key. Advertise the wallet's Ed25519 identity key (not its X25519 DH key) as encryptionPublicKey, so peers can resolve this agent and fetch its prekey bundle.
  • Both are backend-delivery correctness fixes for agent→OpenHuman DMs; no UI surface changes.

Problem

Agents could not reliably message OpenHuman, and messages that were delivered never surfaced:

  1. DMs never surfaced. Orchestration ingest was purely driven by DomainEvent::TinyPlaceStreamMessage (the /inbox/stream WebSocket). But the relay delivers DMs to /messages (poll-only) and never publishes them to /inbox/stream — that stream only carries inbox items for payments/notifications. So inbound DMs from paired agents piled up undrained in the mailbox while the orchestration store stayed empty. (Verified on staging: CIPHERTEXT envelopes sat in the mailbox for hours; the orchestration DB had zero inbound.)
  2. Peers couldn't resolve this agent. signal_register_encryption_key published the wallet's X25519 DH key as the card's metadata.encryptionPublicKey. But Signal prekey bundles and mailboxes are keyed by the Ed25519 identity key (cryptoId). Peers resolve a recipient via the directory card and prefer encryptionPublicKey for both addressing and the bundle fetch — so they resolved to a bundle-less key and 404'd on /keys/<key>/bundle, unable to open a session.

Solution

  • feat(orchestration)start_message_drain_supervisor() spawns a 15s loop calling drain_mailbox_once(), which lists /messages and runs each envelope through the existing decrypt → classify → persist → acknowledge ingest_one pipeline. Unlinked senders are skipped without being decrypted/consumed (the Signal ratchet is protected), so their DMs stay readable by the Messaging UI. Wired in at startup next to the existing wake/ingest subscribers.
  • fix(tinyplace) — advertise signer.public_key_base64() (Ed25519 identity) as encryptionPublicKey; peers derive the X25519 DH key from the bundle themselves (decode_identity_key). The signal_key_status readiness check now compares against the identity key too.

Validated end-to-end on staging: after both fixes, a real base58 SDK-2.0 agent's messages drained → decrypted → persisted → woke the graph → produced a reply (confirmed in world_diff/compressed_history).

Deliberately out of scope: an inbox-stream WS-auth fix (the WS handshake 401s against the backend) — it feeds nothing into DM ingest (is_dm_stream excludes the inbox stream) and belongs in the tinyplace SDK repo, not here. Tracked as a follow-up.

Submission Checklist

  • Tests added or updated — drain_is_a_noop_when_orchestration_disabled covers the guard early-return; the drain's happy path reuses the already-tested ingest_one pipeline (persist_message/classify_message tests).
  • Diff coverage ≥ 80%cargo test green locally; new/changed lines are the guard (tested) + a thin poller over the covered ingest_one path. CI diff-cover is authoritative on the exact number.
  • Coverage matrix updated — N/A: internal delivery-path fix to existing orchestration ingest; no new user-facing feature row.
  • All affected feature IDs listed in ## RelatedN/A: no matrix feature IDs affected.
  • No new external network dependencies — uses the existing in-tree tinyplace client; no new deps or hosts.
  • Manual smoke checklist updated — N/A: no release-cut surface touched (headless core delivery path).
  • Linked issue closed via Closes #NNNN/A: no tracked issue; found while testing the orchestration DM flow.

Impact

  • Desktop core only. Adds one 15s background poll of /messages (bounded, best-effort; silent when the mailbox is empty). No UI, migration, or schema changes.
  • Security: unlinked senders are never decrypted or acknowledged — the Signal ratchet is only advanced for paired agents, unchanged from the existing stream path.
  • The card-key change re-publishes encryptionPublicKey; existing agents pick up the corrected key on their next signal_register_encryption_key (e.g. "Make discoverable" / re-provision).

Related

  • Closes: N/A
  • Follow-up PR(s)/TODOs: tinyplace SDK WS-auth fix (directory/SIWS query params for /inbox/stream and the other WS streams) — separate SDK-repo change.

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

Linear Issue

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

Commit & Branch

  • Branch: feat/orchestration-dm-poll-drain
  • Commit SHA: ce9949e0a (card-key), 130470cfc (poll-drain)

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: no app/ TypeScript changed.
  • pnpm typecheck — N/A: no TypeScript changed.
  • Focused tests: cargo test --lib orchestration::ingest — 5 passed.
  • Rust fmt/check (if changed): cargo fmt --check clean; cargo check --lib Finished, no errors.
  • Tauri fmt/check (if changed): N/A — no app/src-tauri changes.

Validation Blocked

  • command: full pnpm test:coverage / diff-cover not run locally
  • error: N/A — deferred to CI
  • impact: CI computes authoritative diff coverage; changed lines are the tested guard + a thin poller over the already-covered ingest_one pipeline.

Behavior Changes

  • Intended behavior change: inbound relay DMs from paired agents now surface into orchestration (and trigger the wake graph); the directory card advertises the fetchable identity key.
  • User-visible effect: agents can DM OpenHuman and OpenHuman receives/acts on their messages.

Parity Contract

  • Legacy behavior preserved: the stream-driven ingest_stream_message path is unchanged; the poll-drain reuses the same ingest_one (sender gate, dedupe, ack) so semantics match.
  • Guard/fallback/dispatch parity checks: unlinked senders skipped without decrypt/ack (identical to stream path); disabled-orchestration short-circuits with no client call.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: N/A
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Added a background message-polling supervisor to continuously fetch and deliver incoming relay messages for paired agents.
    • Added a batch mailbox drain operation to poll up to 100 message envelopes and process them through the existing ingest pipeline.
  • Bug Fixes

    • Updated encryption-key publishing and readiness checks to use the wallet signer’s Ed25519 identity public key (matching what peers expect).
    • If orchestration is disabled, mailbox draining now safely returns no results.
  • Tests

    • Added an async test verifying the disabled-orchestration mailbox drain short-circuits to Ok(0).

sanil-23 added 2 commits July 5, 2026 15:26
…ionPublicKey

The directory card published the wallet's X25519 DH key as
`metadata.encryptionPublicKey`, but Signal prekey bundles and mailboxes are
keyed by the Ed25519 identity key (cryptoId). Peers resolve a recipient via the
card and prefer encryptionPublicKey for both addressing and the bundle fetch, so
they resolved to a bundle-less key and 404'd on `/keys/<key>/bundle` — no Signal
session could be established and inbound DMs never reached the agent.

Advertise the Ed25519 identity key instead (peers derive the X25519 DH key from
the bundle themselves), and compare against it in the signal_key_status
readiness check.
tiny.place relay DMs are delivered to `/messages` (poll-only); the backend never
publishes them to the `/inbox/stream` WebSocket (that stream only carries inbox
items for payments/notifications). Orchestration ingest was purely stream-driven,
so inbound DMs from paired agents never surfaced — they piled up undrained in the
mailbox while the orchestration store stayed empty.

Add a 15s poll-drain supervisor that lists `/messages` and runs each envelope
through the existing decrypt -> classify -> persist -> acknowledge ingest
pipeline. Unlinked senders are skipped without being consumed, so their DMs stay
readable by the Messaging UI.
@sanil-23 sanil-23 requested a review from a team July 5, 2026 10:00
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d7aa8836-1217-4db0-9d8a-563f7af4ced7

📥 Commits

Reviewing files that changed from the base of the PR and between 130470c and ca18054.

📒 Files selected for processing (2)
  • src/openhuman/tinyplace/manifest.rs
  • src/openhuman/tinyplace/schemas.rs
✅ Files skipped from review due to trivial changes (1)
  • src/openhuman/tinyplace/schemas.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/openhuman/tinyplace/manifest.rs

📝 Walkthrough

Walkthrough

Adds a relay DM polling pipeline with startup wiring, and updates tinyplace encryption key handling to use the signer’s Ed25519 identity public key.

Changes

Mailbox Drain Polling

Layer / File(s) Summary
Mailbox drain ingestion
src/openhuman/orchestration/ingest.rs
Adds drain_mailbox_once to fetch mailbox envelopes and pass them through ingest_one, with a disabled-orchestration short-circuit and test coverage.
Polling supervisor and startup wiring
src/openhuman/orchestration/ops.rs, src/openhuman/orchestration/mod.rs, src/core/jsonrpc.rs
Adds the background drain supervisor, re-exports it from the orchestration module, and starts it from domain subscriber registration.

Encryption Identity Key Correction

Layer / File(s) Summary
Ed25519 identity key usage
src/openhuman/tinyplace/manifest.rs, src/openhuman/tinyplace/schemas.rs
Changes key status and registration logic, plus schema text, to use the signer’s Ed25519 identity key instead of the local X25519 key.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Jsonrpc as core::jsonrpc
  participant Supervisor as start_message_drain_supervisor
  participant Ingest as drain_mailbox_once
  participant Relay as tinyplace relay

  Jsonrpc->>Supervisor: register_domain_subscribers spawns task
  loop every 15 seconds
    Supervisor->>Supervisor: Config::load_or_init()
    Supervisor->>Ingest: drain_mailbox_once(config)
    Ingest->>Relay: fetch up to 100 message envelopes
    Relay-->>Ingest: envelopes
    Ingest->>Ingest: ingest_one per envelope
    Ingest-->>Supervisor: count fetched
  end
Loading

Possibly related PRs

Suggested labels: feature, rust-core, agent

Suggested reviewers: M3gA-Mind

Poem

A rabbit checked the relay trail,
Then nibbled mail without a fail.
The key now shines in Ed25519 light,
And inbox whispers wake the night.
🐰📬

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: mailbox poll-drain for inbound relay DMs and the Signal card key fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@coderabbitai coderabbitai Bot added bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 5, 2026

@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

🧹 Nitpick comments (2)
src/openhuman/orchestration/ops.rs (1)

134-140: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Drain errors logged only at debug.

drain error and drain config load failures are logged at debug, which is typically suppressed in production. Since this poller is the sole delivery path for relay DMs per this PR's own rationale, silent repeated failures (e.g., misconfigured signer, network issues) could go unnoticed indefinitely. Consider log::warn! for these failure branches so operators can detect a broken drain path.

♻️ Suggested log-level bump
                 Ok(config) => match super::ingest::drain_mailbox_once(&config).await {
                     Ok(n) if n > 0 => {
                         log::debug!(target: LOG, "[orchestration] drain: examined {n} envelope(s)")
                     }
                     Ok(_) => {}
-                    Err(e) => log::debug!(target: LOG, "[orchestration] drain error: {e}"),
+                    Err(e) => log::warn!(target: LOG, "[orchestration] drain error: {e}"),
                 },
-                Err(e) => log::debug!(target: LOG, "[orchestration] drain config load: {e}"),
+                Err(e) => log::warn!(target: LOG, "[orchestration] drain config load: {e}"),
🤖 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/ops.rs` around lines 134 - 140, The drain failure
branches in the orchestration poller are logged too quietly, so operators may
miss broken delivery when the sole relay DM path fails. Update the `drain`
handling in `ops.rs` to use a higher-visibility level like `log::warn!` for both
the `Err(e)` cases in the `drain error` and `drain config load` branches, while
keeping the successful `Ok(n)` debug logging unchanged.
src/openhuman/orchestration/ingest.rs (1)

223-227: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No timeout on the mailbox messages.list HTTP call.

If the tinyplace client hangs, this tick blocks indefinitely and the loop never sleeps/retries. Since this runs in a dedicated tokio::spawn background task (not a request thread), impact is limited to delayed draining rather than availability of other work — but adding an explicit timeout would make the failure mode bounded and observable rather than silent.

🤖 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/ingest.rs` around lines 223 - 227, The mailbox
fetch in the background ingest loop can hang indefinitely because the
messages.list call has no explicit timeout. Update the ingest tick logic around
client.messages.list in the orchestration ingest task to wrap that HTTP call
with a bounded timeout and surface a clear timeout error through the existing
map_err path. Keep the fix localized to the tokio::spawn background loop so
failures remain observable and the retry/sleep cycle can continue.
🤖 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/tinyplace/manifest.rs`:
- Around line 3624-3640: Update the RPC schema text for
signal_register_encryption_key so it matches the handler’s actual behavior. In
schemas.rs, revise the description that currently says it publishes the Signal
X25519 identity public key to reflect that the method advertises
signer.public_key_base64(), i.e. the wallet’s Ed25519 identity key. Keep the
wording aligned with the contract used by the signal_register_encryption_key
path in manifest.rs so the docs and implementation agree.

---

Nitpick comments:
In `@src/openhuman/orchestration/ingest.rs`:
- Around line 223-227: The mailbox fetch in the background ingest loop can hang
indefinitely because the messages.list call has no explicit timeout. Update the
ingest tick logic around client.messages.list in the orchestration ingest task
to wrap that HTTP call with a bounded timeout and surface a clear timeout error
through the existing map_err path. Keep the fix localized to the tokio::spawn
background loop so failures remain observable and the retry/sleep cycle can
continue.

In `@src/openhuman/orchestration/ops.rs`:
- Around line 134-140: The drain failure branches in the orchestration poller
are logged too quietly, so operators may miss broken delivery when the sole
relay DM path fails. Update the `drain` handling in `ops.rs` to use a
higher-visibility level like `log::warn!` for both the `Err(e)` cases in the
`drain error` and `drain config load` branches, while keeping the successful
`Ok(n)` debug logging unchanged.
🪄 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: 9a0ab201-632c-40df-945c-d892c7fb168c

📥 Commits

Reviewing files that changed from the base of the PR and between 6ad3b99 and 130470c.

📒 Files selected for processing (5)
  • src/core/jsonrpc.rs
  • src/openhuman/orchestration/ingest.rs
  • src/openhuman/orchestration/mod.rs
  • src/openhuman/orchestration/ops.rs
  • src/openhuman/tinyplace/manifest.rs

Comment thread src/openhuman/tinyplace/manifest.rs
…19 key

Address CodeRabbit: the RPC schema description + handler doc-comment still said
this publishes the X25519 identity key, but it now advertises the Ed25519
identity key (signer.public_key_base64()). Update both to match the contract.
@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior. and removed bug labels Jul 5, 2026
@senamakel senamakel merged commit 6214978 into tinyhumansai:main Jul 5, 2026
19 of 27 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 5, 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/. feature Net-new user-facing capability or product behavior. 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.

2 participants