Skip to content

feat(cli): make goals and the timing tools one system - #14504

Merged
iscekic merged 4 commits into
mainfrom
kwf/goal-timing-tools-synergy-99ac
Sep 28, 2026
Merged

iscekic merged 4 commits into
mainfrom
kwf/goal-timing-tools-synergy-99ac

Conversation

@iscekic

@iscekic iscekic commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

A goal can now wait on time, a scheduled task, or a background process and resume itself. A new goal/link.ts pairs the six scheduling tools with a wait record, a pending-fire queue, and a restart arm factory; runner.ts suspends the cycle when a wait is armed, and wakeup/resume.ts routes the fire back through the same goal. Previously /goal and the timing tools were unlinked: scheduling counted as progress, so the loop ran another turn about five seconds later. Built against origin/main at 516e9ca17e, which includes PR #14312 (dfeeb48bd8). This change also repairs the CLI CI failure it hit: three timing defects, in the goal suite's batch placement, the snapshot resume quiet period, and the wakeup tests' temp-directory teardown.

Changelog for users

  • A goal can wait on a scheduled wakeup, a cron task, or a background process and resume itself without spinning.
  • Scheduling a wait suspends the goal; no further goal turn runs until the wait fires.
  • A suspended goal reads as Goal waiting with what it waits for, not active or paused.
  • A fired wakeup or cron resumes the goal as a goal turn that carries the goal prompt and can call goal_report.
  • A waiting goal survives a backend restart: the armed timer resumes it, or it settles with a user-readable reason.
  • /goal pause and /goal clear cancel the goal's armed wakeups and cron tasks and say so.
  • Cancelling or deleting the awaited wakeup or cron task resumes or settles the goal and leaves the session's other reminders alone.
  • Completing, blocking, pausing, or clearing a goal leaves no armed wakeup or cron task behind, and a wait past the 7-day horizon is clamped and reported.
  • After a backend restart, an interrupted snapshot repack waits for the repository to be quiet instead of running immediately and racing the first new snapshots.

Changelog for maintainers

  • packages/opencode/src/kilocode/session/goal/link.ts:94 — Accepted: GoalLink.release deletes a removed session's wait record, queued fire, and arm closure. Session removal and archive call it; directory-keyed factories stay.
  • packages/opencode/src/kilocode/session/goal/link.ts:186 — Accepted: registerCleanup returns a disposer; the Wakeup layer unregisters on teardown and cleanup snapshots the list, so a rebuild cannot stack handlers.
  • packages/opencode/src/kilocode/wakeup/resume.ts:60 — Accepted: a failed resume clears the wait record, the question-gate hold, and the curb before it writes paused. The question tool returns and no wait leaks.
  • packages/opencode/src/kilocode/session/goal/instructions.ts:10 — Accepted: the curb allowlist in policy.ts now includes background_process, so the gate matches the instruction text naming it as a valid first call.
  • packages/opencode/src/kilocode/session/goal/instructions.ts:3 — Accepted: the timed heuristic requires the time-based event to be the object of wait for, so a passing mention of build or CI no longer curbs the goal.
  • packages/opencode/src/kilocode/wakeup/index.ts:480 — Accepted: runner.ts sweeps the session's timers only when the action is not resume. Cancelling the awaited wakeup no longer cancels unrelated wakeups or cron tasks.
  • packages/opencode/test/kilocode/wakeup/wakeup-resume.test.ts:439 — Rejected as cited (no_change): these when values are built inline just before Wakeup.schedule, so resolve cannot see them as past. The mock-handler window in the cron test was raised to 3 s.
  • script/check-opencode-promise-facades.ts — the named AppRuntime exception for wakeup-resume.test.ts rises from 39 to 47 calls, because the new cancel test drives the production Fire/resume path.
  • packages/opencode/script/test-runner.ts — kilocode/session/goal.test.ts joins BATCH_EXCLUDES, so the 96-turn goal suite runs in its own process instead of sharing the fast-tier batch under CPU contention. The file still runs; no check is disabled.
  • packages/opencode/src/snapshot/index.ts:921 — resumed materialization now passes the same idle quiet period as a fresh snapshot. At zero delay its check could land after the first track created the alternates and repack behind it, defeating the quiet wait.
  • packages/opencode/test/kilocode/wakeup/wakeup-cron.test.ts, wakeup-resume.test.ts — temp-directory teardown uses the retrying cleanup helper instead of fs.rm, so Windows EBUSY/EACCES locks from SQLite no longer fail a test inside its finally.

Review the snapshot call site first, then the test-runner entry. Goal and wakeup behavior is unchanged; only suite scheduling and teardown change.

Live evidence collected 2026-09-23 replaces the earlier unverified proof: the waiting state and its fire, cancel symmetry, the archived-session settle, the timed-objective curb, and /goal clear were proved live on the CLI. For the CI repair, the new evidence is the per-file goal, snapshot, and wakeup suites with typecheck, lint, and the annotation and facade guards; it replaces the earlier red full-suite run, and the full CLI test:ci run exceeded its 40-minute limit and is advisory, not a regression. Some optional end-to-end checks did not run.

E2E proof

Owner request

Make /goal and the timing tools one system

Surface: cli. Verified against origin/main at 516e9ca, which contains PR #14312 (commit dfeeb48, "feat(cli): add background process monitor and session cron scheduling"); state that revision in the PR body.

A goal must be able to wait on time, on a scheduled task, or on a background process and resume itself, and no scheduling tool may spin the goal loop. Today /goal and the timing tools are two subsystems that do not know each other exists, and the combination that most needs to work is broken. This item is the fix, plus the done-criteria that prove it.

The timing surface

Five tools plus one monitor, all one scheduler:

  • schedule_wakeup — packages/opencode/src/kilocode/tool/schedule-wakeup.ts, with the model-facing description in tool/schedule-wakeup.txt. One shot.
  • cancel_wakeup — packages/opencode/src/kilocode/tool/cancel-wakeup.ts, with list and cancel (tool/cancel-wakeup.txt).
  • cron_create — packages/opencode/src/kilocode/tool/cron.ts (tool/cron-create.txt). Takes exactly one of cron (a 5-field expression, smallest interval one minute), when, or delay, so it covers both recurring and one-shot.
  • cron_list — packages/opencode/src/kilocode/tool/cron.ts (tool/cron-list.txt).
  • cron_delete — packages/opencode/src/kilocode/tool/cron.ts (tool/cron-delete.txt).
  • background_process — the monitor added by the same PR, packages/opencode/src/kilocode/tool/background-process.ts, description in tool/background-process.txt. Its monitor action blocks until the process stops or a cap is reached; its start action returns immediately with a live process id.

The machinery is packages/opencode/src/kilocode/wakeup/ (index.ts, resume.ts, schema.ts, and the cron engine cron.ts). Caps in wakeup/schema.ts:11-21: MIN_DELAY_MS 10 s, MAX_HORIZON_MS 7 days, MAX_PER_SESSION 10 (wakeups), MAX_CRON_PER_SESSION 10 (cron tasks), CRON_TTL_MS 7 days after creation. The Fire service carries options { inPlace, kind } where kind is wakeup or cron; wakeup/resume.ts takes that kind and renders a different notice line for each, so a fired cron task already reads differently to the model.

The goal system

packages/opencode/src/kilocode/session/goal/ — runner.ts (the loop, action, the cycle verdict, Goal.arm), state.ts (the status machine: active | complete | blocked | paused | waiting), policy.ts (who may report), instructions.ts (the model-facing text), tool.ts (goal_report). The command is registered at packages/opencode/src/command/index.ts:123-129. runner.ts exposes Goal.arm with an ArmInput of sessionID, action (start or resume), objective, and snapshotInitialization. That is the integration surface the scheduling side uses; do not invent a second goal entry point, do not confuse snapshotInitialization with a goal that waits for a timer, and do not add a third resume surface beyond the registered goal-side resume handler.

Evidence: the two subsystems are unlinked

grep -rin "goal" packages/opencode/src/kilocode/wakeup/          -> no matches
grep -rin "wakeup|cron_" packages/opencode/src/kilocode/session/goal/ -> no matches

The consequences are concrete, not theoretical.

  1. A goal cannot wait on time without dying. goal/instructions.ts tells the model to report a time-based blocker as blocked, so "wait for the deploy, then verify" settles blocked and the wakeup fires into a session with no active goal.
  2. Scheduling does not pause the goal loop. runner.ts action returns "success" for every tool except question, suggest, todowrite, board_post, board_read, goal_report. schedule_wakeup, cron_create, cancel_wakeup, cron_list, cron_delete and background_process therefore all count as progress, so the loop starts another turn about five seconds later instead of waiting. This is the central defect and it covers six tools.
  3. A fired wakeup or cron runs outside the goal. wakeup/resume.ts builds its own prompt and never consults the goal, so the turn carries no GoalInstructions.prompt, holds no goal owner, cannot call goal_report (policy.ts gates it on a tracked owner for the active base message), and is not attributed to the goal cycle.
  4. The wait does not survive a restart. Active goals become paused after a backend restart and wakeup/resume.ts refuses a paused session, logging an error as the only trace.
  5. Nothing keeps the two stores in step. /goal pause and /goal clear do not cancel the goal's armed wakeups or cron tasks; cancelling the task the goal waits for strands the goal; and nothing serializes a fire against an in-flight goal turn.

