Skip to content

feat(core): add session_list and session_read tools - #58445

Open
vimtor wants to merge 3 commits into
devfrom
session-read
Open

vimtor wants to merge 3 commits into
devfrom
session-read

Conversation

@vimtor

@vimtor vimtor commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

No issue.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds two read-only tools to the opencode Code Mode namespace so the model can find and reread earlier conversations, including the parts compaction summarized away. Compaction never deletes rows from session_message, so the full history is still there to read.

session_list({ search?, limit?, cursor? }) lists sessions across projects, most recently updated first, with each session's message count. With search, it matches session titles and the text of user and assistant messages (every word, case-insensitive; tool output and reasoning are not searched). Each matching message comes back with its index and a snippet, up to 3 per session.

{
  "sessions": [
    {
      "sessionID": "ses_…",
      "title": "Compaction rework",
      "directory": "/Users/me/project",
      "updated": "2026-10-21T14:37:36.000Z",
      "messages": 2287,
      "matches": [
        { "index": 141, "snippet": "We dropped chains of accepts because the mapping breaks when…" },
        { "index": 139, "snippet": "why not chains of accepts?" }
      ]
    }
  ]
}

session_read({ sessionID, offset?, limit?, tools? }) reads a page of messages by index, counting from 0, and returns the session's total. Without offset it reads the latest messages. A match's index can be passed as offset, or a little lower to include the surrounding discussion. tools: true adds tool calls and their output.

{
  "total": 2287,
  "messages": [
    { "index": 139, "role": "user", "text": "why not chains of accepts?" },
    { "index": 141, "role": "assistant", "text": "We dropped chains of accepts because the mapping breaks when…" }
  ]
}

Details:

  • Indexes are computed when reading (order by seq with offset) rather than taken from seq, which is the event sequence and is sparse. Reverts only delete the tail and forks copy the prefix, so indexes stay stable. Hidden messages such as agent switches still use an index, which the tool description mentions.
  • Message text is capped at 10,000 characters with a note of how much was cut. Tool input and output reuse the existing 1,250-character cap.
  • SessionStore gains page, counts, and search; Session exposes them, with page checking that the session exists.

How did you verify your code works?

  • Added a test in packages/core/test/tool-opencode.test.ts that seeds a session with sparse seq values and a compaction, then checks title and message search, that tool output isn't matched, reading from a match across the compaction, reading the latest page, tools: true, and an offset past the end.
  • bun run typecheck:tsc passes across the repo, and the core session and tool tests pass.
  • Timed the queries on a copy of a real database (about 20K messages, largest session 2,287 messages): reading a page or counting messages takes under 1 ms, and a search across all sessions takes about 0.4 s.

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Public API: adds the session_list and session_read tools to the opencode Code Mode namespace (backwards compatible)

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found nothing that should block this. I read the diff and ran the new test in packages/core/test/tool-opencode.test.ts, which calls both tools through Code Mode execute: it passes, and typecheck:tsc for packages/core reports no errors. Indexes from search count earlier messages by seq, the same order page reads in, so a match's index works as session_read's offset.

Public API: only additions, all backwards compatible. The model gets two new Code Mode tools, opencode.session_list and opencode.session_read. Inside core, Session/SessionStore gain page, counts and search. The HTTP API, SDK, plugin API and config are unchanged.

One point worth deciding on purpose: these are the first opencode tools that read conversation text from every project, and they ask for no permission. Prompt injection inside one repository could have the model pull history from unrelated projects. If that's intended, fine, but it should be a deliberate choice rather than a side effect. Also see the inline note about the current session matching its own search.

}
const search = input.search
const titled = (yield* sessions.list({ search, limit })).data
const found = yield* sessions.search({ query: search, limit: 200 })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calling session's own user prompt is saved before this runs. A prompt like "find where we decided against chains of accepts" will match itself, so the current session tends to come back first, with the prompt as its newest match (and it uses one of the 3 match slots). Leaving out the calling session (or at least its latest user message) would keep the results about earlier conversations, which is what the tool is for.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant