feat: add native /goal command for autonomous task completion#28610
Open
NathanKong76 wants to merge 1 commit into
Open
feat: add native /goal command for autonomous task completion#28610NathanKong76 wants to merge 1 commit into
NathanKong76 wants to merge 1 commit into
Conversation
Add a native /goal command that enables multi-turn autonomous goal execution, matching the implementation approach of Codex CLI. Key changes: - New GoalTable schema (thread_id PK, objective, status, token budget, iteration count) and GoalStatus union type in session.sql.ts - Goal runtime state machine in session/goal.ts with CRUD operations, continuation prompt generation, budget tracking, and iteration limits - create_goal and update_goal model tools in tool/goal.ts, with SessionGoal.Service resolved lazily inside execute (not at define time) - Goal tools registered unconditionally in tool/registry.ts, feature gated only at runtime via experimental.goals config check - /goal command registered in command/index.ts behind experimental.goals feature flag - Goal continuation logic in prompt.ts runLoop: after outcome is 'break', checks for active goal and injects continuation prompt as a synthetic user message before continuing the loop - SessionGoal.defaultLayer merged into AppLayer in app-runtime.ts - Goal event types (GoalSet, GoalUpdated, GoalCleared, GoalCompleted) in session-event.ts with no-op handlers in session-message-updater.ts Fixes anomalyco#27162
Contributor
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Introduces an experimental “goals” capability that lets a session persist an objective, continue autonomously across turns, and update goal status via new tools and a /goal command.
Changes:
- Added
goalsession storage + service layer and wired it into the app/runtime layers. - Introduced
create_goal/update_goaltools and a new/goalcommand template. - Extended the session prompt loop to optionally auto-continue when a goal is active.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/opencode/src/tool/registry.ts | Registers goal tools and provides the goal service layer. |
| packages/opencode/src/tool/goal.ts | Implements create_goal and update_goal tools. |
| packages/opencode/src/session/session.sql.ts | Adds GoalStatus and a new goal SQLite table. |
| packages/opencode/src/session/prompt.ts | Adds experimental auto-continue behavior based on active goals. |
| packages/opencode/src/session/goal.ts | New SessionGoal service for CRUD + iteration/token tracking. |
| packages/opencode/src/effect/app-runtime.ts | Provides SessionGoal layer at the app level. |
| packages/opencode/src/config/config.ts | Adds experimental.goals config flag. |
| packages/opencode/src/command/template/goal.txt | Adds /goal command prompt template. |
| packages/opencode/src/command/index.ts | Registers /goal command when experimental.goals is enabled. |
| packages/core/src/session-message-updater.ts | Adds no-op handlers for new goal events. |
| packages/core/src/session-event.ts | Defines new goal-related session events and includes them in the union. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1477
to
+1491
| if (outcome === "break") { | ||
| const goalSvc = yield* SessionGoal.Service | ||
| const configSvc = yield* Config.Service | ||
| const cfg = yield* configSvc.get() | ||
| const goalsEnabled = cfg.experimental?.goals === true | ||
| if (goalsEnabled) { | ||
| const goal = yield* goalSvc.get(sessionID) | ||
| if (goal && goalSvc.isGoalContinueNeeded(goal)) { | ||
| yield* goalSvc.incrementIteration(sessionID) | ||
| const updatedGoal = yield* goalSvc.get(sessionID) | ||
| if (updatedGoal && goalSvc.isGoalContinueNeeded(updatedGoal)) { | ||
| const contPrompt = updatedGoal.status === "budget_limited" | ||
| ? goalSvc.getBudgetLimitPrompt(updatedGoal) | ||
| : goalSvc.getContinuationPrompt(updatedGoal) | ||
| const msgId = MessageID.ascending() |
| }) | ||
|
|
||
| const isGoalContinueNeeded: Interface["isGoalContinueNeeded"] = (state: GoalState) => { | ||
| return state.status === "active" |
| }) | ||
|
|
||
| const runLoop: (sessionID: SessionID) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.run")( | ||
| const runLoop: (sessionID: SessionID) => Effect.Effect<MessageV2.WithParts, never, SessionGoal.Service | Config.Service> = Effect.fn("SessionPrompt.run")( |
| input: LoopInput, | ||
| ) { | ||
| return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID)) | ||
| return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID) as Effect.Effect<MessageV2.WithParts>) |
Comment on lines
+14
to
+18
| const UpdateGoalParams = Schema.Struct({ | ||
| status: Schema.String.annotate({ | ||
| description: 'New status: "complete" (goal achieved), "blocked" (cannot proceed)', | ||
| }), | ||
| }) |
| @@ -0,0 +1,230 @@ | |||
| import { Effect, Layer, Context, Schema } from "effect" | |||
Comment on lines
+6
to
+9
| import * as Log from "@opencode-ai/core/util/log" | ||
|
|
||
| const DEFAULT_MAX_ITERATIONS = 50 | ||
| const log = Log.create({ service: "session.goal" }) |
| timeUpdated: number | ||
| } | ||
|
|
||
| export class GoalNotFoundError extends Error { |
| } | ||
| } | ||
|
|
||
| export class GoalAlreadyExistsError extends Error { |
Comment on lines
+412
to
+415
| "session.next.goal.set": () => {}, | ||
| "session.next.goal.updated": () => {}, | ||
| "session.next.goal.cleared": () => {}, | ||
| "session.next.goal.completed": () => {}, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Closes #27167
Type of change
What does this PR do?
Adds a native
/goalcommand that enables multi-turn autonomous goal execution, similar to Codex CLI's/goalcommand. Feature gated behindexperimental.goalsconfig flag (default off).Implementation approach:
The goal runtime is backed by a new
GoalTablein SQLite (via existing Drizzle ORM). ASessionGoal.Servicestate machine handles CRUD, token budget tracking, and iteration limits. Two model tools (create_goal,update_goal) let the model manage goals autonomously.The key design challenge was avoiding
InstanceRef not providederrors —config.get()needsInstanceRefwhich isn't available during layer resolution. The fix:SessionGoal.Serviceandconfig.get()are accessed lazily insiderunLoop(afteroutcome === "break"), not at the layer effect level.Goal continuation works by injecting a synthetic user message with a continuation prompt when the goal is still active after a turn. The loop continues automatically until the goal is marked complete or the iteration limit (default 50) is reached.
/goalcommand registration is conditional oncfg.experimental?.goals === true.How did you verify your code works?
typecheck: cleantest/tool/registry.test.ts: 15/15 passtest/session/prompt.test.ts: 37/3 (3 pre-existing failures unrelated to this change — confirmed same failures exist ondevbranch)Screenshots / recordings
N/A — CLI change only.
Checklist