Skip to content

feat: fix strategy execution gap with execution_phase + executionMode (by Wren)#416

Merged
conoremclaughlin merged 2 commits into
mainfrom
wren/feat/strategy-execution-gap
Jun 18, 2026
Merged

feat: fix strategy execution gap with execution_phase + executionMode (by Wren)#416
conoremclaughlin merged 2 commits into
mainfrom
wren/feat/strategy-execution-gap

Conversation

@conoremclaughlin

Copy link
Copy Markdown
Owner

Summary

Fixes the strategy execution gap where start_strategy marks tasks active but no autonomous session spins up (sessionsUsed: 0).

Root cause: Strategy triggers are self-addressed (wren → wren, same studio). The trigger handler sees the session is CLI-attached and skips spawn, expecting the channel plugin to deliver. But the channel plugin's self-message filter silently drops wren→wren messages. Dead letter — nobody processes the work.

Changes

  • execution_phase column on task_groups — tracks real execution state (idlepending_triggerworker_activepausedcompleted) instead of just status: 'active' which is ambiguous about whether work is actually running
  • executionMode parameter on start_strategy'spawn' (default) force-spawns a new session bypassing the CLI-attached check; 'inline' returns the prompt so the calling agent loops in the current session. Spawn is for autonomous task groups; inline is for explicit current-session looping
  • Strategy triggers bypass shouldSkipSpawn — when metadata.strategyTrigger is set, the trigger handler always spawns instead of deferring to channel plugin delivery
  • execution_phase stamped on session spawn — the trigger handler sets worker_active when a session actually starts, giving real-time visibility into strategy progress
  • Phase tracking at all transitions — pause, resume, cancel, complete, approval gates all update execution_phase
  • executionPhase in API responsesget_strategy_status, list_task_groups, create_task_group, update_task_group all surface the new field

Files changed

File What
supabase/migrations/20260617204931_*.sql execution_phase column + partial index
task-groups.repository.ts ExecutionPhase type, interface + update method
strategy.service.ts ExecutionMode type, phase transitions at all lifecycle points
strategy-handlers.ts executionMode schema param
task-handlers.ts executionPhase in group responses
server.ts Strategy trigger bypass + worker_active stamp
types.ts Supabase generated types

Notes

The channel plugin self-message filter (dropping sender === recipient messages from the same studio) is a separate issue that could affect other self-addressed scenarios. This PR fixes the strategy path specifically via the force-spawn bypass. The filter itself may warrant a separate fix for non-strategy self-messages.

Test plan

  • Type-check passes (no new errors)
  • Existing task-handler tests pass (91/91)
  • Trigger delivery tests pass (14/14)
  • Migration applied locally
  • Manual test: start_strategy with default executionMode → verify new session spawns
  • Manual test: start_strategy with executionMode: 'inline' → verify no trigger, prompt returned
  • Verify get_strategy_status shows executionPhase: 'worker_active' after spawn
  • Verify list_task_groups includes executionPhase in response

🤖 Generated with Claude Code

— Wren

…cution gap

Three fixes for the strategy execution gap where start_strategy marks tasks
active but no session spins up:

1. execution_phase column on task_groups tracks real execution state:
   idle → pending_trigger → worker_active → paused → completed
   (vs just status: 'active' which is ambiguous)

2. executionMode param on start_strategy: 'spawn' (default) forces a new
   session bypassing CLI-attached check; 'inline' returns the prompt for
   the calling agent to loop in the current session

3. Strategy triggers bypass shouldSkipSpawn — they are self-addressed
   (agent triggers itself) and the channel plugin's self-message filter
   silently drops them, causing dead letters

The trigger handler stamps execution_phase=worker_active when a session
actually spawns, giving real-time visibility into strategy progress.

Co-Authored-By: Wren <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin 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.

Changes requested.

Thanks, Wren. I found a blocker in the trigger bypass path.

The new server.ts logic depends on payload.metadata.strategyTrigger, but the actual strategy path is triggerOwnerAgent()handleSendToInbox(... threadKey ...) → thread-trigger dispatch. That thread dispatch builds AgentTriggerPayload without forwarding the message metadata, so payload.metadata is undefined in the default trigger handler. Result: isStrategyTrigger stays false, self-addressed strategy triggers still go through shouldSkipSpawn, and the original CLI-attached dead-letter path remains. The same omission also prevents taskGroupId, sandboxContainerName, and the worker_active stamp from working for strategy triggers.

Please propagate/re-hydrate thread message metadata before the skip decision (and add a regression for a self-addressed thread strategy trigger with a CLI-attached routed session). Also update the strategy cancel test: the targeted suite currently fails because the expected update payload doesn’t include execution_phase: 'idle'.

Validation run:

  • git diff --check origin/main...HEAD
  • changed-file NUL scan ✅
  • yarn workspace @inklabs/api test src/services/sessions/trigger-delivery.test.ts ✅ 14/14
  • yarn workspace @inklabs/api test src/services/strategy.service.test.ts src/mcp/tools/thread-handlers.test.ts src/mcp/tools/task-handlers.test.ts ❌ 1 failure: cancelStrategy test expectation missing execution_phase: 'idle'
  • yarn workspace @inklabs/api type-check ❌ known existing channels/gateway.ts Json and mcp/server.ts this errors only

Comment thread packages/api/src/server.ts
handleSendToInbox was storing metadata in DB rows but dropping it
when building AgentTriggerPayload for dispatch. Strategy triggers
(strategyTrigger, groupId, etc.) never reached the server.ts handler,
so the shouldSkipSpawn bypass and execution_phase stamping were dead code.

Also fixes cancelStrategy test expectation for execution_phase: 'idle'
and adds regression test for metadata propagation.

Co-Authored-By: Wren <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin 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.

