feat(core): replace instruction checkpoints with value-delta sync (#36254)

This commit is contained in:
Kit Langton 2026-07-10 13:26:25 -04:00 committed by GitHub
commit 96a9731947
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 2053 additions and 1278 deletions

View file

@ -8,9 +8,8 @@ integrations, references, skills, and tools; intercept model requests and tool
execution; and call a subset of the V2 client.
<Warning>
The V2 plugin API is beta. Entrypoints, hooks, draft shapes, and configuration
may change before the stable release. Use the `/v2` exports described on this
page.
The V2 plugin API is beta. Entrypoints, hooks, draft shapes, and configuration may change before the stable release.
Use the `/v2` exports described on this page.
</Warning>
## Load plugins
@ -34,10 +33,10 @@ Add ordered entries to the `plugins` field in `opencode.json(c)`:
"package": "./plugins/reviewer.ts",
"options": {
"agent": "reviewer",
"strict": true
}
}
]
"strict": true,
},
},
],
}
```
@ -80,12 +79,7 @@ applied in order:
```jsonc title="opencode.jsonc"
{
"plugins": [
"./plugins/reviewer.ts",
"-acme.reviewer",
"-opencode.provider.*",
"opencode.provider.openai"
]
"plugins": ["./plugins/reviewer.ts", "-acme.reviewer", "-opencode.provider.*", "opencode.provider.openai"],
}
```
@ -133,9 +127,7 @@ export default Plugin.define({
id: "acme.reviewer",
setup: async (ctx) => {
const description =
typeof ctx.options.description === "string"
? ctx.options.description
: "Reviews code for regressions"
typeof ctx.options.description === "string" ? ctx.options.description : "Reviews code for regressions"
await ctx.agent.transform((agents) => {
agents.update("reviewer", (agent) => {
@ -175,22 +167,22 @@ Its read and action methods use the same inputs and responses as the client. It
adds plugin-only methods for transforms, runtime hooks, reloads, registrations,
and plugin options.
| Capability | Available operations |
| --- | --- |
| `ctx.agent` | `list`, `transform`, `reload` |
| `ctx.catalog.provider` | `list`, `get` |
| `ctx.catalog.model` | `list`, `default` |
| `ctx.catalog` | `transform`, `reload` |
| `ctx.command` | `list`, `transform`, `reload` |
| `ctx.integration` | `list`, `get`, `connect`, `attempt`, `transform`, `reload`, and connection lookup/resolution |
| `ctx.plugin` | `list` currently active plugin IDs |
| `ctx.reference` | `list`, `transform`, `reload` |
| `ctx.session` | `create`, `get`, `prompt`, `command`, `interrupt`, and `hook` |
| `ctx.skill` | `list`, `transform`, `reload` |
| `ctx.tool` | `transform` and `hook` |
| `ctx.aisdk` | `hook` |
| `ctx.event` | `subscribe` to the current public server event stream |
| `ctx.options` | Readonly options from the matching config object |
| Capability | Available operations |
| ---------------------- | -------------------------------------------------------------------------------------------- |
| `ctx.agent` | `list`, `transform`, `reload` |
| `ctx.catalog.provider` | `list`, `get` |
| `ctx.catalog.model` | `list`, `default` |
| `ctx.catalog` | `transform`, `reload` |
| `ctx.command` | `list`, `transform`, `reload` |
| `ctx.integration` | `list`, `get`, `connect`, `attempt`, `transform`, `reload`, and connection lookup/resolution |
| `ctx.plugin` | `list` currently active plugin IDs |
| `ctx.reference` | `list`, `transform`, `reload` |
| `ctx.session` | `create`, `get`, `prompt`, `command`, `interrupt`, and `hook` |
| `ctx.skill` | `list`, `transform`, `reload` |
| `ctx.tool` | `transform` and `hook` |
| `ctx.aisdk` | `hook` |
| `ctx.event` | `subscribe` to the current public server event stream |
| `ctx.options` | Readonly options from the matching config object |
### Transform hooks
@ -198,15 +190,15 @@ Transform hooks let a plugin modify how OpenCode is configured. Use them to add
or remove definitions, override settings, choose defaults, and provide tools or
other sources.
| Transform | Draft operations |
| --- | --- |
| `agent.transform` | `list`, `get`, `default`, `update`, `remove` |
| `catalog.transform` | Provider `list`, `get`, `update`, `remove`; model `get`, `update`, `remove`; default model `get`, `set` |
| `command.transform` | `list`, `get`, `update`, `remove` |
| `integration.transform` | Integration `list`, `get`, `update`, `remove`; method `list`, `update`, `remove` |
| `reference.transform` | `add`, `remove`, `list` |
| `skill.transform` | `source`, `list` |
| `tool.transform` | `add` |
| Transform | Draft operations |
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
| `agent.transform` | `list`, `get`, `default`, `update`, `remove` |
| `catalog.transform` | Provider `list`, `get`, `update`, `remove`; model `get`, `update`, `remove`; default model `get`, `set` |
| `command.transform` | `list`, `get`, `update`, `remove` |
| `integration.transform` | Integration `list`, `get`, `update`, `remove`; method `list`, `update`, `remove` |
| `reference.transform` | `add`, `remove`, `list` |
| `skill.transform` | `source`, `list` |
| `tool.transform` | `add` |
Here's an example that keeps models synced from a remote source:
@ -250,13 +242,13 @@ without restarting OpenCode.
Runtime hooks intercept live operations. Their event objects expose specific
mutable fields:
| Hook | Mutable fields |
| --- | --- |
| `ctx.aisdk.hook("sdk", callback)` | `sdk`, after inspecting `model`, `package`, and `options` |
| `ctx.aisdk.hook("language", callback)` | `language`, after inspecting `model`, `sdk`, and `options` |
| `ctx.session.hook("request", callback)` | `system`, `messages`, and the `tools` record immediately before model dispatch |
| `ctx.tool.hook("execute.before", callback)` | `input`, before the selected tool executes |
| `ctx.tool.hook("execute.after", callback)` | `result`, `output`, and `outputPaths`, after execution settles |
| Hook | Mutable fields |
| ------------------------------------------- | ------------------------------------------------------------------------------ |
| `ctx.aisdk.hook("sdk", callback)` | `sdk`, after inspecting `model`, `package`, and `options` |
| `ctx.aisdk.hook("language", callback)` | `language`, after inspecting `model`, `sdk`, and `options` |
| `ctx.session.hook("request", callback)` | `system`, `messages`, and the `tools` record immediately before model dispatch |
| `ctx.tool.hook("execute.before", callback)` | `input`, before the selected tool executes |
| `ctx.tool.hook("execute.after", callback)` | `result`, `output`, and `outputPaths`, after execution settles |
For example, remove a tool from selected model requests and normalize another
tool's input:

View file

@ -114,22 +114,24 @@ and file or media attachments become textual descriptors rather than embedded
data. On later compactions, V2 updates the previous summary and carries forward
its retained recent context before selecting a new tail.
The completed checkpoint is presented to the model as historical conversation
The completed compaction is presented to the model as historical conversation
context, explicitly not as new instructions. Running and failed compactions are
not included in model context.
## Instructions and checkpoints
## Compaction advances the instruction epoch
Conversation compaction and the durable instruction checkpoint are separate.
Before each model step, V2 compares live instruction sources with what that
session's model was last told. Ordinary changes are durable chronological
system updates and do not rewrite the established instruction baseline.
Conversation compaction and instruction synchronization are separate. Before
promoting pending input, V2 compares live instruction sources with the latest
admitted values. Ordinary changes become durable value deltas; their
model-facing System messages are derived during request assembly rather than
persisted.
After a completed compaction, the next model step creates a fresh instruction
baseline from current sources. If a source is temporarily unavailable, V2
restates the last-applied value instead of treating it as removed. Session
movement and a committed revert reset the instruction checkpoint as well. See
[Instructions](/instructions) for source ordering and update behavior.
Completed compaction advances the instruction epoch at the exact ended-event
sequence and makes the currently admitted values initial. It does not reread
sources or publish an instruction event. Session movement and committed revert
clear the instruction fold so the next safe boundary requires one complete
source read. See [Instructions](/instructions) for source ordering and update
behavior.
## Current limitations

View file

@ -5,8 +5,9 @@ description: ""
Instructions are privileged context that guide an agent throughout a session.
V2 combines built-in context, discovered `AGENTS.md` files, and dynamic sources
such as skill, reference, MCP, and session context into a durable instruction
baseline.
such as skill, reference, MCP, and session context. It stores source values as
durable deltas, then renders initial instructions and chronological updates when
assembling each model request.
## AGENTS.md
@ -92,7 +93,7 @@ See [Config](/config) for config locations and general precedence.
## Ordering
The selected agent or provider system prompt is sent first. OpenCode then sends
the session's instruction baseline, composed in this order:
the session's initial instructions, composed in this order:
1. Built-in environment and date context.
2. Ambient `AGENTS.md` discovery.
@ -101,24 +102,27 @@ the session's instruction baseline, composed in this order:
These sources are combined; ordering is not an override mechanism. Nested
`AGENTS.md` files discovered by reads are chronological session entries rather
than part of the baseline.
than part of the initial instructions.
## Changes
Before each model step, V2 compares live instruction sources with what that
session's model was last told:
Before promoting pending input, V2 compares live instruction sources with the
latest admitted source values:
- A new or changed ambient `AGENTS.md` aggregate is announced as a system update
that replaces the previous ambient aggregate.
- Removing all ambient files announces that the previous ambient instructions
no longer apply.
- A temporary read or discovery failure preserves the session's last known
instructions instead of treating them as deleted. If no baseline exists yet,
the first model step waits until required sources are available.
- Completed conversation compaction creates a fresh baseline from the current
sources. Moving a session or committing a revert also resets its instruction
checkpoint so the next step establishes a new baseline.
instructions instead of treating them as deleted. If no instruction epoch
exists yet, pending input waits until every source is available.
- Completed conversation compaction advances the instruction epoch, making the
currently admitted values initial without rereading sources or authoring an
instruction event.
- Moving a session or committing a revert clears the instruction fold. The next
safe boundary requires one complete source read before promoting input.
Updates are durable session history. OpenCode does not rewrite the original
baseline on every change; it records the change so subsequent model steps see
both the established baseline and the chronological update.
The durable event stores changed source keys and value hashes, not rendered
prose. During request assembly, OpenCode renders the epoch's initial values and
interleaves later changes as chronological System messages. Clients see changed
keys but never the privileged value bodies.