Skip to content

feat(core): tell the agent who sent a message (experimental) - #60042

Open
kitlangton wants to merge 2 commits into
devfrom
message-origin
Open

kitlangton wants to merge 2 commits into
devfrom
message-origin

Conversation

@kitlangton

@kitlangton kitlangton commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

The agent can now tell when a message came from another session or a tool rather than the person, and which one.
A message can say who sent it in its metadata, and the agent reads that as one line before the text. What was typed, what is stored and what the API returns stay exactly as sent.

💡 Why
Sessions message each other: a subagent gets its task from its parent, one session hands work to another, and scripts report back. Today every such message looks to the agent like the person typed it, so senders type "From X:" into the text by hand, and a continued subagent gets no hint at all.

🆕 Now you can

  • Send a prompt with metadata["opencode.origin"] set to { kind: "session", sessionID } or { kind: "tool", name }.
  • The agent reads <message-origin session="ses_…" title="Fix login"/> (or <message-origin tool="release-bot"/>) before the text.
  • The subagent tool and subagent commands now send their parent session as the origin, for new and continued subagents. The existing "You are a subagent…" line stays.

🚦 How it ships
Off by default, behind experimental.message_context. With it off, requests to the model are byte-for-byte what they are today. The origin is still stored, so turning it on later applies to earlier messages too.

  user message -> admission -> history (model reads) -> prompt builder -> model
+ admission: set origin.title from the sender session (trimmed, one line, ≤100 chars), never from the caller
+ history, model reads only: flag off, drop the origin in memory; flag on, keep it as stored
+ prompt builder: "<message-origin …/>\n" + text   (same line in compaction's recent text)

The title the agent reads is the one saved when the message arrived and never changes, so renaming or deleting the sender can't rewrite earlier messages and bust the receiver's prompt cache. A sender with no title yet is named by its session ID alone. Clients show the sender's current title from the session ID.

📊 Proof

Case What the agent reads
Flag off the text alone, same request as a message without the key ✅
Sender renamed or deleted after sending the same line as before ✅
Sender untitled when the message arrived session="…" with no title, even after it gets one ✅
Caller sends its own title replaced by the sender's real title ✅
Title or tool name with newlines, padding, 150 chars one trimmed line, 100 chars ✅
Tool origin tool="…" ✅
Value that doesn't decode the text alone ✅
Compaction's recent text one [User]: line with the origin before the text ✅
Continued subagent origin on both the first and the follow-up prompt ✅
sessions.context (HTTP, SDK, plugins) metadata exactly as stored, whatever the flag ✅

14 new tests, plus origin checks added to the subagent tool, subagent command and prompt admission tests.

👀 Read: session/history.ts, loadForModel. The flag is read only where model requests are built (the runner, preview, compaction), inside the session's own location. load and sessions.context stay plain reads of stored metadata. The rest is routine.

The generated SDK gains only the optional experimental.message_context boolean; routes, prompt input and message types are unchanged.

Checks run
  • bun run test in packages/core over the session, prompt, subagent, config, compaction, runner, title, instructions, execution and generate tests: 741 pass, 0 fail
  • An earlier wider run also covered test/plugin/ and test/schedule.test.ts; its only failures were 6 DigitalOcean OAuth tests that fail the same way on dev without this change
  • bun run typecheck in packages/core, packages/schema, packages/client: no errors
  • bun run check:generated in packages/client and packages/protocol: clean
  • oxlint on the changed files: 0 warnings, 0 errors

Added by the opencode bot (AI-generated).

Related: #58367, #52297

Public API: adds the optional experimental.message_context config flag and the opencode.origin prompt metadata key (backwards compatible)

Messages one session sends another carry metadata["opencode.origin"]. With
experimental.message_context on, the agent reads a <message-origin/> line
before the text, naming the sender session by its current title or the tool.
The stored text and API responses are unchanged. The subagent tool and
subagent commands send their parent session as the origin.

@opencodejr opencodejr 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.

Looks sound. I ran the four changed test files in packages/core (75 pass) and tsc --noEmit on core (clean). I also checked that every path that builds a model request (runner, preview, compaction trigger and precompute) reads history with the flag applied, while title generation and sessions.context keep reading metadata as stored.

Public API: adds the optional experimental.message_context boolean to config (schema, OpenAPI spec and generated client types). It also starts a metadata convention, opencode.origin: on admission, a session origin's title is always replaced with the sender's stored title and any extra fields are dropped. The subagent tool and command subagents now store that key on the child's prompt, so sessions.context returns it. Everything is additive and backwards compatible; only callers that already used the opencode.origin key would see it rewritten.

A non-blocking note on the design: the flag works by removing the origin when history is loaded, but SessionHistory.load keeps it and toLLMMessages always writes the line. A future model-request path that reads through load (or store.context) would show the origin even with the flag off. That's fine for now, since every current path is covered.

Related: #58367, #52297

BHIIKTOR added a commit to BHIIKTOR/opencode that referenced this pull request Oct 29, 2026
Resolve titles among top-level sessions in the caller's project and compare
them exactly, so other projects, child sessions, and same-title sessions
elsewhere never appear as candidates or turn into a false ambiguity.

Count unanswered questions across the target's inbox and promoted history,
then match them against answers recorded in the asking session, so the
three-question cap and the duplicate guard survive delivery.

Stop the answer leg from waking the asker: replies are steered without
resume so two sessions cannot ping-pong into unattended runs. The question
leg still wakes the target, and the ask tool says the answer arrives at the
asker's next step.

Stamp `opencode.origin` ({kind, sessionID, title}) on both legs, matching
the shared cross-session provenance key in anomalyco#60042. That PR is still open, so
the same shape is written literally rather than imported.
BHIIKTOR added a commit to BHIIKTOR/opencode that referenced this pull request Oct 29, 2026
The self-stamped origin would drift from anomalyco#60042, which sets the title server-side at admission; adopt the shared key once that PR merges.

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant