Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- Re-check session status and recent messages after an auto-continue cooldown, pause immediately for human intervention, Plan-agent switches, permission rejection, aborts, and provider errors, and abort an accepted continuation when the user takes control.
- Persist one continuation claim per goal execution epoch and source assistant turn to prevent sequential duplicate idle events, while preserving the initiating agent, model, and variant on every continuation prompt.

## 0.6.3 — 2026-07-11

- Clarify the textual completion protocol so models keep evidence and completion on consecutive plain-text lines instead of wrapping markers in Markdown or separating them with blank lines.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ An ordered sequence, run as a strict pipeline:
## How it works

1. When you set a goal, the plugin stores it in session memory and injects it into the system prompt so the assistant keeps it in view on every turn.
2. Each time the session goes idle, the plugin sends a continuation prompt containing the goal, the remaining budget, and a completion audit asking the assistant to verify the current state before declaring done.
2. Each time the session goes idle, the plugin sends a continuation prompt containing the goal, the remaining budget, and a completion audit asking the assistant to verify the current state before declaring done. Continuations retain the agent, provider/model, and variant that initiated the goal. Before sending after a cooldown, the plugin re-checks that the session is still idle and no human message, newer assistant turn, Plan-agent switch, rejected permission, abort, or provider error has superseded the request.
3. The plugin stops auto-continuing when the assistant ends a response with a substantiated `[goal:complete]` or `[goal:blocked]`, or when a safety limit is reached. A `[goal:complete]` is only honored when it is preceded by a `[goal:evidence]` line; a `[goal:blocked]` is only honored when a concrete blocker is stated. Unsubstantiated claims are rejected and the plugin re-prompts for the missing evidence or blocker.
4. If OpenCode compacts the session, the plugin injects a deterministic summary into the compaction context so the goal survives the compaction and the assistant keeps the thread. The summary — objective, status, budget usage, recent checkpoints, and recent lifecycle events — is reconstructed from the plugin's persisted goal record rather than from chat memory, so it is stable and reproducible. While a goal is active, the plugin also disables OpenCode's generic post-compaction auto-continue so it does not race the plugin's own continuation.
5. If you send a message of your own while the goal is running, the plugin treats it as the latest instruction and pauses auto-continue so it does not talk over you. The plugin's own continuation prompts are ignored for this check (they are not "your" messages). Run `/goal resume` to hand control back to the goal loop.
5. If you send a message of your own while the goal is running, the plugin treats it as the latest instruction, pauses auto-continue, and asks OpenCode to abort an already accepted continuation so it does not talk over you. The plugin's own continuation prompts are ignored for this check (they are not "your" messages). A durable claim on the source assistant turn also prevents different idle event IDs from sending the same continuation twice. Run `/goal resume` to hand control back to the goal loop.

## Completion markers

Expand Down
21 changes: 16 additions & 5 deletions scripts/behavior-benchmark.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,29 @@ function assistantMessage(sessionID, text, id = `assistant-${sessionID}`) {
function createHost(messageForSession = () => "Working with tools.") {
const prompts = []
const notices = []
const sourceTurns = new Map()
return {
prompts,
notices,
client: {
app: { log: async () => {} },
session: {
messages: async ({ path }) => ({
data: [assistantMessage(path.id, messageForSession(path.id))],
}),
messages: async ({ path }) => {
const turn = sourceTurns.get(path.id) || 0
return {
data: [
assistantMessage(
path.id,
messageForSession(path.id, turn),
`assistant-${path.id}-${turn}`,
),
],
}
},
promptAsync: async (input) => {
prompts.push(input)
const sessionID = input?.sessionID || input?.path?.id
if (sessionID) sourceTurns.set(sessionID, (sourceTurns.get(sessionID) || 0) + 1)
return {}
},
},
Expand Down Expand Up @@ -157,8 +169,7 @@ results.push(await scenario("false-completion", 20, async () => {

results.push(await scenario("loop-circuit-breaker", 15, async () => {
const sessionID = "benchmark-loop"
let turn = 0
const host = createHost(() => `Still discussing the work, turn ${turn++}.`)
const host = createHost((_sessionID, turn) => `Still discussing the work, turn ${turn}.`)
const hooks = await createHooks(host)
await goalCommand(hooks, sessionID, "stop self-chat loops")
await idle(hooks, sessionID, "loop-1")
Expand Down
Loading
Loading