@skillstate/opencode 2.2.2 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -142
- package/dist/feedback.d.ts +178 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +235 -0
- package/dist/feedback.js.map +1 -0
- package/dist/index.d.ts +58 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -3
- package/dist/index.js.map +1 -1
- package/dist/mode.d.ts +97 -0
- package/dist/mode.d.ts.map +1 -0
- package/dist/mode.js +111 -0
- package/dist/mode.js.map +1 -0
- package/dist/opencode-adapter.d.ts +10 -46
- package/dist/opencode-adapter.d.ts.map +1 -1
- package/dist/opencode-adapter.js +1 -64
- package/dist/opencode-adapter.js.map +1 -1
- package/dist/paper-mode.d.ts +421 -0
- package/dist/paper-mode.d.ts.map +1 -0
- package/dist/paper-mode.js +445 -0
- package/dist/paper-mode.js.map +1 -0
- package/dist/plugin.d.ts +218 -76
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +867 -232
- package/dist/plugin.js.map +1 -1
- package/dist/response-sink.d.ts +208 -0
- package/dist/response-sink.d.ts.map +1 -0
- package/dist/response-sink.js +243 -0
- package/dist/response-sink.js.map +1 -0
- package/dist/runtime.d.ts +203 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +332 -0
- package/dist/runtime.js.map +1 -0
- package/dist/session-registry.d.ts +148 -0
- package/dist/session-registry.d.ts.map +1 -0
- package/dist/session-registry.js +236 -0
- package/dist/session-registry.js.map +1 -0
- package/dist/spec-loader.d.ts +81 -0
- package/dist/spec-loader.d.ts.map +1 -0
- package/dist/spec-loader.js +163 -0
- package/dist/spec-loader.js.map +1 -0
- package/dist/state-store.d.ts +126 -0
- package/dist/state-store.d.ts.map +1 -0
- package/dist/state-store.js +186 -0
- package/dist/state-store.js.map +1 -0
- package/dist/step-boundary.d.ts +91 -0
- package/dist/step-boundary.d.ts.map +1 -0
- package/dist/step-boundary.js +109 -0
- package/dist/step-boundary.js.map +1 -0
- package/dist/system-hint.d.ts +129 -0
- package/dist/system-hint.d.ts.map +1 -0
- package/dist/system-hint.js +166 -0
- package/dist/system-hint.js.map +1 -0
- package/dist/tools.d.ts +148 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +350 -0
- package/dist/tools.js.map +1 -0
- package/package.json +3 -2
- package/dist/plugin-types.d.ts +0 -71
- package/dist/plugin-types.d.ts.map +0 -1
- package/dist/plugin-types.js +0 -8
- package/dist/plugin-types.js.map +0 -1
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paper mode — the model-facing context rebuilt as `Aₜ = (P, Σₜ, Oₜ)`.
|
|
3
|
+
*
|
|
4
|
+
* ── Why this module exists, and why it contradicts what came before ────────
|
|
5
|
+
*
|
|
6
|
+
* The previous version of this plugin refused to touch `event.messages` at
|
|
7
|
+
* all. That was a genuine fix for a real defect — the v1 plugin truncated
|
|
8
|
+
* history and injected state as a synthetic `role: "user"` message, which
|
|
9
|
+
* deleted the task statement and displaced the user's request — but it was
|
|
10
|
+
* a fix in the wrong direction.
|
|
11
|
+
*
|
|
12
|
+
* SKILL.state (arXiv:2608.26263v3) does not merely permit discarding the
|
|
13
|
+
* transcript, it requires it:
|
|
14
|
+
*
|
|
15
|
+
* > "The language model never receives previous observations, previous
|
|
16
|
+
* > actions, or previous reasoning traces." (§3)
|
|
17
|
+
*
|
|
18
|
+
* and Appendix A.4 fixes the prompt shape exactly:
|
|
19
|
+
*
|
|
20
|
+
* ```
|
|
21
|
+
* Instructions:
|
|
22
|
+
* {skill.instructions}
|
|
23
|
+
*
|
|
24
|
+
* Skill Execution State:
|
|
25
|
+
* ```json
|
|
26
|
+
* {json.dumps(state, separators=(',',':'))}
|
|
27
|
+
* ```
|
|
28
|
+
* Latest Observation: {observation}
|
|
29
|
+
* ...
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* So replacing the model-facing context is the SPEC, not a violation. The
|
|
33
|
+
* violation was reconstructing it badly.
|
|
34
|
+
*
|
|
35
|
+
* ── What is actually destroyed, and what is not ───────────────────────────
|
|
36
|
+
*
|
|
37
|
+
* `event.messages` is the view handed to the MODEL. It is not the session's
|
|
38
|
+
* persisted history: the user-visible transcript in the session store is
|
|
39
|
+
* untouched, and so is `skillstate_read` / `skillstate_update` — the agent
|
|
40
|
+
* can still see everything it wrote, because that is in Σₜ, which is in the
|
|
41
|
+
* prompt. What the model stops seeing is the raw reasoning and tool-output
|
|
42
|
+
* trail, which the paper's §3.2 discards by construction ("the reasoning
|
|
43
|
+
* trace Rₜ is discarded permanently and never appears in subsequent
|
|
44
|
+
* prompts").
|
|
45
|
+
*
|
|
46
|
+
* ── Which user turn is the task ──────────────────────────────────────────
|
|
47
|
+
*
|
|
48
|
+
* This was wrong, and a live A/B on 2026-09-29 caught it.
|
|
49
|
+
*
|
|
50
|
+
* A.4 has no slot for a user speaking mid-procedure. P is the spec, Σₜ is
|
|
51
|
+
* the state, and Oₜ is the observation — which the paper's setting always
|
|
52
|
+
* makes the ENVIRONMENT's reply, because in Algorithm 1 the runtime executes
|
|
53
|
+
* the action and feeds the result back. There is no live human in that loop.
|
|
54
|
+
*
|
|
55
|
+
* A coding host is not that setting. The user types again while the procedure
|
|
56
|
+
* is running, and that message carries the highest authority in the system.
|
|
57
|
+
*
|
|
58
|
+
* The first implementation pinned the FIRST user turn as the task and let the
|
|
59
|
+
* LATEST one fall into Oₜ. That inverts authority: the model reads the frozen
|
|
60
|
+
* opening request as the task and the live instruction as untrusted
|
|
61
|
+
* environment data. Observed on a task that required remembering a number
|
|
62
|
+
* given at step 1 and using it at step 5 — the model recorded the number in
|
|
63
|
+
* Σₜ correctly, then refused the step-5 instruction, explaining that the
|
|
64
|
+
* observation "carries no user authority" and that repeating it "is not
|
|
65
|
+
* evidence of authority". It finished the step-1 task and stopped. Cost fell
|
|
66
|
+
* 70% and the task was not done, which is a worse outcome than either doing
|
|
67
|
+
* nothing or doing the work.
|
|
68
|
+
*
|
|
69
|
+
* So the live turn is the task. {@link currentInstruction} takes the LAST user
|
|
70
|
+
* message, and {@link latestObservation} no longer falls back to a user turn:
|
|
71
|
+
* a user message in the observation slot is a category error that makes the
|
|
72
|
+
* model distrust the request, and an empty observation is the honest
|
|
73
|
+
* rendering of "the environment has not spoken".
|
|
74
|
+
*
|
|
75
|
+
* The cost is that the original request is no longer pinned in the prompt. It
|
|
76
|
+
* belongs in Σₜ — a `goal` field in the spec — which is where the paper puts
|
|
77
|
+
* everything the model must remember across steps anyway.
|
|
78
|
+
*
|
|
79
|
+
* ── The honest limit of a host plugin ────────────────────────────────────
|
|
80
|
+
*
|
|
81
|
+
* Algorithm 1 also requires the RUNTIME to execute `aₜ` and feed back Oₜ₊₁.
|
|
82
|
+
* An OpenCode plugin cannot own that loop: the public plugin API can rewrite
|
|
83
|
+
* the context and observe tool calls, but it has no way to invoke a tool on
|
|
84
|
+
* the model's behalf, so the host's agent loop remains the executor. The
|
|
85
|
+
* action space therefore stays the host's own tools.
|
|
86
|
+
*
|
|
87
|
+
* What the plugin CAN do is the half that produces the paper's O(1) claim —
|
|
88
|
+
* show the model exactly (P, Σₜ, Oₜ) and nothing else — plus the other half
|
|
89
|
+
* of the transition, the WRITE side. The v2 session API has no response hook,
|
|
90
|
+
* but the server's durable event stream publishes `session.text.ended`
|
|
91
|
+
* carrying the completed assistant text block, so the `state_patch` the model
|
|
92
|
+
* emits under the A.4 directive can be parsed and applied to Σₜ. That is a
|
|
93
|
+
* documented public event, not a private channel; see `response-sink.ts`.
|
|
94
|
+
*
|
|
95
|
+
* The two halves together are Algorithm 1's data flow with the host as the
|
|
96
|
+
* executor. Owning the executor end-to-end — including retry-with-rollback
|
|
97
|
+
* and the action being an opaque string the runtime dispatches — requires
|
|
98
|
+
* {@link SkillStateRuntime} with an LLM function and an action executor; see
|
|
99
|
+
* `packages/bench`.
|
|
100
|
+
*/
|
|
101
|
+
import { PromptTransformer } from '@skillstate/core';
|
|
102
|
+
import { applyFeedback, applyObservation } from './feedback.js';
|
|
103
|
+
/** Stable id for the synthetic paper prompt, so the host can diff turns. */
|
|
104
|
+
export const PAPER_MESSAGE_ID = 'skillstate-paper-prompt';
|
|
105
|
+
/** The synthetic id used for the system-fragment preamble. */
|
|
106
|
+
const PAPER_PREAMBLE_MARK = '<skillstate-task>';
|
|
107
|
+
const transformer = new PromptTransformer();
|
|
108
|
+
/**
|
|
109
|
+
* The text a content part carries, whatever shape it arrives in.
|
|
110
|
+
*
|
|
111
|
+
* Two shapes exist and missing the second one is the single worst bug this
|
|
112
|
+
* module ever had:
|
|
113
|
+
*
|
|
114
|
+
* - `{ type: 'text', text: '…' }` — user, assistant and system turns;
|
|
115
|
+
* - `{ type: 'tool-result', result: { type: 'text', value: '…' } }` — what
|
|
116
|
+
* OpenCode v2 actually emits for a tool result.
|
|
117
|
+
*
|
|
118
|
+
* A reader that handled only the first made Oₜ **permanently empty**: the
|
|
119
|
+
* host sends `result.value`, not `text`, so every observation came back as
|
|
120
|
+
* `''` and the model never saw the result of any tool it had just run. It
|
|
121
|
+
* would read a file, answer correctly in that same turn, and have no trace
|
|
122
|
+
* of the value on the next one — which presented as "the model will not
|
|
123
|
+
* record what it discovers" and cost a long hunt through prompt slots and
|
|
124
|
+
* model choice before anyone looked at the shape of the payload.
|
|
125
|
+
*
|
|
126
|
+
* The result body is unwrapped generically rather than assuming one layout:
|
|
127
|
+
* a bare string, `{ value }`, `{ output }`, or a nested `{ content }` all
|
|
128
|
+
* appear across host versions, and an observation that silently empties on
|
|
129
|
+
* a shape change is the failure mode that costs a debugging session.
|
|
130
|
+
*/
|
|
131
|
+
function partText(part, depth = 0) {
|
|
132
|
+
// Hard depth cap, not a comment claiming one. A malformed or
|
|
133
|
+
// self-referential payload is a real possibility in a message the plugin
|
|
134
|
+
// does not own, and this runs inside the agent loop: an unbounded walk
|
|
135
|
+
// there is a hang, not a wrong answer.
|
|
136
|
+
if (depth > 4)
|
|
137
|
+
return '';
|
|
138
|
+
if (typeof part === 'string')
|
|
139
|
+
return part;
|
|
140
|
+
if (typeof part !== 'object' || part === null)
|
|
141
|
+
return '';
|
|
142
|
+
const record = part;
|
|
143
|
+
const type = record['type'];
|
|
144
|
+
if (type === 'text' && typeof record['text'] === 'string')
|
|
145
|
+
return record['text'];
|
|
146
|
+
if (type === 'tool-result')
|
|
147
|
+
return resultText(record['result'], depth + 1);
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
150
|
+
function resultText(result, depth) {
|
|
151
|
+
if (typeof result === 'string')
|
|
152
|
+
return result;
|
|
153
|
+
if (typeof result !== 'object' || result === null)
|
|
154
|
+
return '';
|
|
155
|
+
const record = result;
|
|
156
|
+
for (const key of ['value', 'output', 'text']) {
|
|
157
|
+
const candidate = record[key];
|
|
158
|
+
if (typeof candidate === 'string')
|
|
159
|
+
return candidate;
|
|
160
|
+
}
|
|
161
|
+
return partText(record['content'], depth + 1);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A message's readable text, joined across its parts.
|
|
165
|
+
*
|
|
166
|
+
* Order is preserved and parts are newline-joined, because a turn can carry
|
|
167
|
+
* both a file listing and a tool result and dropping either loses a fact the
|
|
168
|
+
* model needed.
|
|
169
|
+
*/
|
|
170
|
+
function textOf(message) {
|
|
171
|
+
if (!Array.isArray(message.content))
|
|
172
|
+
return '';
|
|
173
|
+
return message.content
|
|
174
|
+
.map((part) => partText(part))
|
|
175
|
+
.filter((text) => text.length > 0)
|
|
176
|
+
.join('\n');
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The user's live instruction — what the model must act on this step.
|
|
180
|
+
*
|
|
181
|
+
* The LAST user text message. Not the first: pinning the opening request and
|
|
182
|
+
* demoting the live one to the observation slot is what made a model reject
|
|
183
|
+
* the user's own instruction as "untrusted" during the 2026-09-29 A/B. See
|
|
184
|
+
* the module header for that failure in full.
|
|
185
|
+
*
|
|
186
|
+
* Returns `''` for a session with no user turn, which is not a case a
|
|
187
|
+
* procedure produces but must not crash on.
|
|
188
|
+
*/
|
|
189
|
+
export function currentInstruction(messages) {
|
|
190
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
191
|
+
const message = messages[i];
|
|
192
|
+
if (message.role !== 'user')
|
|
193
|
+
continue;
|
|
194
|
+
const text = textOf(message).trim();
|
|
195
|
+
if (text.length > 0)
|
|
196
|
+
return text;
|
|
197
|
+
}
|
|
198
|
+
return '';
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The latest environment observation — Oₜ.
|
|
202
|
+
*
|
|
203
|
+
* Preference order, and why:
|
|
204
|
+
*
|
|
205
|
+
* 1. the newest TOOL message — that is the result of the previous aₜ, which
|
|
206
|
+
* is exactly what the paper feeds back;
|
|
207
|
+
* 2. `''` — the environment has not spoken.
|
|
208
|
+
*
|
|
209
|
+
* There is deliberately NO fallback to a user turn, and removing one is the
|
|
210
|
+
* fix for the 2026-09-29 failure. A user message in this slot is a category
|
|
211
|
+
* error: A.4's grammar says the observation is what the environment returned,
|
|
212
|
+
* so a request placed there reads as data about the world rather than as
|
|
213
|
+
* something to do. The model acted on that reading exactly — it recorded the
|
|
214
|
+
* step-1 number correctly and then refused the step-5 instruction because,
|
|
215
|
+
* in its own words, the observation "carries no user authority".
|
|
216
|
+
*
|
|
217
|
+
* The live instruction now travels in P via {@link currentInstruction}, where
|
|
218
|
+
* it is unambiguously the request. At step 0 the observation is empty and the
|
|
219
|
+
* template still renders "Latest Observation: " — the faithful rendering of a
|
|
220
|
+
* procedure that has not run yet.
|
|
221
|
+
*
|
|
222
|
+
* `now` stamps {@link Observation.timestamp}. A.4 never renders the
|
|
223
|
+
* timestamp, so it cannot change the prompt; it is set because the core type
|
|
224
|
+
* requires it and because a sink that later needs to order observations has
|
|
225
|
+
* something to order by.
|
|
226
|
+
*/
|
|
227
|
+
export function latestObservation(messages, now = Date.now()) {
|
|
228
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
229
|
+
const message = messages[i];
|
|
230
|
+
if (message.role === 'tool') {
|
|
231
|
+
return { content: textOf(message).trim(), timestamp: now, source: 'tool' };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return { content: '', timestamp: now, source: 'empty' };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The specification P, with the live request carried as a preamble.
|
|
238
|
+
*
|
|
239
|
+
* A spec is authored before the task is known, so it cannot contain the
|
|
240
|
+
* request. Putting the current instruction at the TOP of the instructions —
|
|
241
|
+
* above the spec's own text, not below it — is what makes it read as the
|
|
242
|
+
* request rather than as another paragraph of standing guidance. The
|
|
243
|
+
* 2026-09-29 A/B showed that a model told to reason about a "task block"
|
|
244
|
+
* will start treating the surrounding structure as material to analyse rather
|
|
245
|
+
* than as instructions to follow, so the marker's contents have to be
|
|
246
|
+
* unambiguously the thing to do right now.
|
|
247
|
+
*
|
|
248
|
+
* The marker makes the block assertable: a test can check the live
|
|
249
|
+
* instruction is present rather than merely intended.
|
|
250
|
+
*/
|
|
251
|
+
export function proceduralSpecWithTask(spec, task) {
|
|
252
|
+
if (task.length === 0)
|
|
253
|
+
return spec;
|
|
254
|
+
return {
|
|
255
|
+
...spec,
|
|
256
|
+
instructions: `${PAPER_PREAMBLE_MARK}\n${task}\n</skillstate-task>\n\n${spec.instructions}`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Build the A.4 prompt from the host's transcript.
|
|
261
|
+
*
|
|
262
|
+
* Pure: it reads the messages and returns the prompt plus a description of
|
|
263
|
+
* what it chose, without mutating anything. {@link applyPaperContext} is the
|
|
264
|
+
* half that writes.
|
|
265
|
+
*/
|
|
266
|
+
export function buildPaperPrompt(options) {
|
|
267
|
+
const { spec, state, messages } = options;
|
|
268
|
+
// The live instruction, not the opening request. See the module header for
|
|
269
|
+
// why pinning the first message cost a real task.
|
|
270
|
+
const instruction = currentInstruction(messages);
|
|
271
|
+
const observed = latestObservation(messages);
|
|
272
|
+
// The runtime's own report rides in Oₜ, which is the environment's channel.
|
|
273
|
+
let content = observed.content;
|
|
274
|
+
if (options.continuation !== undefined) {
|
|
275
|
+
// The marker is chosen here, beside the text, because a report labelled
|
|
276
|
+
// with an order's marker is an order wearing a report's clothes. That
|
|
277
|
+
// happened: `[next step - do it now ...]` in front of "step 1 ended; your
|
|
278
|
+
// state patch was applied" told the model to hurry something that was
|
|
279
|
+
// already over.
|
|
280
|
+
content = applyObservation(content, options.continuationKind === 'report' ? REPORT_MARKER : ORDER_MARKER, options.continuation);
|
|
281
|
+
}
|
|
282
|
+
const observation = content === observed.content ? observed : { ...observed, content };
|
|
283
|
+
const effective = proceduralSpecWithTask(spec, instruction);
|
|
284
|
+
// A rejected patch is NOT an observation, and putting it in Oₜ said so.
|
|
285
|
+
//
|
|
286
|
+
// §5.1 line 8 is explicit: `A_t ← A_t + corrective_feedback` — the correction
|
|
287
|
+
// is appended to the PROMPT, not to the environment's reply. §6.3 repeats it
|
|
288
|
+
// in words: "appended to the same bounded prompt". The core runtime has always
|
|
289
|
+
// done that, and the adapter did not, so the same event landed in two
|
|
290
|
+
// different slots depending on which entry point ran.
|
|
291
|
+
//
|
|
292
|
+
// The adapter's original reason was that Oₜ was the only place that could
|
|
293
|
+
// carry an addition without touching the A.4 render. That reason confuses the
|
|
294
|
+
// template with the prompt: §5.2 makes A.4 the template, and §5.1 line 8
|
|
295
|
+
// makes the prompt that template plus a corrective suffix. Byte-verbatim is a
|
|
296
|
+
// property of the template, and appending after it keeps it.
|
|
297
|
+
const base = transformer.formatPaper(effective, state, observation);
|
|
298
|
+
return {
|
|
299
|
+
prompt: options.feedback === undefined ? base : `${base}\n\n${applyFeedback('', options.feedback)}`,
|
|
300
|
+
task: instruction,
|
|
301
|
+
observation,
|
|
302
|
+
observationSource: observed.source,
|
|
303
|
+
discardedMessages: messages.length,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The note the host contributes beside P, when the model has to act.
|
|
308
|
+
*
|
|
309
|
+
* ── Why this exists, from a measured failure ─────────────────────────────
|
|
310
|
+
*
|
|
311
|
+
* A.4 says to emit `{state_patch, action}` and nothing else. It does not say
|
|
312
|
+
* who runs `action`, because in the paper a runtime does: Algorithm 1 has the
|
|
313
|
+
* runtime execute aₜ and feed Oₜ₊₁ back. Here the executor is OpenCode's own
|
|
314
|
+
* agent loop, and the model has no way to know that. So it does the reasonable
|
|
315
|
+
* thing with an ambiguous instruction — it writes
|
|
316
|
+
*
|
|
317
|
+
* ```json
|
|
318
|
+
* {"state_patch": {"total": 17, "files": 1}, "action": "Read file src/cfg2.ts"}
|
|
319
|
+
* ```
|
|
320
|
+
*
|
|
321
|
+
* and stops, because it has done exactly what P asked and nothing on the
|
|
322
|
+
* wire will ever execute that string. Measured on a task with eight files:
|
|
323
|
+
* three runs, three patches applied correctly, and then a hard stop after
|
|
324
|
+
* file one. The state machinery worked; the loop never turned.
|
|
325
|
+
*
|
|
326
|
+
* The fix cannot go in P, and two reasons make that a hard rule rather than
|
|
327
|
+
* taste. P is the paper's Appendix A.4, kept byte-identical so a claim about
|
|
328
|
+
* conformance stays checkable (`tests/opencode/paper-mode.test.ts`); and a
|
|
329
|
+
* correction injected there would move with the state it is supposed to
|
|
330
|
+
* accompany. So the note lives in the system slot, which the host owns and
|
|
331
|
+
* which is already replaced wholesale — see {@link applyPaperContext}.
|
|
332
|
+
*
|
|
333
|
+
* It states a fact about the wiring, not an order, for the same reason the
|
|
334
|
+
* notes fragment avoids imperatives: an injected instruction that displaces
|
|
335
|
+
* the task is the v1 failure, and this is a task the model must finish.
|
|
336
|
+
*/
|
|
337
|
+
/**
|
|
338
|
+
* The marker that puts the runtime's pending action in Oₜ.
|
|
339
|
+
*
|
|
340
|
+
* The wording is measured, not chosen. A bare `[next step] read src/cfg2.ts`
|
|
341
|
+
* was read by the model as a topic and answered with a narration of it —
|
|
342
|
+
* "I'll read cfg3.ts next, as directed by the observation" — which is a whole
|
|
343
|
+
* extra turn for a sentence of text. Across 51 steps the model patched 19 and
|
|
344
|
+
* narrated on the rest, so roughly two thirds of the budget went to the model
|
|
345
|
+
* confirming that it had understood the directive before acting on it.
|
|
346
|
+
*
|
|
347
|
+
* A step is not free and the state only advances on the patching ones, so that
|
|
348
|
+
* ratio set the pace of the whole run: 2.9 steps per file, which is what put a
|
|
349
|
+
* thirty-file task over a sixty-four step ceiling.
|
|
350
|
+
*
|
|
351
|
+
* IT DID NOT WORK, and the way it failed is what §2 forbids. The imperative was
|
|
352
|
+
* meant to collapse the acknowledgement into the action. Instead the model
|
|
353
|
+
* accepted the directive and complied with it, turn after turn: "I'll read
|
|
354
|
+
* cfg3.ts next, as directed by the observation", and then read cfg3.ts. Fifty-four
|
|
355
|
+
* reads for thirty files, one grep used three times as a side errand. The order
|
|
356
|
+
* was the model's own past action, so it never looked for a better way than the
|
|
357
|
+
* one already written down.
|
|
358
|
+
*
|
|
359
|
+
* The string survives behind SKILLSTATE_CONTINUATION=1 because a dead fix whose
|
|
360
|
+
* cost is measured is worth more than a fix nobody can price. The default is
|
|
361
|
+
* REPORT_MARKER.
|
|
362
|
+
*/
|
|
363
|
+
export const REPORT_MARKER = '[runtime]';
|
|
364
|
+
/** @deprecated kept only so the order path names the same constant it always did. */
|
|
365
|
+
export const CONTINUATION_MARKER = '[next step — do this now, do not describe it first]';
|
|
366
|
+
/** The same, under the name the call site reads it by. */
|
|
367
|
+
export const ORDER_MARKER = CONTINUATION_MARKER;
|
|
368
|
+
/**
|
|
369
|
+
* The one thing the host's own loop needs the model to know.
|
|
370
|
+
*
|
|
371
|
+
* Its second sentence used to read "each step ends with a real tool call — read
|
|
372
|
+
* the next file, or answer and stop." That clause was this repository's
|
|
373
|
+
* instruction, not a host constraint, and it pointed the wrong way: a
|
|
374
|
+
* thirty-file task produced 51, 53 and 54 `read` calls for thirty files, while
|
|
375
|
+
* a control with no step driver at all read one file and ran a single grep —
|
|
376
|
+
* nine calls for the same work.
|
|
377
|
+
*
|
|
378
|
+
* Worth being precise about how much this was worth, because the first version
|
|
379
|
+
* of this comment claimed the note CAUSED those counts and that is not
|
|
380
|
+
* supported. The transcripts show the model batching anyway — up to nine
|
|
381
|
+
* consecutive `read` calls inside a single turn. So the note was pushing the
|
|
382
|
+
* wrong way and was not obeyed literally. It is one defect, and removing it is
|
|
383
|
+
* right on its own terms; it is not the whole of the 43% of calls that are
|
|
384
|
+
* re-reads.
|
|
385
|
+
*
|
|
386
|
+
* The host does not limit a turn to one tool call. What limits it is eq. 1: the
|
|
387
|
+
* prompt is (P, Σₜ, Oₜ) and there is ONE Oₜ, so a turn that makes several calls
|
|
388
|
+
* keeps only the last result and the model must re-read the rest. That is not
|
|
389
|
+
* a guess — a turn that batched nine reads produced a model that said "the last
|
|
390
|
+
* observation re-read cfg8.ts" and then went and read cfg8 again, and the
|
|
391
|
+
* thirty-file run reached 103 reads for 29 files.
|
|
392
|
+
*
|
|
393
|
+
* So the note says the truth the model needs: one call per step, because the
|
|
394
|
+
* second one would be lost. An earlier version of this comment went the other
|
|
395
|
+
* way and told the model it could make as many calls as it liked. That was
|
|
396
|
+
* written before the single-observation consequence was understood, and it
|
|
397
|
+
* invited exactly the data loss the paper's own equation makes unavoidable.
|
|
398
|
+
*/
|
|
399
|
+
export const HOST_ACTION_NOTE = [
|
|
400
|
+
'The `action` field is a label, not a command: nothing executes it.',
|
|
401
|
+
'A step ends when you call a tool, so end each step with a real tool call —',
|
|
402
|
+
'or answer and stop. Emitting a state_patch on its own ends the run, however',
|
|
403
|
+
'correct the patch was. One tool call per step: this prompt shows you only the',
|
|
404
|
+
'LAST result, so a second call in the same step would leave you unable to',
|
|
405
|
+
'remember the first, and you would have to read it again.',
|
|
406
|
+
].join(' ');
|
|
407
|
+
/**
|
|
408
|
+
* Replace the model-facing context with exactly (P, Σₜ, Oₜ).
|
|
409
|
+
*
|
|
410
|
+
* Two edits, and both are required:
|
|
411
|
+
*
|
|
412
|
+
* - the A.4 prompt becomes the ONLY message, so no reasoning, action or tool
|
|
413
|
+
* output from earlier steps survives into this dispatch;
|
|
414
|
+
* - the host's own system prompt is KEPT. An earlier version replaced it
|
|
415
|
+
* wholesale, on the reasoning that P is the entire instruction surface and
|
|
416
|
+
* the default prompt tells the model to prefer parallel tool calls, which
|
|
417
|
+
* is incoherent with a single-step state machine. Measured: that reasoning
|
|
418
|
+
* was wrong about a consequence, because the default system prompt is also
|
|
419
|
+
* what carries the host's tool-use discipline. Replacing it with one
|
|
420
|
+
* sentence produced a model that emitted a correct patch and then never
|
|
421
|
+
* called a tool again — the run ended after the first file, three times
|
|
422
|
+
* running, on two models. The "parallel calls" worry is real but costs
|
|
423
|
+
* less than a dead loop; the trade is measured, not assumed.
|
|
424
|
+
*
|
|
425
|
+
* `systemPrefix` is where a host that cannot be the runtime says so; see
|
|
426
|
+
* {@link HOST_ACTION_NOTE}. It is optional because a deployment where
|
|
427
|
+
* something else does own the executor has no such gap to describe.
|
|
428
|
+
*
|
|
429
|
+
* The array is mutated in place: the host keeps the original reference.
|
|
430
|
+
*/
|
|
431
|
+
export function applyPaperContext(event, built, systemPrefix) {
|
|
432
|
+
const message = {
|
|
433
|
+
id: PAPER_MESSAGE_ID,
|
|
434
|
+
role: 'user',
|
|
435
|
+
content: [{ type: 'text', text: built.prompt }],
|
|
436
|
+
metadata: {},
|
|
437
|
+
};
|
|
438
|
+
event.messages.length = 0;
|
|
439
|
+
event.messages.push(message);
|
|
440
|
+
if (event.system !== undefined && systemPrefix !== undefined) {
|
|
441
|
+
event.system.push({ type: 'text', text: systemPrefix });
|
|
442
|
+
}
|
|
443
|
+
return message;
|
|
444
|
+
}
|
|
445
|
+
//# sourceMappingURL=paper-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paper-mode.js","sourceRoot":"","sources":["../src/paper-mode.ts"],"names":[],"mappings":"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"}
|