Skip to content

fix(orchestrator): default session_policy to spawn#415

Merged
dimakis merged 3 commits into
mainfrom
fix/default-spawn-policy
Jun 27, 2026
Merged

fix(orchestrator): default session_policy to spawn#415
dimakis merged 3 commits into
mainfrom
fix/default-spawn-policy

Conversation

@dimakis

@dimakis dimakis commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Flips the default sessionPolicy from reuse to spawn in TaskOrchestrator.tick()
  • Tasks without an explicit policy now get dedicated headless sessions instead of injecting into the user's active conversation
  • The spawn infrastructure was already built in PR feat(orchestrator): multi-agent goal execution via session_policy spawn #377 but was opt-in — this makes it the default
  • Explicit sessionPolicy: 'reuse' still works for tasks that need it

Test plan

  • All 45 existing orchestrator tests pass (tests that relied on reuse either set sessionPolicy: 'reuse' explicitly or don't provide spawnSession in deps, so they fall through correctly)
  • Verify task board no longer injects prompts into interactive sessions
  • Verify spawned tasks complete in their own headless sessions

🤖 Generated with Claude Code

Tasks without an explicit sessionPolicy were defaulting to 'reuse',
which injects task board instructions into the user's active
interactive session. The spawn infrastructure (PR #377) was built
to prevent this but was opt-in. Flip the default so tasks get
dedicated headless sessions unless explicitly set to reuse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@dimakis dimakis left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Centaur Review

Found 2 issue(s) (1 warning).

server/__tests__/task-orchestrator.test.ts

The change is logically correct in intent but effectively a no-op — TaskStore.create() defaults sessionPolicy to 'auto', which bypasses the ?? fallback. Either the store default or the 'auto' handling in the orchestrator also needs updating to achieve the desired spawn-by-default behavior.

  • 🟡 missing_tests: No test verifies the new default. All spawn tests explicitly set sessionPolicy: 'spawn', and all non-spawn tests either omit sessionPolicy (which the TaskStore defaults to 'auto', bypassing the ?? fallback entirely) or explicitly set 'reuse'. A test should cover the edge case where sessionPolicy is null/undefined to confirm the new 'spawn' default fires correctly. [fixable]

server/task-orchestrator.ts

The change is logically correct in intent but effectively a no-op — TaskStore.create() defaults sessionPolicy to 'auto', which bypasses the ?? fallback. Either the store default or the 'auto' handling in the orchestrator also needs updating to achieve the desired spawn-by-default behavior.

  • 🔵 regressions (L345): The ?? fallback only triggers when next.sessionPolicy is null/undefined — but TaskStore.create() defaults sessionPolicy to 'auto' (task-store.ts:269), and 'auto' is neither 'spawn' nor 'reuse', so it falls through to the reuse (else) branch regardless of this change. In practice this makes the change a no-op for tasks created through the normal store path. If the intent is for tasks without an explicit policy to spawn by default, the store's default at line 269 (input.sessionPolicy ?? 'auto') may also need updating to 'spawn', or the orchestrator should handle 'auto' explicitly (e.g., treating it as 'spawn'). [fixable]

Comment thread server/task-orchestrator.ts Outdated
case 'agent_work':
default: {
const policy = next.sessionPolicy ?? 'reuse';
const policy = next.sessionPolicy ?? 'spawn';

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 regressions: The ?? fallback only triggers when next.sessionPolicy is null/undefined — but TaskStore.create() defaults sessionPolicy to 'auto' (task-store.ts:269), and 'auto' is neither 'spawn' nor 'reuse', so it falls through to the reuse (else) branch regardless of this change. In practice this makes the change a no-op for tasks created through the normal store path. If the intent is for tasks without an explicit policy to spawn by default, the store's default at line 269 (input.sessionPolicy ?? 'auto') may also need updating to 'spawn', or the orchestrator should handle 'auto' explicitly (e.g., treating it as 'spawn'). [fixable]

The ?? fallback was a no-op because TaskStore.create() defaults
sessionPolicy to 'auto', a truthy string. Now 'auto' explicitly
resolves to 'spawn'. Added test covering the auto-to-spawn path
and fixed an existing test that implicitly relied on the old default.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@dimakis dimakis left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Centaur Review

Found 2 issue(s).

server/task-orchestrator.ts

Correct fix — resolves auto (the store default) to spawn instead of silently falling through to reuse. Minor style nit on dead-code fallback and one missing status assertion in the new test.

  • 🔵 style (L347): The ?? 'spawn' fallback on line 347 is unreachable: TaskStore.create() always defaults sessionPolicy to 'auto' (task-store.ts:269), so the field is never undefined on a stored task. The null-coalesce is dead code — only the raw === 'auto' branch matters. Not a bug, but it could mislead future readers into thinking undefined is a real case. Consider simplifying to const policy = next.sessionPolicy === 'auto' ? 'spawn' : next.sessionPolicy; or just removing the fallback. [fixable]

server/__tests__/task-orchestrator.test.ts

Correct fix — resolves auto (the store default) to spawn instead of silently falling through to reuse. Minor style nit on dead-code fallback and one missing status assertion in the new test.

  • 🔵 missing_tests (L838): The new test asserts setTaskContext is not called (proving it spawns rather than reuses), but does not verify the task's status is set to 'active' after spawn dispatch. Other spawn tests in this file also check status transitions. Consider adding expect(store.get(task.id)?.status).toBe('active') for parity. [fixable]

Comment thread server/task-orchestrator.ts Outdated
const policy = next.sessionPolicy ?? 'reuse';
// Default to spawn: 'auto' and undefined both resolve to 'spawn'
// so tasks get dedicated sessions unless explicitly set to 'reuse'.
const raw = next.sessionPolicy ?? 'spawn';

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 style: The ?? 'spawn' fallback on line 347 is unreachable: TaskStore.create() always defaults sessionPolicy to 'auto' (task-store.ts:269), so the field is never undefined on a stored task. The null-coalesce is dead code — only the raw === 'auto' branch matters. Not a bug, but it could mislead future readers into thinking undefined is a real case. Consider simplifying to const policy = next.sessionPolicy === 'auto' ? 'spawn' : next.sessionPolicy; or just removing the fallback. [fixable]

await vi.waitFor(() => expect(spawnSession).toHaveBeenCalled());

expect(spawnSession).toHaveBeenCalledWith(task.id, expect.any(String), goal.id);
expect(deps.setTaskContext).not.toHaveBeenCalled();

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 missing_tests: The new test asserts setTaskContext is not called (proving it spawns rather than reuses), but does not verify the task's status is set to 'active' after spawn dispatch. Other spawn tests in this file also check status transitions. Consider adding expect(store.get(task.id)?.status).toBe('active') for parity. [fixable]

…taur)

Remove dead-code ?? fallback — sessionPolicy is always set by the
store. Simplify to a direct 'reuse' check. Add status assertion
to new test for parity with other spawn tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@dimakis dimakis left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Centaur Review

LGTM — no issues found.

@dimakis dimakis merged commit 76a56d7 into main Jun 27, 2026
1 check passed
@dimakis dimakis deleted the fix/default-spawn-policy branch June 27, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant