fix: track agent in boulder state to fix session continuation (fixes #927)

Add 'agent' field to BoulderState to track which agent (atlas) should
resume on session continuation. Previously, when user typed 'continue'
after interruption, Prometheus (planner) resumed instead of Sisyphus
(executor), causing all delegate_task calls to get READ-ONLY mode.

Changes:
- Add optional 'agent' field to BoulderState interface
- Update createBoulderState() to accept agent parameter
- Set agent='atlas' when /start-work creates boulder.json
- Use stored agent on boulder continuation (defaults to 'atlas')
- Add tests for new agent field functionality
This commit is contained in:
Rishi Vhavle
2026-02-04 12:47:34 +05:30
parent 708d15ebcc
commit d8137c0c90
5 changed files with 38 additions and 6 deletions

View File

@@ -246,5 +246,33 @@ describe("boulder-state", () => {
expect(state.plan_name).toBe("auth-refactor")
expect(state.started_at).toBeDefined()
})
test("should include agent field when provided", () => {
//#given - plan path, session id, and agent type
const planPath = "/path/to/feature.md"
const sessionId = "ses-xyz789"
const agent = "atlas"
//#when - createBoulderState is called with agent
const state = createBoulderState(planPath, sessionId, agent)
//#then - state should include the agent field
expect(state.agent).toBe("atlas")
expect(state.active_plan).toBe(planPath)
expect(state.session_ids).toEqual([sessionId])
expect(state.plan_name).toBe("feature")
})
test("should allow agent to be undefined", () => {
//#given - plan path and session id without agent
const planPath = "/path/to/legacy.md"
const sessionId = "ses-legacy"
//#when - createBoulderState is called without agent
const state = createBoulderState(planPath, sessionId)
//#then - state should not have agent field (backward compatible)
expect(state.agent).toBeUndefined()
})
})
})

View File

@@ -139,12 +139,14 @@ export function getPlanName(planPath: string): string {
*/
export function createBoulderState(
planPath: string,
sessionId: string
sessionId: string,
agent?: string
): BoulderState {
return {
active_plan: planPath,
started_at: new Date().toISOString(),
session_ids: [sessionId],
plan_name: getPlanName(planPath),
...(agent !== undefined ? { agent } : {}),
}
}

View File

@@ -14,6 +14,8 @@ export interface BoulderState {
session_ids: string[]
/** Plan name derived from filename */
plan_name: string
/** Agent type to use when resuming (e.g., 'atlas') */
agent?: string
}
export interface PlanProgress {

View File

@@ -431,7 +431,7 @@ export function createAtlasHook(
return state
}
async function injectContinuation(sessionID: string, planName: string, remaining: number, total: number): Promise<void> {
async function injectContinuation(sessionID: string, planName: string, remaining: number, total: number, agent?: string): Promise<void> {
const hasRunningBgTasks = backgroundManager
? backgroundManager.getTasksByParentSession(sessionID).some(t => t.status === "running")
: false
@@ -477,7 +477,7 @@ export function createAtlasHook(
await ctx.client.session.prompt({
path: { id: sessionID },
body: {
agent: "atlas",
agent: agent ?? "atlas",
...(model !== undefined ? { model } : {}),
parts: [{ type: "text", text: prompt }],
},
@@ -568,7 +568,7 @@ export function createAtlasHook(
state.lastContinuationInjectedAt = now
const remaining = progress.total - progress.completed
injectContinuation(sessionID, boulderState.plan_name, remaining, progress.total)
injectContinuation(sessionID, boulderState.plan_name, remaining, progress.total, boulderState.agent)
return
}

View File

@@ -102,7 +102,7 @@ All ${progress.total} tasks are done. Create a new plan with: /plan "your task"`
if (existingState) {
clearBoulderState(ctx.directory)
}
const newState = createBoulderState(matchedPlan, sessionId)
const newState = createBoulderState(matchedPlan, sessionId, "atlas")
writeBoulderState(ctx.directory, newState)
contextInfo = `
@@ -187,7 +187,7 @@ All ${plans.length} plan(s) are complete. Create a new plan with: /plan "your ta
} else if (incompletePlans.length === 1) {
const planPath = incompletePlans[0]
const progress = getPlanProgress(planPath)
const newState = createBoulderState(planPath, sessionId)
const newState = createBoulderState(planPath, sessionId, "atlas")
writeBoulderState(ctx.directory, newState)
contextInfo += `