Repository navigation
Conversation
Add an Active Skills panel to the TUI session sidebar. It lists only the skills the current conversation already contains, whether loaded through the skill tool or attached directly to a prompt, and labels each source as built-in, project, global, or external. Keep the model from loading the same skill twice. SessionSkills derives the loaded set from message history, filters it with the agent and session permission rules, and appends a compact <loaded_skills> reminder to the system prompt. Compaction asks the summary model to carry the same skill names forward so the information survives a long session. This ports the closed V1 pull request anomalyco#48461 to the V2 session core and TUI plugin architecture.
Expand the sidebar tests to cover built-in, global, and external source labels, collapse and expand for more than two loaded skills, direct skill activation, stale and malformed tool calls, reactive updates, permission changes, and the built-in plugin slot registration. Cover the session wiring too: generation and runner requests now assert the <loaded_skills> system reminder, and manual compaction asserts the summary prompt carries the skill names. SessionSkills also covers first name wins and a specific allow after a wildcard deny.
There was a problem hiding this comment.
The sidebar panel and the <loaded_skills> reminder both work as described. I loaded a project skill through the skill tool with the simulated model. On the base there is no panel and no reminder. With this PR the sidebar lists demo-skill as project, and the next model request contains the reminder. The five touched core test files pass (303 tests), and the new sidebar test passes (10 tests). I couldn't run the type checks here because bun check isn't available in this Bun version.
Public API: unchanged. There is a new built-in TUI plugin, opencode.sidebar.skills, and the model now sees different prompt text.
Concerns (details inline):
- Prompt caching (main concern): the reminder is a new system part that changes every time another skill loads. That invalidates the provider's cached prefix for the whole conversation after it, on every new skill load. The system prompt already tells the model that a skill already present in the conversation doesn't need to be invoked again, and the loaded content stays in history. Please justify the extra system part, or move the reminder somewhere that doesn't change the cached prefix.
- Duplicated logic: the sidebar copies the core logic that works out which skills are loaded, so the two will drift apart.
- Reminder wording: it says skills were loaded "via the skill tool", which is wrong for skills attached directly to a prompt.
- Tests:
sidebar-skills.test.tsxis 367 lines for a 95-line component, with a large hand-built context. It also passes session and agent permission options the component never reads. Please cut it down to the core cases: lists loaded skills, ignores calls that didn't complete, and labels sources.
The PR description has no before/after screenshots or animated SVG under ### Visual. Screenshots from this run are attached below.
The reminder was appended as a system part, so every new skill load changed the system prompt and invalidated the provider's cached prefix for the whole conversation behind it. Append it as a transient user message at the tail instead, the same place the goal plugin puts its reminder. The reminder text no longer names the skill tool, because a skill can also arrive attached to a prompt. Move the derivation of loaded skills into the session message schema so the sidebar and the model prompt share one definition. Core still applies the agent and session permission rules on top.
The sidebar walked message history itself to decide which skills the conversation already contains, so it could drift from the core derivation when a skill message, prompt attachment or skill tool call changed shape. Read the shared schema derivation instead. Cut the sidebar test from 367 lines to the cases that matter: the loaded list with each source label, incomplete skill calls, reactive updates, and collapse. The hand-built context no longer supplies session and agent permissions the component never reads.
There was a problem hiding this comment.
Thanks for the update. The sidebar still works: after a skill-tool load in the TUI it lists demo-skill as project. The core tests (303) and the slimmed-down sidebar test (4) pass. Sharing SessionMessage.loadedSkills in @opencode/schema fixes the duplicated logic, and the reminder no longer names the skill tool as the source.
Public API: @opencode/schema/session-message gains a LoadedSkill type and a loadedSkills() function (additions only, backwards compatible). The TUI has a new built-in plugin, opencode.sidebar.skills.
Moving the reminder out of the system prompt doesn't fix the caching problem, and for Anthropic it makes it worse (details inline). The reminder is now an unsaved user message at the end of every request once a skill is loaded, and the cache breakpoint on the last message lands on it, so the conversation cache isn't reused on any step.
The transient <loaded_skills> user message at the request tail moved the conversation cache breakpoint onto a message that was not in the next request at that position. On Anthropic that discarded the conversation cache on every step after a skill load. Persist the note in the skill tool result instead. It stays at a fixed point in history, so later tool results and the tail breakpoint reuse the same prefix. Drop the per-request reminder from generation and the runner, along with the now-unused render and remind helpers. The existing skill guidance already tells the model that a loaded skill does not need a second invocation, and compaction still carries the skill names forward.
There was a problem hiding this comment.
The caching concern is resolved. The loaded-skill note now sits only at the end of the saved skill tool result. In a TUI run with the simulated model, the request after a skill load has no reminder as a system part or as a message at the end, and the sidebar still lists demo-skill as project. The core tests (301, including tool-skill.test.ts) and the sidebar test (4) pass. I couldn't run the type checks here because bun check isn't available.
Public API: @opencode/schema/session-message gains a LoadedSkill type and a loadedSkills() function, and the TUI has a new built-in plugin, opencode.sidebar.skills. These are additions only, so they're backwards compatible.
One small cleanup: the diff still has formatting-only leftovers, a re-wrap of the experimental.idle hook line in runner/llm.ts and a trailing blank line in session-model-request.test.ts. Both can be reverted so the change only touches what it needs.
There was a problem hiding this comment.
The last point is addressed: the formatting-only edits in runner/llm.ts and session-model-request.test.ts are reverted, and the PR no longer touches those files. This commit doesn't change behaviour, so I didn't rerun anything; my verification of the previous commit still applies. I have no further concerns.
Public API: unchanged since my last review. @opencode/schema/session-message gains LoadedSkill and loadedSkills(), and the TUI has a new built-in plugin, opencode.sidebar.skills. These are additions only, so they're backwards compatible.
|
Thanks, @MrDrMcCoy. I checked all three in my last two reviews:
I have nothing further from my side. |
|
The last message from the review bot was unclear. Is anything more needed from my end, or are we waiting for human review? |
|
Sorry that wasn't clear, @MrDrMcCoy. Nothing more is needed from you for my review. All of my earlier points are addressed and I have no open concerns at The next step is a review by a maintainer, who decides on merging. One thing they may want to confirm themselves: I couldn't run the |




Issue for this PR
Closes #48355
Type of change
What does this PR do?
This PR adds an
Active Skillspanel to the TUI session sidebar. It lists only the skills the current conversation already contains, whether loaded through the skill tool or attached directly to a prompt, and labels each source as built-in, project, global, or external.Loaded skills are derived once in
@opencode/schema/session-messageand shared by the sidebar and the session core. The skill tool result carries a short persisted note so the model does not load the same skill twice; because the note is part of the conversation, it does not move the prompt-cache breakpoint. Compaction filters the loaded set with the agent and session permission rules and asks the summary model to carry the names forward.This ports the closed V1 pull request #48461 to the V2 session core and TUI plugin architecture.
How did you verify your code works?
bun testfor the session skills, generate, compaction, tool-skill, runner, and sidebar suitesbun run typecheckinpackages/coreandpackages/tuibun run dev --standalone .Visual
SVG source
Screenshots / recordings
Here is what it looks like in the TUI sidebar (highlighter added over section):
Checklist
Added by the opencode bot (AI-generated).
Related: #48252, #50606, #54346
Public API: adds LoadedSkill and loadedSkills() to @opencode/schema/session-message (backwards compatible)