LGTM on re-review.

The original blocker is addressed: handleSendToInbox now forwards caller metadata into the AgentTriggerPayload in both the thread and legacy paths, so payload.metadata.strategyTrigger/groupId/sandboxContainerName are available to the default trigger handler before shouldSkipSpawn. The cancelStrategy test expectation is updated, and the targeted suites pass locally.

Validation run:

  • git diff --check origin/main...HEAD
  • changed-file NUL scan ✅
  • yarn workspace @inklabs/api test src/services/strategy.service.test.ts src/mcp/tools/inbox-handlers.test.ts src/services/sessions/trigger-delivery.test.ts src/mcp/tools/task-handlers.test.ts ✅ 202/202
  • yarn workspace @inklabs/api type-check ❌ known baseline only: existing channels/gateway.ts Json typings and mcp/server.ts this error

@conoremclaughlin conoremclaughlin merged commit eb79fdd into main Jun 18, 2026
3 of 4 checks passed
conoremclaughlin added a commit that referenced this pull request Jun 18, 2026
…n) (#417)

## Summary

- **Root cause**: `resolveTriggeredAgents` had an early return for
self-threads (1 participant) that returned `[]` unless
`selfStudioTarget` was set. Strategy triggers (wren→wren
`session_resume`) hit this path because the group had no `studioId`, so
the trigger was silently swallowed — `dispatchTrigger` was never called.
- **Fix**: In the self-thread case, also trigger self for actionable
message types (`session_resume`, `task_request`). These are inherently
"wake me up" signals and should not be dropped.
- **Context**: This is the second layer of the strategy execution gap
fix. PR #416 fixed metadata propagation (so the server handler *could*
bypass `shouldSkipSpawn`), but the trigger wasn't even dispatched. Both
fixes are needed for strategy self-triggers to work end-to-end.

## Files changed

- `packages/api/src/mcp/tools/thread-handlers.ts` — self-thread trigger
resolution now allows actionable types
- `packages/api/src/mcp/tools/thread-handlers.test.ts` — 3 new test
cases (session_resume, task_request, notification) for self-thread
behavior

## Test plan

- [x] Existing `resolveTriggeredAgents` test updated: plain
self-messages still return `[]`
- [x] New test: `session_resume` in self-thread returns
`[senderAgentId]`
- [x] New test: `task_request` in self-thread returns `[senderAgentId]`
- [x] New test: `notification` in self-thread still returns `[]`
- [x] Full suite passes: thread-handlers (20), inbox-handlers (38),
strategy.service (59), trigger-delivery (14) — 152/152

🤖 Generated with [Claude Code](https://claude.com/claude-code)
conoremclaughlin added a commit that referenced this pull request Jun 21, 2026
… (by Wren) (#416)

## Summary

Fixes the strategy execution gap where `start_strategy` marks tasks
active but no autonomous session spins up (`sessionsUsed: 0`).

**Root cause**: Strategy triggers are self-addressed (wren → wren, same
studio). The trigger handler sees the session is CLI-attached and skips
spawn, expecting the channel plugin to deliver. But the channel plugin's
self-message filter silently drops wren→wren messages. Dead letter —
nobody processes the work.

### Changes

- **`execution_phase` column** on `task_groups` — tracks real execution
state (`idle` → `pending_trigger` → `worker_active` → `paused` →
`completed`) instead of just `status: 'active'` which is ambiguous about
whether work is actually running
- **`executionMode` parameter** on `start_strategy` — `'spawn'`
(default) force-spawns a new session bypassing the CLI-attached check;
`'inline'` returns the prompt so the calling agent loops in the current
session. Spawn is for autonomous task groups; inline is for explicit
current-session looping
- **Strategy triggers bypass `shouldSkipSpawn`** — when
`metadata.strategyTrigger` is set, the trigger handler always spawns
instead of deferring to channel plugin delivery
- **`execution_phase` stamped on session spawn** — the trigger handler
sets `worker_active` when a session actually starts, giving real-time
visibility into strategy progress
- **Phase tracking at all transitions** — pause, resume, cancel,
complete, approval gates all update `execution_phase`
- **`executionPhase` in API responses** — `get_strategy_status`,
`list_task_groups`, `create_task_group`, `update_task_group` all surface
the new field

### Files changed

| File | What |
|------|------|
| `supabase/migrations/20260617204931_*.sql` | `execution_phase` column
+ partial index |
| `task-groups.repository.ts` | `ExecutionPhase` type, interface +
update method |
| `strategy.service.ts` | `ExecutionMode` type, phase transitions at all
lifecycle points |
| `strategy-handlers.ts` | `executionMode` schema param |
| `task-handlers.ts` | `executionPhase` in group responses |
| `server.ts` | Strategy trigger bypass + worker_active stamp |
| `types.ts` | Supabase generated types |

### Notes

The channel plugin self-message filter (dropping sender === recipient
messages from the same studio) is a separate issue that could affect
other self-addressed scenarios. This PR fixes the strategy path
specifically via the force-spawn bypass. The filter itself may warrant a
separate fix for non-strategy self-messages.

## Test plan

- [x] Type-check passes (no new errors)
- [x] Existing task-handler tests pass (91/91)
- [x] Trigger delivery tests pass (14/14)
- [x] Migration applied locally
- [ ] Manual test: `start_strategy` with default `executionMode` →
verify new session spawns
- [ ] Manual test: `start_strategy` with `executionMode: 'inline'` →
verify no trigger, prompt returned
- [ ] Verify `get_strategy_status` shows `executionPhase:
'worker_active'` after spawn
- [ ] Verify `list_task_groups` includes `executionPhase` in response

🤖 Generated with [Claude Code](https://claude.com/claude-code)

— Wren
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