From fbacffae447bdda6838ca2d76250678762f1e9fe Mon Sep 17 00:00:00 2001
From: ops
Date: Wed, 31 Dec 2025 23:46:11 +0100
Subject: [PATCH 1/3] (feat) customizable subagent config and system env prompt
---
packages/opencode/src/session/compaction.ts | 3 ++-
packages/opencode/src/session/llm.ts | 5 +++-
packages/opencode/src/session/prompt.ts | 3 ++-
packages/opencode/src/session/summary.ts | 25 +++++++++++++++++--
packages/opencode/src/session/system.ts | 27 ++++++++++++++++-----
packages/opencode/src/tool/task.ts | 10 +++++---
6 files changed, 59 insertions(+), 14 deletions(-)
diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts
index fb382530291..5095a8bb768 100644
--- a/packages/opencode/src/session/compaction.ts
+++ b/packages/opencode/src/session/compaction.ts
@@ -97,7 +97,8 @@ export namespace SessionCompaction {
auto: boolean
}) {
const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User
- const agent = await Agent.get("compaction")
+ const msgAgent = await Agent.get(userMessage.agent)
+ const agent = await Agent.get(msgAgent.options.agents?.compaction ?? "compaction")
const model = agent.model
? await Provider.getModel(agent.model.providerID, agent.model.modelID)
: await Provider.getModel(userMessage.model.providerID, userMessage.model.modelID)
diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts
index d651308032e..4e6b58801d8 100644
--- a/packages/opencode/src/session/llm.ts
+++ b/packages/opencode/src/session/llm.ts
@@ -67,13 +67,16 @@ export namespace LLM {
const isCodex = provider.id === "openai" && auth?.type === "oauth"
const system = []
+ const customSystem = input.agent.options.system
+ ? await SystemPrompt.environment(input.model, input.agent.options.system)
+ : input.system
system.push(
[
// use agent prompt otherwise provider prompt
// For Codex sessions, skip SystemPrompt.provider() since it's sent via options.instructions
...(input.agent.prompt ? [input.agent.prompt] : isCodex ? [] : SystemPrompt.provider(input.model)),
// any custom prompt passed into this call
- ...input.system,
+ ...customSystem,
// any custom prompt from last user message
...(input.user.system ? [input.user.system] : []),
]
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 23ca473541c..b408c5ad817 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -1773,7 +1773,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the
const subtaskParts = firstRealUser.parts.filter((p) => p.type === "subtask") as MessageV2.SubtaskPart[]
const hasOnlySubtaskParts = subtaskParts.length > 0 && firstRealUser.parts.every((p) => p.type === "subtask")
- const agent = await Agent.get("title")
+ const msgAgent = await Agent.get(firstRealUser.info.agent)
+ const agent = await Agent.get(msgAgent.options.agents?.title ?? "title")
if (!agent) return
const model = await iife(async () => {
if (agent.model) return await Provider.getModel(agent.model.providerID, agent.model.modelID)
diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts
index 91a520a9bdf..2c5a9077216 100644
--- a/packages/opencode/src/session/summary.ts
+++ b/packages/opencode/src/session/summary.ts
@@ -132,8 +132,22 @@ export namespace SessionSummary {
const textPart = msgWithParts.parts.find((p) => p.type === "text" && !p.synthetic) as MessageV2.TextPart
if (textPart && !userMsg.summary?.title) {
- const agent = await Agent.get("title")
- if (!agent) return
+ const msgAgent = await Agent.get(userMsg.agent)
+ const titleAgent = msgAgent.options.agents?.title ?? "title"
+ if (titleAgent === "none") {
+ const title = textPart.text.length > 50 ? textPart.text.slice(0, 50) + "..." : textPart.text
+ userMsg.summary.title = title
+ await Session.updateMessage(userMsg)
+ await Session.update(
+ userMsg.sessionID,
+ (draft) => {
+ draft.title = title
+ },
+ { touch: false },
+ )
+ return
+ }
+ const agent = await Agent.get(titleAgent)
const stream = await LLM.stream({
agent,
user: userMsg,
@@ -163,6 +177,13 @@ export namespace SessionSummary {
log.info("title", { title: result })
userMsg.summary.title = result
await Session.updateMessage(userMsg)
+ await Session.update(
+ userMsg.sessionID,
+ (draft) => {
+ draft.title = result
+ },
+ { touch: false },
+ )
}
}
diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts
index d34a086fe44..d84fc590450 100644
--- a/packages/opencode/src/session/system.ts
+++ b/packages/opencode/src/session/system.ts
@@ -24,11 +24,20 @@ export namespace SystemPrompt {
return [PROMPT_ANTHROPIC_WITHOUT_TODO]
}
- export async function environment(model: Provider.Model) {
+ export async function environment(
+ model: Provider.Model,
+ options?: { model_id?: boolean; env?: boolean; files?: boolean },
+ ) {
const project = Instance.project
- return [
- [
+ const parts: string[] = []
+
+ if (options?.model_id !== false)
+ parts.push(
`You are powered by the model named ${model.api.id}. The exact model ID is ${model.providerID}/${model.api.id}`,
+ )
+
+ if (options?.env !== false)
+ parts.push(
`Here is some useful information about the environment you are running in:`,
``,
` Working directory: ${Instance.directory}`,
@@ -36,9 +45,14 @@ export namespace SystemPrompt {
` Platform: ${process.platform}`,
` Today's date: ${new Date().toDateString()}`,
``,
+ )
+
+ // files info was disabled during permission rework
+ if (options?.files !== false && false)
+ parts.push(
``,
` ${
- project.vcs === "git" && false
+ project.vcs === "git"
? await Ripgrep.tree({
cwd: Instance.directory,
limit: 200,
@@ -46,7 +60,8 @@ export namespace SystemPrompt {
: ""
}`,
``,
- ].join("\n"),
- ]
+ )
+
+ return [parts.join("\n")]
}
}
diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts
index c87add638aa..afdbf9e2dd4 100644
--- a/packages/opencode/src/tool/task.ts
+++ b/packages/opencode/src/tool/task.ts
@@ -41,20 +41,24 @@ export const TaskTool = Tool.define("task", async (ctx) => {
async execute(params: z.infer, ctx) {
const config = await Config.get()
+ // Resolve subagent type through calling agent's mapping
+ const callingAgent = ctx.agent ? await Agent.get(ctx.agent) : undefined
+ const resolvedSubagentType = callingAgent?.options.agents?.[params.subagent_type] ?? params.subagent_type
+
// Skip permission check when user explicitly invoked via @ or command subtask
if (!ctx.extra?.bypassAgentCheck) {
await ctx.ask({
permission: "task",
- patterns: [params.subagent_type],
+ patterns: [resolvedSubagentType],
always: ["*"],
metadata: {
description: params.description,
- subagent_type: params.subagent_type,
+ subagent_type: resolvedSubagentType,
},
})
}
- const agent = await Agent.get(params.subagent_type)
+ const agent = await Agent.get(resolvedSubagentType)
if (!agent) throw new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`)
const hasTaskPermission = agent.permission.some((rule) => rule.permission === "task")
From 3adcdc8a10199d18d020828beb5865d13a75bb8f Mon Sep 17 00:00:00 2001
From: ops
Date: Wed, 28 Jan 2026 09:00:23 +0100
Subject: [PATCH 2/3] gpt subagent fix
---
packages/opencode/src/session/llm.ts | 4 +---
packages/opencode/src/session/prompt.ts | 21 +++++++++++++++++++--
packages/opencode/src/session/summary.ts | 14 --------------
packages/opencode/src/session/system.ts | 3 +--
4 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts
index 4e6b58801d8..45aadc53000 100644
--- a/packages/opencode/src/session/llm.ts
+++ b/packages/opencode/src/session/llm.ts
@@ -67,9 +67,7 @@ export namespace LLM {
const isCodex = provider.id === "openai" && auth?.type === "oauth"
const system = []
- const customSystem = input.agent.options.system
- ? await SystemPrompt.environment(input.model, input.agent.options.system)
- : input.system
+ const customSystem = input.system
system.push(
[
// use agent prompt otherwise provider prompt
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index b408c5ad817..ca547b792bc 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -945,7 +945,7 @@ export namespace SessionPrompt {
]
}
break
- case "file:":
+ case "file:": {
log.info("file", { mime: part.mime })
// have to normalize, symbol search returns absolute paths
// Decode the pathname since URL constructor doesn't automatically decode it
@@ -1128,6 +1128,7 @@ export namespace SessionPrompt {
source: part.source,
},
]
+ }
}
}
@@ -1774,7 +1775,23 @@ NOTE: At any point in time through this workflow you should feel free to ask the
const hasOnlySubtaskParts = subtaskParts.length > 0 && firstRealUser.parts.every((p) => p.type === "subtask")
const msgAgent = await Agent.get(firstRealUser.info.agent)
- const agent = await Agent.get(msgAgent.options.agents?.title ?? "title")
+ const titleAgent = msgAgent.options.agents?.title ?? "title"
+ if (titleAgent === "none") {
+ const textPart = firstRealUser.parts.find((part) => part.type === "text" && !part.synthetic) as
+ | MessageV2.TextPart
+ | undefined
+ const text = textPart?.text?.trim()
+ if (!text) return
+ const title = text.length > 50 ? text.slice(0, 50) + "..." : text
+ return Session.update(
+ input.session.id,
+ (draft) => {
+ draft.title = title
+ },
+ { touch: false },
+ )
+ }
+ const agent = await Agent.get(titleAgent)
if (!agent) return
const model = await iife(async () => {
if (agent.model) return await Provider.getModel(agent.model.providerID, agent.model.modelID)
diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts
index 2c5a9077216..2932d9154b4 100644
--- a/packages/opencode/src/session/summary.ts
+++ b/packages/opencode/src/session/summary.ts
@@ -138,13 +138,6 @@ export namespace SessionSummary {
const title = textPart.text.length > 50 ? textPart.text.slice(0, 50) + "..." : textPart.text
userMsg.summary.title = title
await Session.updateMessage(userMsg)
- await Session.update(
- userMsg.sessionID,
- (draft) => {
- draft.title = title
- },
- { touch: false },
- )
return
}
const agent = await Agent.get(titleAgent)
@@ -177,13 +170,6 @@ export namespace SessionSummary {
log.info("title", { title: result })
userMsg.summary.title = result
await Session.updateMessage(userMsg)
- await Session.update(
- userMsg.sessionID,
- (draft) => {
- draft.title = result
- },
- { touch: false },
- )
}
}
diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts
index d84fc590450..fd537657d37 100644
--- a/packages/opencode/src/session/system.ts
+++ b/packages/opencode/src/session/system.ts
@@ -47,8 +47,7 @@ export namespace SystemPrompt {
``,
)
- // files info was disabled during permission rework
- if (options?.files !== false && false)
+ if (options?.files !== false)
parts.push(
``,
` ${
From 001c20127da42965222de586ee5f4326a6538df9 Mon Sep 17 00:00:00 2001
From: ops
Date: Wed, 28 Jan 2026 16:58:50 +0100
Subject: [PATCH 3/3] resolve slop
---
packages/opencode/src/session/llm.ts | 4 +++-
packages/opencode/src/session/prompt.ts | 3 +--
packages/opencode/src/session/system.ts | 3 ++-
3 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts
index 45aadc53000..4e6b58801d8 100644
--- a/packages/opencode/src/session/llm.ts
+++ b/packages/opencode/src/session/llm.ts
@@ -67,7 +67,9 @@ export namespace LLM {
const isCodex = provider.id === "openai" && auth?.type === "oauth"
const system = []
- const customSystem = input.system
+ const customSystem = input.agent.options.system
+ ? await SystemPrompt.environment(input.model, input.agent.options.system)
+ : input.system
system.push(
[
// use agent prompt otherwise provider prompt
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index ca547b792bc..ac610d20d64 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -945,7 +945,7 @@ export namespace SessionPrompt {
]
}
break
- case "file:": {
+ case "file:":
log.info("file", { mime: part.mime })
// have to normalize, symbol search returns absolute paths
// Decode the pathname since URL constructor doesn't automatically decode it
@@ -1128,7 +1128,6 @@ export namespace SessionPrompt {
source: part.source,
},
]
- }
}
}
diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts
index fd537657d37..d84fc590450 100644
--- a/packages/opencode/src/session/system.ts
+++ b/packages/opencode/src/session/system.ts
@@ -47,7 +47,8 @@ export namespace SystemPrompt {
``,
)
- if (options?.files !== false)
+ // files info was disabled during permission rework
+ if (options?.files !== false && false)
parts.push(
``,
` ${