Repository navigation
Description
Related issues
- [FEATURE]: Add explicit hidden or ephemeral sessions for plugin background tasks #40863 — the same plugin use case, proposing
hidden/ephemeralfields. It was auto-closed after 60 days of inactivity; the close comment is a bot, not a maintainer decision. - Support session visibility independently of parentage #39911 — session visibility independent of parentage (
hidden). - [FEATURE]: Add an ephemeral mode that does not persist sessions locally #53921 — an ephemeral mode that does not persist sessions locally.
- [FEATURE]: Ephemeral one‑off sessions for opencode run (happy to implement) #4489 — ephemeral
opencode run(closed; PR feat(opencode): OpenCode ephemeral sessions #43656 closed unmerged).
The problem
Plugins often need to run a short, tool-using agent turn as an internal step: consolidate memory, review a diff, classify files, etc. The V2 plugin API has no way to do that without creating a normal, persisted session.
ctx.generate.text is not enough. The docs describe it as generating text "without creating a session, invoking tools, or adding to session history". A tool loop needs a session.
The only workaround is a child session:
const child = await ctx.session.create({ parentID, agent, model })
try {
await ctx.session.prompt({ sessionID: child.id, text })
await ctx.session.wait({ sessionID: child.id })
const messages = await ctx.session.context({ sessionID: child.id })
} finally {
await ctx.session.remove({ sessionID: child.id })
}This is not a temporary session:
parentIDis hierarchy, not visibility. The child is persisted from creation and appears in unfilteredGET /api/session/ctx.session.list, and in any client that does not filter byparentID.- Nothing guarantees cleanup. If the plugin is unloaded, crashes, or the server exits mid-run, the session and its messages, tool output and snapshots remain.
removeonly runs if the plugin reachesfinally. - Parentage and visibility are conflated. A background controller may be top-level; a child may need to be visible.
- The write happens before plugin hooks, so a hook cannot prevent persistence.
Proposed API
Add two independent properties to ctx.session.create (and POST /api/session):
const temp = await ctx.session.create({
title: "memory-consolidation",
agent: "build",
hidden: true, // keep it out of user-facing pickers/history
ephemeral: true, // do not leave a durable session/message record
})
await ctx.session.prompt({ sessionID: temp.id, text: "..." })
await ctx.session.wait({ sessionID: temp.id })
const messages = await ctx.session.context({ sessionID: temp.id })Semantics
hidden: true— excluded from user-facing session lists, pickers and live session events by default; addressable by ID; list APIs acceptincludeHidden; persistence unchanged. This is the shape proposed in Support session visibility independently of parentage #39911.ephemeral: true— the session exists only for its lifetime. Tools, permissions,prompt,wait,contextandinterruptwork normally, but the server owns cleanup: on completion, cancellation, a TTL, plugin unload and process exit. After that there is no recoverable session or message row. Session snapshots and tool artifacts are cleaned up with it. Implieshidden.parentIDremains independent: an ephemeral/hidden session can be top-level; a child can be visible.- Optional convenience for the common one-shot case:
ctx.session.run({ prompt })returning the final assistant text, implemented on top of an ephemeral session. Not required if the create properties exist.
Observability: ephemeral does not have to mean "no logs". Server logs, traces and usage accounting can still record the run; the request is about not polluting user session history and not leaving a recoverable conversation.
Acceptance criteria
- A plugin can create a session, run a prompt that calls tools,
wait, read the result, and leave no user-facing session inGET /api/sessionorctx.session.list. - Cleanup happens on completion, cancellation, plugin unload/crash and server exit, not only when the plugin reaches
finally. hiddensessions are excluded from the TUI/app root session lists but remain readable by ID.hidden/ephemeralare independent ofparentID.- Normal sessions behave exactly as today.
Environment
OpenCode 2.1.0, @opencode/plugin 2.1.2.
Activity
opencode-agent commentedon Oct 22, 2026
Thanks for the detailed write-up, @mossgowild!
To restate it: you'd like plugins to be able to create sessions that run a full tool loop (
prompt/wait/context) but are kept out of user-facing session lists (hidden). You'd also like those sessions to be cleaned up by the server, not by the plugin (ephemeral), with both settings separate fromparentID.I checked the current code.
ctx.session.create/POST /api/sessionhas no visibility or lifetime field today, so the child-session plusremoveinfinallyapproach is the only option. As you describe, that session is persisted and listed while it runs, and it is left behind if the plugin or server goes away mid-run. A plugin can't work around this alone; it needs support in the session API. Maintainers will consider it.Related issues:
hiddenhalf), still open.opencode run(closed).