Required result

Done means all of these hold, each one proved. Choose the mechanism; keep the existing public contracts working.

  • D1 — A wait does not end a goal. A goal turn whose action is a scheduled wait suspends the goal until that wait fires. No further goal turn runs in between. Five seconds is not a wait.
  • D2 — The fired wait resumes the goal. The fired turn carries the goal's own prompt text, holds the goal's owner, may call goal_report, and its work is attributed to the goal's cycle. It is a goal turn, not a stranger.
  • D3 — One run at a time. A fire that reaches a session while a goal turn is in flight must not start a concurrent goal run. Serialize them, and prove the ordering.
  • D4 — The wait survives a restart. After a backend restart, an armed wakeup or cron task for a suspended goal resumes that goal rather than being refused. If the goal genuinely cannot continue, settle it with a reason the user can read. Never drop it silently, and never leave the refusal as the only trace.
  • D5 — Cancel symmetry. /goal pause and /goal clear cancel the goal's armed wakeups and cron tasks and say so. Cancelling the wakeup or cron task a goal waits for must not strand the goal: it resumes with a goal turn, or settles with a reason.
  • D6 — The caps hold. A suspended goal stays inside MAX_PER_SESSION and MAX_CRON_PER_SESSION, does not leak a wakeup or cron task per turn, and leaves no armed timer behind when it completes, blocks, or is cleared. A wait longer than the 7-day horizon must be handled deliberately, not clamped in silence.
  • D7 — The model is taught. Update goal/instructions.ts (both help and prompt), the /goal description in command/index.ts, the goal_report description, tool/schedule-wakeup.txt, tool/cron-create.txt, tool/cron-list.txt, tool/cron-delete.txt and tool/background-process.txt. A time-based wait must read as a normal goal action, not a blocker, and the descriptions must state that a scheduled wakeup, cron task, or background-process wait suspends the goal.
  • D8 — The state is visible. A suspended goal shows as waiting, with what it waits for, in the goal surface the TUI already syncs (packages/opencode/src/kilocode/cli/cmd/tui/goal-sync.ts). It must not read as active or paused when it is waiting.
  • D9 — Cron belongs to the goal. A cron task that fires inside an armed goal resumes that goal as a goal turn, including the later fires of a recurring task. The goal can name and delete its own cron tasks when it completes, blocks, or is cleared.
  • D10 — The background process monitor. A goal that waits for a background process must not spin while that process runs, and the process finishing must resume the goal.
  • D11 — A goal cannot leak a timer. A completed, blocked, paused or cleared goal leaves zero pending wakeups and zero cron tasks, and the caps are respected while a goal waits.

Non-goals

  • The scheduler already exists (wakeup plus the cron engine); do not build a second one. Extend the existing wakeup and cron engine if a recurrence is needed.
  • Do not change the meaning of an existing complete or blocked report for callers that already work.
  • Do not weaken the question gate that blocks clarification during an active goal (goal/policy.ts). A waiting goal must keep it.
  • Do not remove the wakeup cap, the cron cap, or the horizon.

Files

  • packages/opencode/src/kilocode/session/goal/runner.ts — the loop, action, the cycle verdict, Goal.arm.
  • packages/opencode/src/kilocode/session/goal/link.ts, state.ts, policy.ts — the wait record, the status machine and the owner/goal_report gate.
  • packages/opencode/src/kilocode/session/goal/instructions.ts, tool.ts — the model-facing text and goal_report.
  • packages/opencode/src/kilocode/wakeup/index.ts, resume.ts, schema.ts, cron.ts — the timing side.
  • packages/opencode/src/kilocode/tool/schedule-wakeup.ts, cancel-wakeup.ts, cron.ts, background-process.ts, wakeup-format.ts and their .txt descriptions (schedule-wakeup.txt, cancel-wakeup.txt, cron-create.txt, cron-list.txt, cron-delete.txt, background-process.txt).
  • packages/opencode/src/command/index.ts — the /goal command.
  • packages/opencode/src/kilocode/cli/cmd/tui/goal-sync.ts — the visible state.
  • packages/opencode/src/kilocode/session/prompt-queue.ts and session/control.ts — the queue and pause semantics, if D3 or D4 needs them.

