Minimal task continuation and goal management plugin for OpenCode - automatically continues sessions when incomplete tasks remain and provides structured goal tracking for AI agents using the modern tool pattern.
This plugin provides two complementary systems:
- Task Continuation: Automatically continues sessions when incomplete todos remain
- Goal Management: Structured goal tracking with persistence across sessions using the modern OpenCode tool pattern
- Goal Context Injection: Automatically injects the current goal into session context so agents never forget their objectives
Perfect for:
- Multi-step task execution with automatic continuation
- Long-running agent workflows with clear objectives
- Goal-oriented AI agents that need to maintain context
- Preventing premature session termination
- Ensuring all tasks in a todo list are completed
This plugin follows the modern OpenCode plugin architecture with a clean separation of concerns:
src/
├── plugin.ts # Main plugin entry point
├── types.ts # TypeScript type definitions
├── logger.ts # Logging utilities
├── goal/ # Core goal management implementation
│ ├── management.ts # Goal CRUD operations
│ ├── continuation.ts # Task continuation logic
│ └── storage.ts # File-based storage
└── tools/goal/ # OpenCode tool definitions (NEW!)
├── index.ts # Tool factory function
├── goal_set.ts # goal_set tool
├── goal_status.ts # goal_status tool
├── goal_done.ts # goal_done tool
└── goal_cancel.ts # goal_cancel tool
- Tool Pattern: Uses
@opencode-ai/plugintool()decorator for LLM-accessible tools - Separation of Concerns: Core logic (
goal/) separated from tool definitions (tools/goal/) - Type Safety: Full TypeScript support with comprehensive interfaces
- Event-Driven: Uses OpenCode event system for session management
npm install @frugally3683/agent-loop-pluginimport agentLoopPlugin from "@frugally3683/agent-loop-plugin"
export default agentLoopPluginimport { agentLoopPlugin } from "@frugally3683/agent-loop-plugin"
export default {
plugins: [agentLoopPlugin],
// Custom configuration if needed
}The plugin exposes four powerful tools that AI agents can use during conversations to manage goals effectively.
Purpose: Set a new goal for the current session to keep the agent focused on primary objectives.
Usage:
goal_set({
title: "Implement user authentication",
done_condition: "Users can sign up, log in, and log out securely",
description: "Create a complete auth system with JWT tokens",
})Parameters:
title(string, required): Short, clear title for the goaldone_condition(string, required): Description of what constitutes goal completiondescription(string, optional): Detailed explanation of the goal
Example Response:
âś… Goal set successfully!
**Title:** Implement user authentication
**Done Condition:** Users can sign up, log in, and log out securely
**Description:** Create a complete auth system with JWT tokens
The agent will work toward this goal. Use goal_done when the condition is met.
Purpose: Check the current goal status to understand what the agent should be working on.
Usage:
goal_status()Parameters: None required
Example Response:
🎯 **Current Goal:** Implement user authentication
**Description:** Create a complete auth system with JWT tokens
**Status:** 🟡 In Progress
**Done Condition:** Users can sign up, log in, and log out securely
**Created:** 1/15/2024, 10:30 AM
Purpose: Mark the current goal as successfully completed when the done condition is met.
Usage:
goal_done()Parameters: None required
Example Response:
🎉 Goal completed!
**Title:** Implement user authentication
**Completed At:** 1/15/2024, 2:45 PM
The goal has been marked as complete.
Purpose: Cancel or abandon the current goal without completing it when goals are no longer relevant.
Usage:
goal_cancel({
reason: "Requirements changed, need to reassess approach",
})Parameters:
reason(string, optional): Explanation for why the goal is being cancelled
Example Response:
đźš« Goal cancelled.
**Title:** Implement user authentication
**Reason:** Requirements changed, need to reassess approach
The goal has been removed.
Purpose: Validate a completed goal after agent review. This is the final step in the goal lifecycle where the agent confirms the done condition has been met.
Usage:
goal_validate()Preconditions:
- Goal must be in "completed" status (use goal_done first)
- Agent should review the goal and verify the done condition is satisfied
Parameters: None required
Example Response:
âś… Goal validated!
**Title:** Implement user authentication
**Status:** Validated
**Completed:** 1/15/2024, 2:45 PM
**Validated:** 1/15/2024, 3:00 PM
**Done Condition:** Users can sign up, log in, and log out with secure password handling
The goal has been successfully validated and is now complete.
Goal Lifecycle:
- Active: Goal is being worked on
- Completed: Agent has marked goal as done (goal_done)
- Validated: Agent has reviewed and confirmed the done condition is met (goal_validate)
A goal represents a distinct objective that an AI agent should work toward. Unlike todos, which are individual tasks, goals are broader achievements that typically require multiple steps to accomplish. Goals provide:
- Persistent Context: Goals persist across sessions, helping agents remember their objectives
- Clear Completion Criteria: Each goal has a defined "done condition" that specifies when it's achieved
- Structured Workflows: Goals help organize complex multi-step workflows into coherent units
- Progress Tracking: Goals track their status (active/completed/validated) and completion/validation timestamps
Goals are stored as JSON files with the following structure:
interface Goal {
/** Title of the goal */
title: string
/** Optional detailed description of the goal */
description?: string
/** String description of what constitutes goal completion */
done_condition: string
/** Current status of the goal */
status: "active" | "completed" | "validated"
/** ISO timestamp when the goal was created */
created_at: string
/** ISO timestamp when the goal was completed, null if not completed */
completed_at: string | null
/** ISO timestamp when the goal was validated, null if not validated */
validated_at: string | null
}Goals are stored in the following location:
- Base Path:
~/.local/share/opencode/plugin/agent-loop - Session Path:
{basePath}/{sessionID}/goal.json - Custom Path: Configurable via
goalsBasePathoption
Each session can have one active goal at a time. Setting a new goal overwrites the existing one, ensuring agents always have a clear, current objective.
For more control, you can use the goal management API directly:
import { createGoalManagement } from "@frugally3683/agent-loop-plugin"
export default function myPlugin() {
// Create goal management with custom options
const goalManagement = createGoalManagement({
goalsBasePath: "/custom/path/to/goals",
})
return { goalManagement }
}Creates a new active goal for the session. Overwrites any existing goal.
interface GoalManagement {
createGoal: (
sessionID: string,
title: string,
doneCondition: string,
description?: string
) => Promise<Goal>
}Example:
const goal = await goalManagement.createGoal(
sessionID,
"Implement user authentication",
"Users can sign up, log in, and log out with secure password handling",
"Create a complete authentication system with JWT tokens"
)Retrieves the current goal for a session, or null if no goal exists.
interface GoalManagement {
getGoal: (sessionID: string) => Promise<Goal | null>
}Example:
const currentGoal = await goalManagement.getGoal(sessionID)
if (currentGoal) {
console.log(`Working on: ${currentGoal.title}`)
console.log(`Done when: ${currentGoal.done_condition}`)
}Marks the current goal as completed and records the completion timestamp.
interface GoalManagement {
completeGoal: (sessionID: string) => Promise<Goal | null>
}Example:
const completedGoal = await goalManagement.completeGoal(sessionID)
if (completedGoal) {
console.log(`Goal completed: ${completedGoal.title}`)
console.log(`Completed at: ${completedGoal.completed_at}`)
}The task continuation system integrates with goal management for intelligent session continuation:
import { createTaskContinuation } from "@frugally3683/agent-loop-plugin"
export default function myPlugin(ctx: PluginContext) {
const goalManagement = createGoalManagement({})
const taskContinuation = createTaskContinuation(ctx, {
countdownSeconds: 3,
goalManagement, // Enable goal-aware continuation
})
return { taskContinuation, goalManagement }
}This plugin demonstrates the modern OpenCode tool pattern using the tool() decorator from @opencode-ai/plugin:
import { tool } from "@opencode-ai/plugin"
export const myTool = tool({
description: `My Custom Tool
A detailed description of what this tool does and when to use it.
**Parameters:**
- \`param1\`: Description of first parameter
- \`param2\`: Description of second parameter`,
args: {
param1: tool.schema.string().describe("First parameter description"),
param2: tool.schema.number().describe("Second parameter description"),
optionalParam: tool.schema.boolean().optional().describe("Optional parameter"),
},
async execute(args, context) {
// Tool implementation
return `Tool executed with: ${args.param1}, ${args.param2}`
},
})- Use Descriptive Names: Name parameters clearly to help LLM agents understand their purpose
- Add Descriptions: Use
.describe()to provide context for each parameter - Validate Input Types: Use appropriate schema types (string, number, boolean, etc.)
- Mark Optional Parameters: Use
.optional()for non-required parameters - Provide Defaults: Use
.default()when appropriate for optional parameters
Tools receive a context object with session information:
interface ToolContext {
sessionID: string // Current session identifier
messageID: string // Current message identifier
agent: string // Current agent name
abort: AbortSignal // Signal for cancellation
}AI agents can use goals to maintain focus on overarching objectives:
// Agent sets a high-level goal at the start of a complex task
await goal_set({
title: "Build REST API for task management",
done_condition: "GET, POST, PUT, DELETE endpoints work for tasks with proper error handling",
description: "Create a complete REST API with Express.js including validation and authentication",
})
// Agent can check the goal to stay on track
const goalInfo = await goal_status()
console.log(`Remember the goal: ${goalInfo}`)
// When API is complete, mark goal as done
await goal_done()Agents can break down complex objectives into sub-goals:
// Set main project goal
await goal_set({
title: "Complete e-commerce platform",
done_condition:
"Users can browse products, add to cart, checkout, and receive order confirmation",
})
// When starting a specific feature, update the goal
await goal_set({
title: "Implement shopping cart",
done_condition: "Users can add/remove items, view cart contents, and proceed to checkout",
})
// Later, move to next goal
await goal_set({
title: "Implement checkout flow",
done_condition: "Users can enter shipping info, payment details, and receive order confirmation",
})Combine goals with todos for comprehensive task management:
// Set a goal
await goal_set({
title: "Deploy application to production",
done_condition: "Application is running in production with SSL and accessible via domain",
})
// Create todos that support the goal
await todowrite([
{ id: "1", content: "Set up CI/CD pipeline", status: "pending", priority: "high" },
{ id: "2", content: "Configure production database", status: "pending", priority: "high" },
{ id: "3", content: "Set up SSL certificates", status: "pending", priority: "medium" },
{ id: "4", content: "Update DNS records", status: "pending", priority: "medium" },
{ id: "5", content: "Test production deployment", status: "pending", priority: "high" },
])
// When all todos are complete, mark goal as done
await goal_done()Goals persist across sessions, making them ideal for long-running workflows:
// Session 1: Agent sets a complex goal
await goal_set({
title: "Migrate legacy database to new schema",
done_condition: "All data migrated, applications updated, old database decommissioned",
})
// Session ends, but goal persists...
// Session 2: Agent checks goal and continues work
const goalInfo = await goal_status()
if (goalInfo.includes("Migrate legacy database")) {
console.log("Resuming work on database migration...")
// Continue with migration tasks...
}Agents can use the done_condition to evaluate progress:
const goalInfo = await goal_status()
if (goalInfo.includes("🟡 In Progress")) {
// Check if done condition is met
const progress = assessProgress()
if (progress.meetsCriteria()) {
await goal_done()
console.log("Goal completion criteria met!")
} else {
console.log("Still working toward goal completion...")
}
}The plugin automatically injects the current goal into session context to ensure agents never forget their objectives. This provides persistent goal awareness across interactions.
Features:
- Automatic Goal Injection: Injects the current goal into new sessions
- Goal Guidance: Provides helpful goal tool usage instructions
- Smart Deduplication: Only injects context once per session
- Graceful Degradation: Silently skips if no goal exists
- Session Recovery: Re-injects goal context on session compaction
- Mode Preservation: Maintains the current model and agent settings
How It Works:
-
On New Sessions: When a chat message is received, the plugin:
- Checks if goal context was already injected
- Gets the current goal for the session
- Injects goal context with helpful tool guidance
- Preserves model/agent settings to prevent mode switching
-
On Session Compaction: When sessions are compacted, goal context is re-injected to maintain persistence
-
Goal Context Format: The injected context includes:
- Goal title
- Goal description (if present)
- Done condition (what constitutes completion)
- Current status
- Guidance on using goal tools
Goal Context Example:
<goal-context>
Implement user authentication system
Description: Create a complete authentication system with JWT tokens
Done Condition: Users can sign up, log in, and log out securely
Status: active
</goal-context>
## Goal Context
The agent has an active goal for this session. Use the goal tools to manage it:
- `goal_status` - Check the current goal details
- `goal_done` - Mark the goal as completed when the done condition is met
- `goal_cancel` - Cancel the goal if it's no longer relevant
**Remember:** Work toward completing the goal's done condition.Benefits:
- Never Forget Goals: Agents always have the current goal in context
- Persistent Awareness: Goal context survives session compaction and plugin reload
- Clear Focus: Agents can see exactly what they should be working toward
- Tool Integration: Provides immediate access to goal management tools
Configuration:
The goal context injection is automatic and requires no configuration. It gracefully handles:
- Missing goals (no injection occurs)
- Plugin reload/reconnection scenarios
- Session compaction events
| Option | Type | Default | Description |
|---|---|---|---|
taskLoop |
boolean | true | Enable task loop functionality |
countdownSeconds |
number | 2 | Seconds to wait before auto-continuation |
errorCooldownMs |
number | 3000 | Cooldown period after errors |
toastDurationMs |
number | 900 | Toast notification duration |
debug |
boolean | true | Enable debug logging |
logFilePath |
string | - | Path to log file for debugging |
| Option | Type | Default | Description |
|---|---|---|---|
goalsBasePath |
string | ~/.local/share/opencode/plugin/agent-loop | Custom base path for goal storage |
| Option | Type | Default | Description |
|---|---|---|---|
countdownSeconds |
number | 2 | Seconds to wait before continuation |
errorCooldownMs |
number | 3000 | Cooldown period after errors |
toastDurationMs |
number | 900 | Toast notification duration |
agent |
string | - | Agent name for continuation prompts |
model |
string | - | Model name for continuation prompts |
logFilePath |
string | - | Path to log file for debugging |
goalManagement |
GoalManagement | - | Goal management instance for goal integration |
This plugin uses the OpenCode SDK patterns for session interaction:
interface PluginContext {
/** Working directory for the session */
directory: string
/** Client API for interacting with OpenCode */
client: {
/** Session management APIs */
readonly session: {
/** Get current session ID */
readonly id: string
/** Get session details including agent and model */
get(opts: { path: { id: string } }): Promise<SessionInfo>
/** List messages in a session, returns most recent first */
messages(opts: {
path: { id: string }
}): Promise<Array<{ info: MessageInfo; parts: unknown[] }>>
/** Send a prompt to a session */
prompt(opts: {
path: { id: string }
body: {
agent?: string
model?: string | ModelSpec
noReply?: boolean
parts: Array<PromptPart>
}
query?: { directory: string }
}): Promise<void>
/** Get todos for a session */
todo(opts: { path: { id: string } }): Promise<Todo[] | { data: Todo[] }>
}
/** Text UI APIs */
tui: {
/** Show a toast notification in the UI */
showToast(opts: {
body: {
title: string
message: string
variant: "info" | "success" | "warning" | "error"
duration: number
}
}): Promise<void>
}
}
}# Install dependencies
npm install
# Run tests
npm test
# Type check
npm run typecheck
# Build
npm run build
# Lint
npm run lint
# Format
npm run formatGoals are stored as JSON files in the following structure:
File Location: {goalsBasePath}/{sessionID}/goal.json
Example Goal File:
{
"title": "Implement user authentication system",
"description": "Create a complete authentication system with JWT tokens, refresh tokens, and secure password hashing",
"done_condition": "Users can sign up, log in, and log out with secure password handling and session management",
"status": "active",
"created_at": "2024-01-15T10:30:00.000Z",
"completed_at": null
}Example Completed Goal:
{
"title": "Set up development environment",
"description": "Configure all necessary tools and dependencies for development",
"done_condition": "All developers can run 'npm install' and 'npm run dev' successfully",
"status": "completed",
"created_at": "2024-01-10T09:00:00.000Z",
"completed_at": "2024-01-10T11:45:00.000Z"
}- Clear Goal Titles: Use concise, descriptive titles that fit in a single line
- Specific Done Conditions: Define exactly what "done" means for each goal
- Reasonable Scope: Goals should be achievable within a few hours to days
- Update Goals: When objectives change, update the goal rather than creating new ones
- Use with Todos: Combine goals with todos for comprehensive task management
- Complete Goals: Always call goal_done when a goal is achieved
- Cancel When Needed: Use goal_cancel when goals are no longer relevant
- Leverage Tools: Use the goal tools during conversations for better agent coordination
const agentLoopPlugin: Plugin = async (ctx: PluginContext): Promise<PluginResult>Parameters:
ctx: Plugin context with session and tui access
Returns: PluginResult with tools and event handlers
Tools Provided:
goal_set: Set a new goal for the sessiongoal_status: Check current goal statusgoal_done: Mark current goal as completedgoal_cancel: Cancel current goal
function createGoalManagement(options?: GoalManagementOptions): GoalManagementParameters:
options: Optional configuration for goal management
Returns: GoalManagement interface with readGoal, writeGoal, createGoal, completeGoal, getGoal, hasActiveGoal, handler, and cleanup methods
function createTaskContinuation(
ctx: PluginContext,
options?: TaskContinuationOptions
): TaskContinuationParameters:
ctx: Plugin context with session and tui accessoptions: Optional configuration for task continuation
Returns: TaskContinuation interface with handler, markRecovering, markRecoveryComplete, cancel, and cleanup methods
If you're migrating from an older version of this plugin:
-
Import Changes: Update imports to use the new structure
// Old way import { createGoalManagement } from "@frugally3683/agent-loop-plugin" // New way (still supported) import { createGoalManagement } from "@frugally3683/agent-loop-plugin"
-
Tool Usage: Tools are now automatically exposed to agents
// Old way - manual command handling .prompt({ await ctx.client.session path: { id: sessionID }, body: { parts: [{ type: "text", text: JSON.stringify({ command: "goal_set", args: { title: "...", done_condition: "..." } }) }] } }) // New way - direct tool calls await goal_set({ title: "...", done_condition: "...", description: "..." })
-
Plugin Integration: The plugin now automatically handles tool exposure
// Just import and use the plugin import agentLoopPlugin from "@frugally3683/agent-loop-plugin" export default agentLoopPlugin
- Natural Language Interface: Agents can call tools using natural language
- Automatic Schema Validation: Input validation built into the tool pattern
- Better Error Handling: Consistent error responses across all tools
- Context Awareness: Tools have access to session context automatically
- Type Safety: Full TypeScript support for tool definitions
MIT