An OpenCode plugin that lets a smaller/local model phone-a-friend (consult a stronger advisor model) when it gets stuck in an optimization or debugging loop. Hybrid stuck detection combines explicit experiment tracking via log_experiment with implicit heuristics (consecutive tool errors, no-progress plateau). Supports nudge mode (suggest advisor call) or ask mode (auto-call advisor).
- Hybrid stuck detection: Explicit tracking via
log_experimenttool + implicit heuristics - Two action modes:
nudge(default): Injects a message suggesting the agent callphone_a_friendask: Automatically calls the advisor and injects the response
- Flexible advisor configuration:
- OpenCode-native: use any provider/model from your OpenCode registry
- External API: use any OpenAI-compatible API directly
- Session-scoped state: Tracking resets per session; no persistence across sessions
- Rate limiting: Configurable minimum runs between calls and max calls per session
- Compaction-aware context: Preserves bounded Lifeline state during OpenCode session compaction
OpenCode plugins are loaded from .opencode/plugins/<name>/ directories (project-level or global).
# macOS / Linux
brew install opencode-ai
# Or via npm/pnpm/bun
npm install -g opencode-ai
# Verify
opencode --versionmkdir -p .opencode/plugins/opencode-lifeline# Project-level (recommended for per-project config)
cp -r src/* .opencode/plugins/opencode-lifeline/
# Or globally
cp -r src/* ~/.config/opencode/plugins/opencode-lifeline/For development, use a symlink so changes reload on restart:
ln -s $(pwd)/src ~/.config/opencode/plugins/opencode-lifelineCreate lifeline.json in one of the following locations (first found wins):
.opencode/lifeline.json— project-level~/.config/opencode/lifeline.json— global (XDG)~/.opencode/lifeline.json— global (legacy)
{
"auto": true,
"action": "nudge",
"minRunsBetweenCalls": 5,
"triggerAfterConsecutiveFailures": 3,
"triggerAfterPlateauRuns": 6,
"maxCallsPerSession": 10,
"advisor": {
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"maxTokens": 4096,
"temperature": 0.7
},
"implicitDetection": true,
"includeContext": true
}See lifeline.json.example for the full schema.
OpenCode loads plugins at startup. Restart to pick up the new plugin.
| Option | Type | Default | Description |
|---|---|---|---|
auto |
boolean |
true |
Enable automatic stuck detection |
action |
"nudge" | "ask" |
"nudge" |
nudge = suggest phone_a_friend; ask = auto-call advisor |
minRunsBetweenCalls |
number |
5 |
Minimum turns between advisor calls |
triggerAfterConsecutiveFailures |
number |
3 |
Trigger after N consecutive failures |
triggerAfterPlateauRuns |
number |
6 |
Trigger after N runs without improvement |
maxCallsPerSession |
number |
10 |
Max advisor calls per session |
advisor.provider |
string |
— | OpenCode provider ID (e.g., anthropic, openai) |
advisor.model |
string |
— | Model ID (e.g., claude-sonnet-4-20250514) |
advisor.maxTokens |
number |
4096 |
Max tokens for advisor response |
advisor.temperature |
number |
0.7 |
Temperature for advisor generation |
advisor.apiKey |
string |
— | API key for external advisor calls |
advisor.baseUrl |
string |
— | Base URL for external advisor (OpenAI-compatible) |
implicitDetection |
boolean |
true |
Enable implicit stuck detection |
includeContext |
boolean |
true |
Include session context in advisor prompts |
Advisor options can be overridden via environment variables:
export LIFELINE_ADVISOR_PROVIDER=openai
export LIFELINE_ADVISOR_MODEL=gpt-5
export LIFELINE_ADVISOR_API_KEY=sk-...
export LIFELINE_ADVISOR_BASE_URL=https://api.openai.com/v1
export LIFELINE_ADVISOR_MAX_TOKENS=4096
export LIFELINE_ADVISOR_TEMPERATURE=0.7Testing:
export LIFELINE_FAKE_RESPONSE="Test advisor response without spending tokens"Manually ask the advisor for help:
phone_a_friend
question: "We've tried three parser optimizations and all failed. What should we try next?"
mode: "next_experiment"
context: "Attempts: inline cache (segfault), arena reuse (no improvement), branchless scan (slower)"
max_ideas: 4
provider: "openai"
model: "gpt-5"
Modes:
ideas— brainstormingcritique— critique current approachdebug— debugging helpnext_experiment— what to try next (default)
Overrides:
provider— override the configured advisor provider for this callmodel— override the configured advisor model for this call
Log an experiment result for explicit tracking:
log_experiment
run: 5
metric: 142.3
status: "discard"
description: "Tried memoizing parser tokens — no improvement"
Status values:
keep— improvement keptdiscard— no improvement or regressioncrash— caused a crashchecks_failed— tests/checks failed
Logs are appended to autoresearch.jsonl in the current directory.
When the agent uses log_experiment to track optimization attempts, the plugin reads autoresearch.jsonl and triggers when:
- Consecutive failures: N
discard/crash/checks_failedin a row - Plateau: N runs without a
keep
Even without log_experiment, the plugin detects stuck patterns by monitoring:
- Consecutive tool errors: Repeated failures of the same tool
- No-progress plateau: Many turns without successful file edits/writes
- Run counter: Incremented on each
session.idleevent
Implicit detection only activates after a coding-relevant tool (bash, edit, write, grep, glob, read) has been used, preventing false triggers on purely conversational sessions.
OpenCode can compact long sessions. Lifeline adds a bounded summary to the experimental.session.compacting hook so continuation summaries retain useful stuck-detection context.
The summary includes run counts, advisor call count, the last trigger reason, recent advisor advice, recent autoresearch.jsonl runs, and recent failed tool names. It is truncated before injection to avoid bloating the compaction prompt.
-
nudgemode (default, recommended): Injects a context message suggesting the agent callphone_a_friend. Costs nothing until the agent decides to act. -
askmode: Automatically calls the advisor model and injects the response. Useful for unattended runs but consumes tokens immediately.
# Type-check
npm run check
# Test with fake response (no tokens spent)
export LIFELINE_FAKE_RESPONSE="Try measuring phase timings before further code changes."MIT