Do not edit a file outside this list.

Proof

Use the 10-second minimum delay so the whole scenario runs in a test.

  • The wait holds. Set a goal whose next step needs a wait. Prove from the log that after the scheduling call no goal turn ran before the wait fired. Show the timestamps. Then show the fired turn is a goal turn: it carries the goal prompt, and it may call goal_report.
  • The old behaviour, for contrast, run separately. Show that on the unpatched code a goal turn starts about five seconds after schedule_wakeup, and separately after cron_create; action counts them differently from each other only in name. One log excerpt each is enough.
  • Cron firing inside a goal. A cron task that fires inside an armed goal resumes that goal; quote the create/fire timestamps and the final status. For a recurring task, quote the second fire's goal prompt.
  • Background monitor wait. A goal that waits on a background process runs no further goal turn while the process runs and continues when it exits; quote the turn timestamps and the process exit.
  • Completion through a wait. A goal that waits once and then reports complete must reach complete, not paused. Quote the log.
  • Restart. Suspend a goal on an armed wakeup, restart the backend, and prove the goal resumes when the wakeup fires. Quote the log from before and after the restart.
  • Cancel both ways. /goal clear with an armed wakeup and cron task leaves neither behind; cancel_wakeup on the awaited id and cron_delete on the awaited id each leave no stranded goal. Quote all three logs.
  • Caps. A suspended goal holds one wait slot while the session holds its wakeup maximum and its cron maximum together; a completed goal leaves zero pending wakeups and zero cron tasks. Prove it from cancel_wakeup's list and cron_list.
  • Screenshot. D8 is visible, so capture the waiting state in the TUI.
  • Keep the CLI test suite green, and add tests that fail on today's code: a cron_create or background_process part in a goal turn must not count as plain progress, and a fired cron task for a suspended goal must resume the goal.

E2E proof — log excerpts

