feat(background-agent): add parentAgent tracking to preserve agent context in background tasks

- Add parentAgent field to BackgroundTask, LaunchInput, and ResumeInput interfaces
- Pass parentAgent through background task manager to preserve agent identity
- Update sisyphus-orchestrator to set orchestrator-sisyphus agent context
- Add session tracking for background agents to prevent context loss
- Propagate agent context in background-task and sisyphus-task tools

This ensures background/subagent spawned tasks maintain proper agent context for notifications and continuity.

🤖 Generated with assistance of oh-my-opencode
This commit is contained in:
YeonGyu-Kim 2026-01-08 23:01:23 +09:00
parent 9481770a39
commit 1e239e6155
5 changed files with 22 additions and 2 deletions

View File

@ -98,6 +98,7 @@ export class BackgroundManager {
lastUpdate: new Date(), lastUpdate: new Date(),
}, },
parentModel: input.parentModel, parentModel: input.parentModel,
parentAgent: input.parentAgent,
model: input.model, model: input.model,
concurrencyKey, concurrencyKey,
} }
@ -236,6 +237,7 @@ export class BackgroundManager {
existingTask.parentSessionID = input.parentSessionID existingTask.parentSessionID = input.parentSessionID
existingTask.parentMessageID = input.parentMessageID existingTask.parentMessageID = input.parentMessageID
existingTask.parentModel = input.parentModel existingTask.parentModel = input.parentModel
existingTask.parentAgent = input.parentAgent
existingTask.progress = { existingTask.progress = {
toolCalls: existingTask.progress?.toolCalls ?? 0, toolCalls: existingTask.progress?.toolCalls ?? 0,
@ -438,8 +440,8 @@ export class BackgroundManager {
} }
try { try {
// Use only parentModel - don't fallback to prevMessage.model // Use only parentModel/parentAgent - don't fallback to prevMessage
// This prevents accidentally changing parent session's model // This prevents accidentally changing parent session's model/agent
const modelField = task.parentModel?.providerID && task.parentModel?.modelID const modelField = task.parentModel?.providerID && task.parentModel?.modelID
? { providerID: task.parentModel.providerID, modelID: task.parentModel.modelID } ? { providerID: task.parentModel.providerID, modelID: task.parentModel.modelID }
: undefined : undefined
@ -447,6 +449,7 @@ export class BackgroundManager {
await this.client.session.prompt({ await this.client.session.prompt({
path: { id: task.parentSessionID }, path: { id: task.parentSessionID },
body: { body: {
agent: task.parentAgent,
model: modelField, model: modelField,
parts: [{ type: "text", text: message }], parts: [{ type: "text", text: message }],
}, },

View File

@ -30,6 +30,8 @@ export interface BackgroundTask {
model?: { providerID: string; modelID: string } model?: { providerID: string; modelID: string }
/** Agent name used for concurrency tracking */ /** Agent name used for concurrency tracking */
concurrencyKey?: string concurrencyKey?: string
/** Parent session's agent name for notification */
parentAgent?: string
} }
export interface LaunchInput { export interface LaunchInput {
@ -39,6 +41,7 @@ export interface LaunchInput {
parentSessionID: string parentSessionID: string
parentMessageID: string parentMessageID: string
parentModel?: { providerID: string; modelID: string } parentModel?: { providerID: string; modelID: string }
parentAgent?: string
model?: { providerID: string; modelID: string } model?: { providerID: string; modelID: string }
skills?: string[] skills?: string[]
skillContent?: string skillContent?: string
@ -50,4 +53,5 @@ export interface ResumeInput {
parentSessionID: string parentSessionID: string
parentMessageID: string parentMessageID: string
parentModel?: { providerID: string; modelID: string } parentModel?: { providerID: string; modelID: string }
parentAgent?: string
} }

View File

@ -352,6 +352,7 @@ export function createSisyphusOrchestratorHook(
await ctx.client.session.prompt({ await ctx.client.session.prompt({
path: { id: sessionID }, path: { id: sessionID },
body: { body: {
agent: "orchestrator-sisyphus",
parts: [{ type: "text", text: prompt }], parts: [{ type: "text", text: prompt }],
}, },
query: { directory: ctx.directory }, query: { directory: ctx.directory },

View File

@ -74,6 +74,7 @@ export function createBackgroundTask(manager: BackgroundManager): ToolDefinition
parentSessionID: ctx.sessionID, parentSessionID: ctx.sessionID,
parentMessageID: ctx.messageID, parentMessageID: ctx.messageID,
parentModel, parentModel,
parentAgent: prevMessage?.agent,
}) })
ctx.metadata?.({ ctx.metadata?.({

View File

@ -9,6 +9,7 @@ import { findNearestMessageWithFields, MESSAGE_STORAGE } from "../../features/ho
import { resolveMultipleSkills } from "../../features/opencode-skill-loader/skill-content" import { resolveMultipleSkills } from "../../features/opencode-skill-loader/skill-content"
import { createBuiltinSkills } from "../../features/builtin-skills/skills" import { createBuiltinSkills } from "../../features/builtin-skills/skills"
import { getTaskToastManager } from "../../features/task-toast-manager" import { getTaskToastManager } from "../../features/task-toast-manager"
import { subagentSessions } from "../../features/claude-code-session-state"
type OpencodeClient = PluginInput["client"] type OpencodeClient = PluginInput["client"]
@ -159,6 +160,7 @@ export function createSisyphusTask(options: SisyphusTaskToolOptions): ToolDefini
parentSessionID: ctx.sessionID, parentSessionID: ctx.sessionID,
parentMessageID: ctx.messageID, parentMessageID: ctx.messageID,
parentModel, parentModel,
parentAgent: prevMessage?.agent,
}) })
ctx.metadata?.({ ctx.metadata?.({
@ -325,6 +327,7 @@ ${textContent || "(No text output)"}`
parentSessionID: ctx.sessionID, parentSessionID: ctx.sessionID,
parentMessageID: ctx.messageID, parentMessageID: ctx.messageID,
parentModel, parentModel,
parentAgent: prevMessage?.agent,
model: categoryModel, model: categoryModel,
skills: args.skills, skills: args.skills,
skillContent: systemContent, skillContent: systemContent,
@ -352,6 +355,7 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
const toastManager = getTaskToastManager() const toastManager = getTaskToastManager()
let taskId: string | undefined let taskId: string | undefined
let syncSessionID: string | undefined
try { try {
const createResult = await client.session.create({ const createResult = await client.session.create({
@ -366,6 +370,8 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
} }
const sessionID = createResult.data.id const sessionID = createResult.data.id
syncSessionID = sessionID
subagentSessions.add(sessionID)
taskId = `sync_${sessionID.slice(0, 8)}` taskId = `sync_${sessionID.slice(0, 8)}`
const startTime = new Date() const startTime = new Date()
@ -461,6 +467,8 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
toastManager.removeTask(taskId) toastManager.removeTask(taskId)
} }
subagentSessions.delete(sessionID)
return `Task completed in ${duration}. return `Task completed in ${duration}.
Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""} Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}
@ -473,6 +481,9 @@ ${textContent || "(No text output)"}`
if (toastManager && taskId !== undefined) { if (toastManager && taskId !== undefined) {
toastManager.removeTask(taskId) toastManager.removeTask(taskId)
} }
if (syncSessionID) {
subagentSessions.delete(syncSessionID)
}
const message = error instanceof Error ? error.message : String(error) const message = error instanceof Error ? error.message : String(error)
return `❌ Task failed: ${message}` return `❌ Task failed: ${message}`
} }