Skip to content

Wire /goal kickoff so setting or resuming a goal actually starts a turn - #399

Closed
TheGreatAxios wants to merge 1 commit into
mainfrom
cl-5693-goal-kickoff-never-wired
Closed

Wire /goal kickoff so setting or resuming a goal actually starts a turn#399
TheGreatAxios wants to merge 1 commit into
mainfrom
cl-5693-goal-kickoff-never-wired

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Collaborator

Summary

  • /goal <brief> printed "Goal set." and did nothing: api.kickoff?.(...) (src/tui/commands/built-in.ts:257,286) was declared optional (src/tui/commands/registry.ts:20) and the only construction site (src/tui/runner.ts:1797) never provided it, so the optional-chain call silently no-op'd. Governor state changed, the agent was never told.
  • goalKickoffUserMessage (src/agent/goal.ts:322) had zero callers in src/.
  • Fixed by extracting the wiring into createGoalKickoff (src/tui/goal-kickoff.ts) and providing it at runner.ts's only construction site. kickoff is now required on CommandContext.goal since that's confirmed the only constructor (checked src/exec/ and src/tui-opentui/demo.ts — neither builds this API).

Decisions

  1. Immediate vs queued — kickoff is routed through agentProxy.send, the exact path every typed prompt and every command's {type:"send"} result already uses. It awaits sessionOps.awaitTail() internally, so a goal set mid-turn queues behind the in-flight turn instead of corrupting it.
  2. Transcript visibility — no separate echo row. The standard send path already renders the sent message as a full user-turn bubble (same as any operator prompt, however long); adding a manually-collapsed row on top would have duplicated it (confirmed by running it — an earlier version of this diff double-printed the message before this was caught and reverted).
  3. Resumephase: "resume" goes through the identical createGoalKickoff call; verified via unit test and live run (see below), not assumed symmetric.

Second bug reported, not fixed here

The operator also reported the phase line stuck on "working" indefinitely. Traced this: command dispatch (/goal ...) never touches turn-state (isProcessing/ramp) — that only flips via the composer's sendPrompt path, which slash commands bypass entirely. So under the old code the "working" label had no route to fire from /goal alone; it looks like an independent report, not a symptom of the kickoff no-op. Flagging for separate ticketing rather than guessing further under this PR's scope.

Verified by running

Built the binary and drove it in tmux against a scratch git repo:

  • /goal add a README file explaining this repo → agent autonomously ran manage_goal (created a 5-item acceptance checklist), manage_tasks, read the repo, wrote README.md, verified it, and reached Phase: completed in ~38s — all unprompted after the one /goal command.
  • Confirmed no duplicate transcript row for the kickoff message.

Test plan

  • bun run typecheck
  • bun run build
  • bun run test (4143 pass / 0 fail)
  • New tests: src/tui/goal-kickoff.test.ts (asserts the kickoff message reaches the send path itself, not just governor state, for both set and resume) and src/tui/commands/goal.test.ts updates
  • Live run in tmux: goal set → agent starts and completes work unprompted

This is a director/prompt-injection change under the project's human-review gate. Not to be merged without operator read-through.

@linear-code

linear-code Bot commented Aug 8, 2026

Copy link
Copy Markdown

CL-5693

api.kickoff was declared optional on CommandContext.goal and the only
construction site (runner.ts) never provided it, so /goal's set and
resume paths silently no-op'd: governor state changed but the agent
was never told, leaving the operator staring at "Goal set." forever.

Extract the wiring into createGoalKickoff (src/tui/goal-kickoff.ts):
builds goalKickoffUserMessage and sends it through agentProxy.send,
the same queue-safe path every typed prompt and command "send" result
already uses, so a goal set mid-turn queues behind it instead of
corrupting it. kickoff is now required on CommandContext.goal since
runner.ts is confirmed the only constructor.

Verified live: /goal set now drives the agent through the full
planning -> implementing -> reviewing -> completed lifecycle
unprompted.
@TheGreatAxios
TheGreatAxios force-pushed the cl-5693-goal-kickoff-never-wired branch from 7e0338e to 6009e9a Compare August 8, 2026 18:54
@TheGreatAxios

Copy link
Copy Markdown
Collaborator Author

Review gauntlet complete: Greybeard, Gaasbot, Critique, Bruckheimer, Neckbeard — no blockers from any of them.

One fix applied from Critique's finding: reordered handleSendFailure/systemRow above the commandContext object literal in src/tui/runner.ts so the kickoff closure references them directly instead of through a lazy same-scope-later-const wrapper. Functionally identical (Critique confirmed the original was safe as written), but removes a forward-reference fragility that had no compiler guard. Re-verified: typecheck clean, full suite 4143/0.

Non-blocking follow-ups raised by reviewers, filed as separate concerns rather than folded into this bugfix:

  • Gaasbot: this is the third "mechanism built, never wired" bug reported today — worth a repo-wide audit of optional fields/callbacks on context-bag types (ctx.\w+\?\.\( in src/tui) to find siblings before a fourth shows up.
  • Bruckheimer: the kickoff message now shows in full on every /goal set/resume (~350 words) via the standard turn echo — correct and visible, but boilerplate-heavy for repeat use. A collapsed-row treatment is the right long-term UX; explicitly out of scope here since an earlier attempt at it duplicated the message and was reverted.

Still gated on operator read-through per the standing director/prompt-change review rule — not merging this from any agent.

@TheGreatAxios

Copy link
Copy Markdown
Collaborator Author

Closing unmerged. The fix is correct and was verified end to end in a real session — /goal sets a brief and the agent works autonomously to completion — but goal is being removed entirely rather than repaired.

The kickoff no-op this fixes (api.kickoff?.() declared optional and never provided) is real and documented in the ticket for the record. It is not worth merging a fix for a subsystem scheduled for deletion.

Continuation belongs to the director unconditionally: createGoalGovernor "only decides whether to re-enter inference after a clean yield," which is a general capability, not a goal-specific one. Goal will be rescoped as one input to that decision rather than a parallel substrate.

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