Skip to content

Commit 16196e3

Browse files
Ankur ChakrabortyAnkur Chakraborty
authored andcommitted
fix(chain): stop auto-injecting content-type skills; fix(scrollbox): conditional stickyScroll
- Split chain execution results into script vs content types - Content-type skills (SKILL.md) no longer auto-injected as <script-result> blocks — agent MUST load them via the skill tool - Chain obligation now shows [MUST-LOAD] vs [EXECUTED] labels for each skill - Added <pending-skill-load> warning for unloaded content skills - Subagent scrollbox stickyScroll now only active while status is 'running', allowing click-to-scroll after completion/cancellation/error
1 parent ac20ac7 commit 16196e3

2 files changed

Lines changed: 42 additions & 11 deletions

File tree

‎packages/opencode/src/cli/cmd/run/footer.subagent.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export function RunFooterSubagentBody(props: {
156156
<scrollbox
157157
width="100%"
158158
height="100%"
159-
stickyScroll={true}
159+
stickyScroll={tab()?.status === "running"}
160160
stickyStart="bottom"
161161
verticalScrollbarOptions={scrollbar()}
162162
ref={(item) => {

‎packages/opencode/src/session/prompt.ts‎

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,15 +1208,32 @@ export const layer = Layer.effect(
12081208
return Effect.succeed([] as ChainResult[])
12091209
}),
12101210
)
1211-
for (const result of chainResults) {
1211+
// Split chain results: "script" type = pre-executed automation output;
1212+
// "content" type = raw SKILL.md (NOT injected — agent must load via `skill` tool).
1213+
const scriptResults = chainResults.filter(r => r.executionType === "script")
1214+
const contentResults = chainResults.filter(r => r.executionType === "content")
1215+
for (const result of scriptResults) {
12121216
if (result.status === "ok" && result.output) {
12131217
system.push(`\n<script-result name="${sanitizeForSystemPrompt(result.name)}">\n${sanitizeForSystemPrompt(result.output.slice(0, 5000))}\n</script-result>`)
1214-
} else if (result.status === "not_found") {
1215-
system.push(`\n<skill-missing name="${sanitizeForSystemPrompt(result.name)}"/>`)
12161218
} else {
12171219
system.push(`\n<script-result name="${sanitizeForSystemPrompt(result.name)}" status="error">\n${sanitizeForSystemPrompt(result.output.slice(0, 2000))}\n</script-result>`)
12181220
}
12191221
}
1222+
// Content-type skills are NOT auto-injected — the agent must load them
1223+
// via the `skill` tool to receive SKILL.md workflow instructions.
1224+
if (contentResults.length > 0) {
1225+
const pending = contentResults.map(r => r.name)
1226+
system.push(
1227+
`\n<pending-skill-load requirement="mandatory">` +
1228+
`These skills have NOT been pre-loaded — you MUST load them via the \`skill\` tool: ` +
1229+
`${sanitizeForSystemPrompt(pending.join(", "))}</pending-skill-load>`,
1230+
)
1231+
}
1232+
for (const result of chainResults) {
1233+
if (result.status === "not_found") {
1234+
system.push(`\n<skill-missing name="${sanitizeForSystemPrompt(result.name)}"/>`)
1235+
}
1236+
}
12201237

12211238
// ─── Chain Enforcement: Sensor Gate Graph Must Be Followed ──
12221239
// The sensor gate classified the prompt and produced a skill chain.
@@ -1328,20 +1345,34 @@ export const layer = Layer.effect(
13281345
// follow it by loading each skill via the `skill` tool. Pre-injected
13291346
// <script-result> blocks contain automation output but NOT the skill's
13301347
// workflow instructions — those come from loading SKILL.md via the tool.
1348+
// Skills classified as "content" type are NOT auto-injected at all;
1349+
// the model MUST load them via the `skill` tool to receive instructions.
1350+
const scriptSkillNames = scriptResults.map(r => r.name)
1351+
const contentSkillNames = contentResults.map(r => r.name)
1352+
const chainItems = gateResult.chain.map((name: string) => {
1353+
if (contentSkillNames.includes(name)) {
1354+
return ` [MUST-LOAD] Call \`skill\` with name="${sanitizeForSystemPrompt(name)}" — content NOT injected, REQUIRED to load via tool`
1355+
}
1356+
if (scriptSkillNames.includes(name)) {
1357+
return ` [EXECUTED] Call \`skill\` with name="${sanitizeForSystemPrompt(name)}" — script pre-executed, load SKILL.md for workflow instructions`
1358+
}
1359+
return ` [MUST-LOAD] Call \`skill\` with name="${sanitizeForSystemPrompt(name)}" — REQUIRED to load via tool`
1360+
})
13311361
system.push([
13321362
"",
13331363
"<skill-chain-obligation mode=\"strict\">",
13341364
"The sensor gate has classified this prompt and produced a mandatory skill chain.",
1335-
"You MUST load EVERY skill in this chain using the `skill` tool:",
1365+
"Every skill in this chain MUST be loaded via the `skill` tool:",
13361366
"",
1337-
...gateResult.chain.map((name: string) => ` [MANDATORY] Call \`skill\` with name="${sanitizeForSystemPrompt(name)}" — load content and follow instructions`),
1367+
...chainItems,
13381368
"",
13391369
"Rules:",
1340-
" 1. Load each skill's content via the `skill` tool — do NOT skip any",
1341-
" 2. Execute the skill's workflow instructions and scripts",
1342-
" 3. Any subagent you spawn MUST also load the same chain skills",
1343-
" 4. After ALL chain skills are loaded, acknowledge with: [SKILLS LOADED]",
1344-
" 5. FAILURE to load all chain skills will produce INCOMPLETE results",
1370+
" 1. [MUST-LOAD] skills were NOT pre-injected — you MUST call the `skill` tool to load their content",
1371+
" 2. [EXECUTED] skills had their automation script pre-run; load SKILL.md via `skill` tool for workflow instructions",
1372+
" 3. Follow each skill's workflow instructions after loading",
1373+
" 4. Any subagent you spawn MUST also load the same chain skills",
1374+
" 5. After ALL chain skills are loaded, acknowledge with: [SKILLS LOADED]",
1375+
" 6. FAILURE to load all chain skills will produce INCOMPLETE results",
13451376
"</skill-chain-obligation>",
13461377
].join("\n"))
13471378

0 commit comments

Comments
 (0)