Archived
status
This commit is contained in:
1 parent
c9c25f86eb
commit
40aaf4fea7
56 files changed
+5683
-179
No files matched your search
@@ -0,0 +1,149 @@
|
||||
# Implementation Brief: OpenCode Loop Guard Plugin
|
||||
|
||||
## Context
|
||||
|
||||
OpenCode is configured globally at `~/.config/opencode/`. A local Qwen model frequently enters execution loops, repeating a small sequence of tool calls such as `A -> B -> C` until it consumes many turns. The built-in `doom_loop` permission only catches three consecutive identical tool-name and exact-argument calls. This task adds a complementary global plugin that detects periodic cycles across all tools.
|
||||
|
||||
## Deliverables
|
||||
|
||||
1. Create `/Users/shane/.config/opencode/plugins/loop-guard.ts`.
|
||||
2. Create focused tests at `/Users/shane/.config/opencode/plugins/loop-guard.test.ts` if the test runner can execute them directly; otherwise create a standalone test script under the same directory that uses no new dependencies.
|
||||
3. Do not modify `~/.config/opencode/opencode.json`; plugins in the global plugin directory are auto-discovered.
|
||||
4. Do not add npm dependencies. Use only Node/Bun built-ins and existing `@opencode-ai/plugin` types.
|
||||
5. Write implementation report to `/Users/shane/dev/voice-ios/.artifacts/20260831-105429-opencode-loop-guard/implementation.md`.
|
||||
|
||||
## Plugin Contract
|
||||
|
||||
Export a default plugin function typed as `Plugin` from `@opencode-ai/plugin`:
|
||||
|
||||
```ts
|
||||
import type { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export default (async (input) => ({ ... })) satisfies Plugin
|
||||
```
|
||||
|
||||
The plugin must:
|
||||
|
||||
- Maintain an in-memory map keyed by session ID.
|
||||
- On every `tool.execute.before` hook, append a canonical signature for `{ tool, args }`.
|
||||
- Inspect the recent history for a repeating suffix cycle of length 1 through 4.
|
||||
- Detect a cycle only when the pattern has repeated at least three complete times after the current call is included.
|
||||
- If detected, throw an `Error` to block that tool execution.
|
||||
- Otherwise allow execution by returning normally.
|
||||
|
||||
## Canonicalization Requirements
|
||||
|
||||
Canonical signatures must be stable and deterministic:
|
||||
|
||||
- Include the exact tool name.
|
||||
- For object arguments, recursively sort object keys before serialization.
|
||||
- Preserve arrays in order.
|
||||
- Preserve string values exactly; do not trim, lowercase, collapse whitespace, or otherwise rewrite command text.
|
||||
- Use a compact JSON representation with sorted keys. A stable replacer is acceptable, but key sorting must be explicit and recursive.
|
||||
- Treat `undefined` fields as absent so key presence does not create spurious differences if the provider omits optional arguments.
|
||||
|
||||
## Detection Algorithm
|
||||
|
||||
For each session history array `H`, after appending the current signature:
|
||||
|
||||
1. For cycle length `L = 1..4`:
|
||||
- Require `H.length >= L * 3`.
|
||||
- Let the candidate pattern be the last `L` signatures.
|
||||
- Verify that the preceding `2L` signatures consist of exactly two repetitions of that same pattern.
|
||||
- If yes, a three-repetition cycle is detected.
|
||||
2. Prefer the shortest matching cycle length for diagnostics.
|
||||
3. Do not inspect more than 16 recent signatures; keep this bounded and simple.
|
||||
|
||||
This means:
|
||||
|
||||
- `A A A A` detects length 1 on the fourth call.
|
||||
- `A B A B A B` detects length 2 on the sixth call.
|
||||
- `A B C A B C A B C` detects length 3 on the ninth call.
|
||||
- `A B C D A B C D A B C D` detects length 4 on the twelfth call.
|
||||
- `A B C A B C A B` does not detect because only two complete repetitions plus a partial cycle exist.
|
||||
|
||||
## Blocking Message
|
||||
|
||||
The thrown error message should be concise and model-facing. It must:
|
||||
|
||||
- State that repeated tool-call cycling was detected and this exact call was blocked.
|
||||
- Describe the repeating shape without dumping raw arguments, for example `3 calls repeated 3 times`.
|
||||
- Instruct the model to stop retrying the same sequence unchanged.
|
||||
- Ask it to compare expected versus observed results from prior attempts.
|
||||
- Require either a materially different tool or arguments based on new evidence, or a concise explanation of why progress is blocked and what information is needed.
|
||||
|
||||
Avoid including full command strings, file contents, tokens, environment values, or other sensitive data in the message.
|
||||
|
||||
## State Management
|
||||
|
||||
- Store at most 32 signatures per session.
|
||||
- Prune older entries when the limit is exceeded.
|
||||
- Clear state for a session on `session.idle` and `session.deleted` events if those event types are available on the SDK event union; guard with type-safe narrowing so the plugin still compiles if event names differ.
|
||||
- The plugin should be safe under concurrent tool calls within one assistant turn: use synchronous in-memory updates and no external I/O in the hook.
|
||||
- Do not write per-call state to disk.
|
||||
|
||||
## Logging
|
||||
|
||||
Use `input.client.app.log()` when available, wrapped so a logging failure cannot block tool execution. Log only:
|
||||
|
||||
- session ID
|
||||
- detected cycle length
|
||||
- number of repetitions
|
||||
- tool names in the cycle (not arguments)
|
||||
- outcome `blocked`
|
||||
|
||||
Do not log argument values.
|
||||
|
||||
## Tests
|
||||
|
||||
Write tests that verify at least:
|
||||
|
||||
1. Single-tool repetition is blocked on the fourth identical call and allowed for the first three.
|
||||
2. A two-call cycle is blocked only after `A B A B A B`.
|
||||
3. A three-call cycle is blocked only after `A B C A B C A B C`.
|
||||
4. A four-call cycle is blocked only after one full 12-call sequence.
|
||||
5. Changing any argument in the repeated call prevents detection.
|
||||
6. Object key order does not affect equality.
|
||||
7. Two sessions do not share history.
|
||||
8. History is bounded to the configured maximum.
|
||||
9. Session cleanup removes state.
|
||||
|
||||
Tests should exercise the exported detection/canonicalization logic directly rather than relying on a live OpenCode session. Structure the plugin so pure functions are exported for tests while the default export remains the plugin entry point. Example:
|
||||
|
||||
```ts
|
||||
export function canonicalSignature(tool: string, args: unknown): string
|
||||
export function detectCycle(history: string[]): { length: number; repetitions: number } | null
|
||||
export function createLoopGuard(options?: { maxHistory?: number }): {
|
||||
record(sessionID: string, tool: string, args: unknown): { cycleLength: number; repetitions: number } | null
|
||||
clear(sessionID: string): void
|
||||
}
|
||||
```
|
||||
|
||||
Run the tests with Bun if available:
|
||||
|
||||
```sh
|
||||
bun test /Users/shane/.config/opencode/plugins/loop-guard.test.ts
|
||||
```
|
||||
|
||||
If `bun test` is unavailable, run the standalone script and record the exact command.
|
||||
|
||||
## Verification
|
||||
|
||||
- Type-check the plugin if a local TypeScript compiler is available; otherwise use Bun's loader/runtime as evidence.
|
||||
- Run the focused tests and capture pass/fail output in the implementation report.
|
||||
- Do not restart OpenCode or start a live model session.
|
||||
- Do not commit changes to any repository.
|
||||
- Do not modify files outside the global plugin directory and this task artifact directory.
|
||||
|
||||
## Report Requirements
|
||||
|
||||
`implementation.md` must include:
|
||||
|
||||
- task ID
|
||||
- status: complete, blocked, or no-change
|
||||
- files created or modified
|
||||
- detection algorithm summary
|
||||
- exact test command(s) and observed results
|
||||
- type-check or runtime evidence
|
||||
- known limitations
|
||||
- recommendation for the next review step
|
||||
@@ -0,0 +1,47 @@
|
||||
# Task Manifest
|
||||
|
||||
- Task: 20260831-105429-opencode-loop-guard
|
||||
- Created: 2026-08-31T10:54:29Z
|
||||
- Orchestrator: build
|
||||
- Status: planned
|
||||
- Current owner: coder
|
||||
|
||||
## Objective
|
||||
|
||||
Add a global OpenCode plugin that detects repeated tool-call cycles, blocks the call that completes a detected cycle, and provides corrective feedback to the model. The default behavior is balanced sensitivity, all models, and all tools.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- A global TypeScript plugin is installed at `~/.config/opencode/plugins/loop-guard.ts`.
|
||||
- No OpenCode configuration change or dependency install is required because the plugin is auto-discovered and uses only built-in APIs plus existing `@opencode-ai/plugin` types.
|
||||
- Tool-call history is tracked separately by session ID.
|
||||
- Detection covers cycle lengths 1 through 4.
|
||||
- A cycle is detected only after three complete repetitions, for a minimum of four calls in the repeating pattern.
|
||||
- Object argument key order does not affect signature equality; string values are preserved exactly and not normalized.
|
||||
- The call that completes a qualifying cycle is blocked with an error message that identifies the repeated pattern shape without leaking sensitive arguments and directs the model to reassess evidence and choose a materially different action.
|
||||
- Legitimate repeated calls with changed arguments are allowed.
|
||||
- Session state is bounded and cleared when sessions become idle or deleted.
|
||||
- Focused tests demonstrate cycle detection, false-positive avoidance, session isolation, key-order normalization, and history cleanup.
|
||||
- No secrets, credentials, or sensitive configuration values are added to the repository or artifacts.
|
||||
|
||||
## Artifact Index
|
||||
|
||||
- `brief.md`: implementation contract
|
||||
- `implementation.md`: coder report (pending)
|
||||
- `review.md`: reviewer report (pending)
|
||||
- `iterations/`: correction rounds, if needed
|
||||
- `attachments/`: raw logs and diagnostic collateral
|
||||
|
||||
## Iteration Log
|
||||
|
||||
- 2026-08-31T10:54:29Z: Build created task workspace and brief.
|
||||
|
||||
## Open Risks
|
||||
|
||||
- Throwing from `tool.execute.before` is the intended blocking mechanism, but runtime behavior should be verified after restart.
|
||||
- Cycle detection may still produce false positives for genuinely repetitive workflows; thresholds are intentionally conservative.
|
||||
- The plugin cannot recover a model that emits no further tool calls; it can only block repeated calls and provide corrective feedback.
|
||||
|
||||
## Final Outcome
|
||||
|
||||
Pending implementation and review.
|
||||
Reference in new issue
Block a user