Repository navigation
feat(opencode): add /goal plugin for autonomous task completion - #28610
NathanKong76 wants to merge 2 commits into
Conversation
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
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.
| 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" |
| @@ -1237,7 +1238,7 @@ export const layer = Layer.effect( | |||
| throw new Error("Impossible") | |||
| }) | |||
|
|
|||
| 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")( | |||
| @@ -1485,7 +1523,7 @@ export const layer = Layer.effect( | |||
| const loop: (input: LoopInput) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.loop")(function* ( | |||
| 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>) | |||
| 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" | |||
| 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 { |
| "session.next.goal.set": () => {}, | ||
| "session.next.goal.updated": () => {}, | ||
| "session.next.goal.cleared": () => {}, | ||
| "session.next.goal.completed": () => {}, |
3d912fc to
33076c5
Compare
4728f09 to
b2c696b
Compare
|
Rewritten as a plugin per @aiden Cline feedback — zero core code changes, just 2 files. Architecture
Design
Open to feedback — happy to adjust anything. If this pattern works, future goal features (time limits, subtask goals, etc.) can be added to the plugin without touching core. |
0030ef4 to
1268d72
Compare
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
1268d72 to
05c7a21
Compare
d11ead8 to
6b656b6
Compare
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
26c7fc1 to
da8a52c
Compare
da8a52c to
d11f33b
Compare
|
This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window. Feel free to open a new pull request that follows our guidelines. |

Summary
A built-in plugin that enables multi-turn autonomous goal execution via
/goalcommand. Zero core changes — all business logic in the plugin, command registration via existing skill system.create_goal/update_goal/get_goaltools viaHooks.tool/goal pause/resume/clearviacommand.execute.beforeclient.promptAsync().opencode/goals.jsonper directory (atomic write)Closes #27167
Testing
bun test test/plugin/goal.test.ts: 18/18 pass (storage, logic, formatting)bun test test/plugin/goal.integration.test.ts: 16/16 pass (all hooks via mock PluginInput)bun typecheck: 0 errors