feat(orchestration): connect a co-located local CLI agent + fix inbound peer attribution#4777
feat(orchestration): connect a co-located local CLI agent + fix inbound peer attribution#4777oxoxDev wants to merge 2 commits into
Conversation
A plain (non-harness) DM is only ever classified on the inbound decrypt path, so its author is always the peer, never us. It was stamped role "user", which the transcript treats as owner-authored and right-aligns — so an incoming peer DM stacked on the owner's own side (both halves of a two-way conversation rendered right). Stamp "peer" instead; our own outgoing Master message is persisted separately as "owner" and stays right.
A freshly-launched local coding-agent CLI cannot link to its OpenHuman
without a manual contact-request approval: the CLI sends a contact
request, but auto-accept only ever accepted already-linked ids, so the
request sat pending and the CLI's session stream was dropped by the
sender gate until a human clicked accept. A tiny.place contact request
carries no owner declaration on the wire, so OpenHuman cannot recognise a
co-located CLI from the request alone.
Add a same-machine handshake: the CLI writes { agentId, owner, ts } into
~/.openhuman/local-agents.json before it requests contact. Auto-accept
now also accepts a pending requester whose id appears in that file under
a FRESH entry that names THIS OpenHuman as owner. Same-user filesystem
access is the trust proof; the owner-match stops a CLI aimed at a
different local OpenHuman from being cross-accepted, and the 1h TTL stops
a departed agent's id lingering as auto-acceptable. Both sources stay
fail-closed (missing/corrupt file, locked wallet, stale/undated entry all
trust nothing). Encoding-agnostic id + owner matching reuses
resolve_linked_id.
📝 WalkthroughWalkthroughAdds fail-closed local handshake trust with timestamp validation to contact auto-acceptance, and classifies decrypted plain Master inbound DMs as peer-authored. ChangesLocal pairing trust
DM role classification
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant ContactRequester
participant AutoAccept as auto_accept_linked_contact_requests
participant LocalAgents as local-agents.json
participant Identity as resolve_linked_id
ContactRequester->>AutoAccept: submit contact request
AutoAccept->>LocalAgents: load local handshake entries
AutoAccept->>Identity: resolve local OpenHuman signer
Identity-->>AutoAccept: local identity
AutoAccept->>AutoAccept: validate owner and timestamp
AutoAccept-->>ContactRequester: accept only trusted requester
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 589ce6da9c
ℹ️ 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".
| match std::fs::read(&path) { | ||
| Ok(bytes) => serde_json::from_slice(&bytes).unwrap_or_else(|e| { |
There was a problem hiding this comment.
Validate permissions before trusting local handshakes
Because this file becomes the trust proof for auto-accepting a contact request, reading and trusting any ~/.openhuman/local-agents.json that is merely readable is unsafe on multi-user systems where the directory or file is group/world-writable (for example from a permissive umask or shared home). In that case another local account can write a fresh entry naming this owner and have its agent contact auto-accepted; the reader should fail closed unless the file and parent directory are owned by the current user and not writable by others (or otherwise use a private, mode-checked location).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
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/ingest.rs`:
- Around line 181-192: The ClassifiedMessage role change to "peer" must be
coordinated with every downstream consumer. Trace consumers of ClassifiedMessage
and persisted roles, updating Claude Code ingestion, history prompt
construction, system flattening, and memory extraction to treat "peer" as
inbound user content while preserving correct display alignment; alternatively
retain "user" for inference and add a separate author/display field. Add
end-to-end coverage proving inbound peer messages are included in prompts,
histories, flattened systems, and memory extraction.
🪄 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: ab86560d-10fd-4b1b-85f7-3564d4007ec2
📒 Files selected for processing (2)
src/openhuman/agent_orchestration/pairing.rssrc/openhuman/orchestration/ingest.rs
| // | ||
| // This path only ever runs on an INBOUND, decrypted DM (the counterpart is | ||
| // the sender — see `ingest_one`), so the author is the PEER, never us. Stamp | ||
| // `"peer"` rather than `"user"`: the transcript treats `you`/`owner`/`user` | ||
| // as owner-authored and right-aligns them, so a peer's incoming Master DM was | ||
| // rendering on the owner's side (both halves of a two-way DM stacked right). | ||
| // `"peer"` is not owner-authored → it left-aligns as the counterpart. Our own | ||
| // outgoing Master message is persisted separately as `"owner"` and stays right. | ||
| ClassifiedMessage { | ||
| chat_kind: ChatKind::Master, | ||
| session_id: "master".to_string(), | ||
| role: "user".to_string(), | ||
| role: "peer".to_string(), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Update downstream consumers before changing this role.
Persisting "peer" fixes alignment, but current consumers only recognize "user" for inbound content: Claude Code drops "peer" rows, history providers may send an empty prompt, system flattening may insert a synthetic user, and memory extraction skips user-only preferences/commitments. Either preserve "user" for inference semantics with a separate display/author field, or update all consumers and add end-to-end coverage before merging.
🤖 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 181 - 192, The
ClassifiedMessage role change to "peer" must be coordinated with every
downstream consumer. Trace consumers of ClassifiedMessage and persisted roles,
updating Claude Code ingestion, history prompt construction, system flattening,
and memory extraction to treat "peer" as inbound user content while preserving
correct display alignment; alternatively retain "user" for inference and add a
separate author/display field. Add end-to-end coverage proving inbound peer
messages are included in prompts, histories, flattened systems, and memory
extraction.
Summary
Two fixes for the coding-agent → OpenHuman orchestration bridge: (1) a freshly-launched local CLI agent now connects with no manual contact-approval click, and (2) an inbound peer Master DM now renders on the correct (left) side instead of the owner's side.
Problem
Handshake: a fresh CLI identity sends a tiny.place contact request, but auto-accept only ever accepted already-linked ids — so the request sat pending and the CLI's session stream was dropped by the sender gate until a human clicked accept. A contact request carries no owner declaration on the wire, so OpenHuman could not recognise a co-located CLI from the request alone.
Attribution: a plain (non-harness) DM is only ever classified on the inbound decrypt path, so its author is always the peer — never us. It was stamped role
"user", which the transcript treats as owner-authored and right-aligns, so an incoming peer DM stacked on the owner's own side (both halves of a two-way conversation rendered right).Solution
Handshake (
agent_orchestration/pairing.rs): a same-machine handshake — the CLI writes{ agentId, owner, ts }to~/.openhuman/local-agents.jsonbefore it requests contact.auto_accept_linked_contact_requestsnow also accepts a pending requester whose id appears in that file under a fresh entry that names this OpenHuman as owner. Same-user filesystem access is the trust proof; the owner-match stops a CLI aimed at a different local OpenHuman from being cross-accepted, and a 1h TTL stops a departed agent's id lingering as auto-acceptable. Both trust sources stay fail-closed (missing/corrupt file, locked wallet, stale/undated entry → trust nothing). Id + owner matching reusesresolve_linked_id, so base64/base58 encodings unify.Attribution (
orchestration/ingest.rs): stamp an inbound Master DM role"peer"(not"user") so the transcript left-aligns it; our own outgoing Master message is persisted separately as"owner"and stays right.The CLI side that writes the handshake file is a companion PR in
tinyhumansai/tiny.place(#242).Submission Checklist
## Related— N/A: no matrix feature touchedCloses #NNN— N/A: bridge-epic work, no tracking issueImpact
tinyplace codex/claudeconnects to its OpenHuman on the same machine with zero manual pairing.Related
Companion CLI change: tinyhumansai/tiny.place#242 (writes
~/.openhuman/local-agents.json).AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
N/A — not a Linear/Codex PR.
Commit & Branch
Authored with Claude Code assistance. Branch
feat/orchestration-cli-connect; commits73c9d25(attribution) +589ce6d(handshake).Summary by CodeRabbit
New Features
Bug Fixes