feat(orchestration): surface inbound relay DMs via mailbox poll-drain + fix Signal card key#4564
Conversation
…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.
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a relay DM polling pipeline with startup wiring, and updates tinyplace encryption key handling to use the signer’s Ed25519 identity public key. ChangesMailbox Drain Polling
Encryption Identity Key Correction
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
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/openhuman/orchestration/ops.rs (1)
134-140: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winDrain errors logged only at
debug.
drain erroranddrain config loadfailures are logged atdebug, 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. Considerlog::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 winNo timeout on the mailbox
messages.listHTTP call.If the tinyplace client hangs, this tick blocks indefinitely and the loop never sleeps/retries. Since this runs in a dedicated
tokio::spawnbackground 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
📒 Files selected for processing (5)
src/core/jsonrpc.rssrc/openhuman/orchestration/ingest.rssrc/openhuman/orchestration/mod.rssrc/openhuman/orchestration/ops.rssrc/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.
Summary
/messagesmailbox so DMs from paired agents actually reach the wake graph — previously they never surfaced.encryptionPublicKey, so peers can resolve this agent and fetch its prekey bundle.Problem
Agents could not reliably message OpenHuman, and messages that were delivered never surfaced:
DomainEvent::TinyPlaceStreamMessage(the/inbox/streamWebSocket). 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.)signal_register_encryption_keypublished the wallet's X25519 DH key as the card'smetadata.encryptionPublicKey. But Signal prekey bundles and mailboxes are keyed by the Ed25519 identity key (cryptoId). Peers resolve a recipient via the directory card and preferencryptionPublicKeyfor 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 callingdrain_mailbox_once(), which lists/messagesand runs each envelope through the existing decrypt → classify → persist → acknowledgeingest_onepipeline. 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)— advertisesigner.public_key_base64()(Ed25519 identity) asencryptionPublicKey; peers derive the X25519 DH key from the bundle themselves (decode_identity_key). Thesignal_key_statusreadiness 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_streamexcludes theinboxstream) and belongs in the tinyplace SDK repo, not here. Tracked as a follow-up.Submission Checklist
drain_is_a_noop_when_orchestration_disabledcovers the guard early-return; the drain's happy path reuses the already-testedingest_onepipeline (persist_message/classify_messagetests).cargo testgreen locally; new/changed lines are the guard (tested) + a thin poller over the coveredingest_onepath. CIdiff-coveris authoritative on the exact number.N/A: internal delivery-path fix to existing orchestration ingest; no new user-facing feature row.## Related—N/A: no matrix feature IDs affected.tinyplaceclient; no new deps or hosts.N/A: no release-cut surface touched (headless core delivery path).Closes #NNN—N/A: no tracked issue; found while testing the orchestration DM flow.Impact
/messages(bounded, best-effort; silent when the mailbox is empty). No UI, migration, or schema changes.encryptionPublicKey; existing agents pick up the corrected key on their nextsignal_register_encryption_key(e.g. "Make discoverable" / re-provision).Related
/inbox/streamand the other WS streams) — separate SDK-repo change.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
feat/orchestration-dm-poll-draince9949e0a(card-key),130470cfc(poll-drain)Validation Run
pnpm --filter openhuman-app format:check— N/A: noapp/TypeScript changed.pnpm typecheck— N/A: no TypeScript changed.cargo test --lib orchestration::ingest— 5 passed.cargo fmt --checkclean;cargo check --libFinished, no errors.app/src-taurichanges.Validation Blocked
command:fullpnpm test:coverage/diff-covernot run locallyerror:N/A — deferred to CIimpact:CI computes authoritative diff coverage; changed lines are the tested guard + a thin poller over the already-coveredingest_onepipeline.Behavior Changes
Parity Contract
ingest_stream_messagepath is unchanged; the poll-drain reuses the sameingest_one(sender gate, dedupe, ack) so semantics match.Duplicate / Superseded PR Handling
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Ok(0).