Repository navigation
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before. Closest matches: [FEATURE]: Plugin API: bridge session revert, and expose server self-endpoint to plugins #51599 (server self-endpoint exposure, which would unblock this indirectly), [FEATURE]: Plugin API: read the session context size estimate #57216 (context size estimate), [FEATURE]: Select and reference another session's context in OpenCode V2 #53933 (cross-session context referencing). None of them requests read access to a session's message list from a plugin.
Describe the enhancement you want to request
In the v1 plugin API, a plugin could read a session transcript: the server() input carried a client with working client.session.messages(...) and client.session.get(...), plus serverUrl. Several published plugins rely on this (e.g. @gotgenes/opencode-session-context, @gotgenes/opencode-agent-identity build agent-attribution and session-reading tools on it).
In v2 (verified on 2.1.0 by probing a live setup(ctx) context), that capability is gone with no replacement:
ctx.sessionexposeshook, list, create, get, remove, switchAgent, switchModel, prompt, generate, command, compact, synthetic, interrupt, update, move, wait, context— no way to read messages.ctx.session.context()is not the transcript: afterctx.session.synthetic()added a message it still returned an empty array.- A two-level walk of the whole
ctxfor anything matching/message|history|transcript|part/ifinds zero hits. - Tool
execute(args, context)receives["sessionID","messageID","agent","progress","signal"]— no client either. - Unlike v1, the context exposes no
serverUrl/port/credential, so a plugin cannot even fall back to the HTTP API (GET /api/session/:id/messageexists and works) without out-of-band configuration — that half of the gap is [FEATURE]: Plugin API: bridge session revert, and expose server self-endpoint to plugins #51599.
Request: bridge session message reading into the plugin session domain — e.g. ctx.session.messages(sessionID) returning what the HTTP endpoint returns (messages with roles, agent/model attribution, parts). That restores v1 parity for transcript-reading plugins (attribution, session summarization, quota/retry tooling, cross-session context) and is read-only, so it adds no new mutation surface.
Workarounds considered and why they fall short:
ctx.event.subscribe()indexing: only sees messages after plugin load; cannot serve "fetch all messages of a session".- HTTP self-calls: no in-process endpoint/credential discovery (see [FEATURE]: Plugin API: bridge session revert, and expose server self-endpoint to plugins #51599);
~/.local/state/opencode/service.jsonis written only in service mode and is not reliably the plugin's own instance.
Activity
opencode-agent commentedon Oct 20, 2026
Thanks for the detailed write-up and for probing the live
setup(ctx)context!You're asking for a read-only way for v2 plugins to get a session's stored messages, e.g.
ctx.session.messages(sessionID), returning whatGET /api/session/:id/messagereturns, so transcript-reading plugins work again as they did with v1'sclient.session.messages.You're right that this can't be done as a plugin today. The public
ctx.sessiondomain doesn't includemessages, even though core already has a session messages read that the HTTP endpoint uses. Maintainers will consider it.Related issues:
session.messages) and the public plugin contexticeteaSA commentedon Nov 4, 2026
Data for this request, from a plugin that must find the last assistant message of a different session (the last answer of a subagent).
Today the only read is
ctx.session.context(sessionID). It loads and decodes the full model window. We measured the CPU time of the main thread on an actual 2.2.5 host with seeded sessions:context, warm medianTo find the last assistant message, we read the latest 64 messages, newest first. When that page does not give the answer, we read
contextas before. At 10,000 messages, this changed the CPU time for each call from 141.5 ms to 4.7 ms (−96.6%). We compared the result with the full read 104 times, and it was equal each time. These tests included compaction, part updates and plugin reloads. On a shared service with long sessions, the full reads cause freezes of 0.6 to 3.6 s each.A plugin cannot get the same result from a cache that events update. The events do not give an atomic watermark, so the plugin cannot know that it did not receive the last event. In our test, a cache that did not receive one last event gave the expired text.
The host has the necessary reads at this time:
Session.messageswithlimit,order,typeand an ID cursor (packages/core/src/session/store.ts:43-52,102-134at v2.2.5).store.ts:137-138,195-200).packages/server/src/handlers/message.ts:36-78).Only the plugin
SessionDomaindoes not include them (packages/plugin/src/promise/session.ts:201-220).Request (read-only):
ctx.session.messages({ sessionID, limit, order, cursor, type }), with the same codecs and limits as the HTTP route.ctx.session.message({ sessionID, messageID }).These do not give the plugin a service URL or a credential, and they add no write operation.