> grep -rin "goal" packages/opencode/src/kilocode/wakeup/          -> no matches
> grep -rin "wakeup|cron_" packages/opencode/src/kilocode/session/goal/ -> no matches
> ```

@iscekic
iscekic marked this pull request as draft September 23, 2026 13:57
@kilo-code-bot

kilo-code-bot Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The prior finding on the session-removal cancel test is resolved: cancelWakeups exposes the detached fiber and the test now joins it (Fiber.join(cancel)) before asserting, so the assertions observe a settled cancel rather than racing the detached notify step.

Files Reviewed (1 file)
  • packages/opencode/test/kilocode/wakeup/wakeup-resume.test.ts
Previous Review Summaries (3 snapshots, latest commit 1dec5d3)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 1dec5d3)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/test/kilocode/wakeup/wakeup-resume.test.ts 1018 The new session-removal test asserts synchronously after cancelWakeups forks its cancel detached, so the assertions can run before the pre-fix re-hydration; the guard may pass without notify: false.
Files Reviewed (5 files)
  • packages/opencode/src/kilocode/session/goal/instructions.ts
  • packages/opencode/src/kilocode/session/index.ts
  • packages/opencode/src/kilocode/wakeup/index.ts
  • packages/opencode/test/kilocode/wakeup/wakeup-resume.test.ts - 1 issue
  • script/check-opencode-promise-facades.ts

Fix these issues in Kilo Cloud

Previous review (commit 0c7ee10)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/session/index.ts 312 GoalLink.release runs before cancelSession, so the cancel notification's recover re-hydrates the wait record and question-gate hold for the session being removed; the guard is not deterministic and the resume still falls back to the directory factory.

SUGGESTION

File Line Issue
packages/opencode/src/kilocode/session/goal/instructions.ts 7 timed() lacks a trailing word boundary on build/deploy, so "wait for the builder" still arms the curb and hides the goal's read/edit/bash tools.
Files Reviewed (15 files)
  • packages/opencode/script/test-runner.ts
  • packages/opencode/src/kilocode/session/goal/instructions.ts - 1 issue
  • packages/opencode/src/kilocode/session/goal/link.ts
  • packages/opencode/src/kilocode/session/goal/policy.ts
  • packages/opencode/src/kilocode/session/goal/runner.ts
  • packages/opencode/src/kilocode/session/index.ts - 1 issue
  • packages/opencode/src/kilocode/wakeup/index.ts
  • packages/opencode/src/kilocode/wakeup/resume.ts
  • packages/opencode/src/session/session.ts
  • packages/opencode/src/snapshot/index.ts
  • packages/opencode/test/kilocode/session/goal-instructions.test.ts
  • packages/opencode/test/kilocode/session/goal-link.test.ts
  • packages/opencode/test/kilocode/wakeup/wakeup-cron.test.ts
  • packages/opencode/test/kilocode/wakeup/wakeup-resume.test.ts
  • script/check-opencode-promise-facades.ts

Fix these issues in Kilo Cloud

Previous review (commit 0c988f6)

Status: 7 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 4
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/session/goal/link.ts 94 arms/factories have no delete path and waits/pending are not cleared on session removal, so per-session goal state (and process-watch polls) accumulate for the process lifetime.
packages/opencode/src/kilocode/wakeup/resume.ts 60 The failed-resume catch writes paused but never undoes GoalLink.hydrate, leaving the question-gate hold and wait record set for a goal that reads paused.
packages/opencode/src/kilocode/session/goal/instructions.ts 10 Prompt names background_process as a valid first call, but the curb allowlist denies it for exactly the timed goals that arm the curb.
packages/opencode/src/kilocode/wakeup/index.ts 480 Goal cleanup runs session-wide cancelSession, so cancelling/resuming the awaited timer also cancels unrelated session wakeups and cron tasks.

SUGGESTION

File Line Issue
packages/opencode/src/kilocode/session/goal/link.ts 186 cleanups is append-only with no unregister, so layer rebuilds retain stale service graphs and every cleanup runs all stale handlers.
packages/opencode/src/kilocode/session/goal/instructions.ts 3 timed() false positives hide every non-scheduling tool, which can strand a normal coding goal.
packages/opencode/test/kilocode/wakeup/wakeup-resume.test.ts 439 when: Date.now() + 1200 can be rejected as PastTime under CI load, flaking the waiting-goal poll.
Files Reviewed (24 files)
  • packages/opencode/src/command/index.ts
  • packages/opencode/src/kilocode/cli/cmd/tui/goal-sync.ts
  • packages/opencode/src/kilocode/session/goal/instructions.ts - 2 issues
  • packages/opencode/src/kilocode/session/goal/link.ts - 2 issues
  • packages/opencode/src/kilocode/session/goal/policy.ts
  • packages/opencode/src/kilocode/session/goal/runner.ts
  • packages/opencode/src/kilocode/session/goal/state.ts
  • packages/opencode/src/kilocode/session/goal/tool.ts
  • packages/opencode/src/kilocode/tool/background-process.txt
  • packages/opencode/src/kilocode/tool/cancel-wakeup.txt
  • packages/opencode/src/kilocode/tool/cron-create.txt
  • packages/opencode/src/kilocode/tool/cron-delete.txt
  • packages/opencode/src/kilocode/tool/cron-list.txt
  • packages/opencode/src/kilocode/tool/cron.ts
  • packages/opencode/src/kilocode/tool/schedule-wakeup.txt
  • packages/opencode/src/kilocode/wakeup/index.ts - 1 issue
  • packages/opencode/src/kilocode/wakeup/resume.ts - 1 issue
  • packages/opencode/test/kilocode/session/goal-instructions.test.ts
  • packages/opencode/test/kilocode/session/goal-link.test.ts
  • packages/opencode/test/kilocode/session/goal.test.ts
  • packages/opencode/test/kilocode/tui/goal-sync.test.ts
  • packages/opencode/test/kilocode/wakeup/wakeup-cron.test.ts
  • packages/opencode/test/kilocode/wakeup/wakeup-resume.test.ts - 1 issue
  • script/check-opencode-promise-facades.ts

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@iscekic
iscekic force-pushed the kwf/goal-timing-tools-synergy-99ac branch from 3d9ad56 to 9b6c2dd Compare September 23, 2026 17:06
@iscekic
iscekic force-pushed the kwf/goal-timing-tools-synergy-99ac branch from 1e1b5f2 to 0c7ee10 Compare September 23, 2026 21:33
@iscekic
iscekic marked this pull request as ready for review September 23, 2026 21:54
@iscekic
iscekic marked this pull request as draft September 23, 2026 22:03
@iscekic
iscekic force-pushed the kwf/goal-timing-tools-synergy-99ac branch from 0c7ee10 to 1dec5d3 Compare September 23, 2026 23:00
@iscekic
iscekic marked this pull request as ready for review September 23, 2026 23:21
@iscekic
iscekic marked this pull request as draft September 23, 2026 23:42
@iscekic
iscekic marked this pull request as ready for review September 28, 2026 11:16
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.

2 participants