Files
lda-wf/docs/historical/superpowers/plans/2026-06-30-opencode-resume-metadata.md
T

936 lines
30 KiB
Markdown

# OpenCode Resume Metadata Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Store OpenCode session metadata first-class in challenge results and expose a safe way to resume incomplete trials with `opencode run --session`.
**Architecture:** Add a focused OpenCode resume helper module that extracts `sessionID` from JSONL stdout, builds continuation prompts from result state, and formats resume commands. The V2 runner records this metadata in every raw result; report projections render it for operators. A small CLI can print the resume command, and optionally execute it into a separate resume result file without mutating original raw evidence.
**Tech Stack:** Python 3.14, stdlib `json`/`subprocess`/`argparse`, existing `examples.agent_challenges` harness, pytest, Pydantic report DTOs.
---
## File Structure
- Create `examples/agent_challenges/opencode_resume.py`
- Owns `extract_session_id`, prompt selection, command construction, and resume-result path selection.
- No challenge-specific assertions here.
- Modify `examples/agent_challenges/runner.py`
- Stores `opencode` metadata in the raw result after stdout/stderr are known.
- Does not change task classification rules.
- Modify `examples/agent_challenges/report_models.py`
- Adds strict DTOs for bounded OpenCode metadata in machine reports.
- Modify `examples/agent_challenges/reports.py`
- Renders resume metadata and command in `final-report.md` / `*.report.md`.
- Create `examples/agent_challenges/resume_trial.py`
- CLI for printing or executing a resume from one raw result JSON.
- Modify `tests/examples/test_agent_challenge_harness_v2.py`
- Runner-level coverage for storing metadata.
- Modify `tests/examples/test_agent_challenge_reports.py`
- Report rendering and machine projection coverage.
- Create `tests/examples/test_agent_challenge_resume.py`
- Unit tests for session extraction, prompt selection, command construction, and CLI behavior.
- Modify `docs/runbooks/agent-challenge-evaluation.md`
- Adds operator instructions for incomplete/timeouts and resume.
- Modify `docs/current_roadmap.md`
- Records completion when implementation is done.
---
### Task 1: Add OpenCode Resume Helper
**Files:**
- Create: `examples/agent_challenges/opencode_resume.py`
- Test: `tests/examples/test_agent_challenge_resume.py`
- [ ] **Step 1: Write failing tests for session extraction and prompt selection**
Create `tests/examples/test_agent_challenge_resume.py`:
```python
from __future__ import annotations
import json
from pathlib import Path
from examples.agent_challenges.opencode_resume import (
build_resume_command,
extract_session_id,
resume_prompt_for_result,
resume_result_path,
)
def _event(**payload: object) -> str:
return json.dumps(payload, separators=(",", ":"))
def test_extract_session_id_reads_top_level_session_id() -> None:
stdout = "\n".join(
[
_event(type="step_start", sessionID="ses_abc"),
_event(type="text", sessionID="ses_def", text="later"),
]
)
assert extract_session_id(stdout) == "ses_abc"
def test_extract_session_id_reads_nested_part_session_id() -> None:
stdout = _event(type="step_start", part={"sessionID": "ses_nested"})
assert extract_session_id(stdout) == "ses_nested"
def test_extract_session_id_returns_none_for_empty_or_malformed_stdout() -> None:
assert extract_session_id("") is None
assert extract_session_id("not json\n{}") is None
def test_resume_prompt_asks_continue_for_timeout_with_partial_stdout() -> None:
prompt = resume_prompt_for_result(
{
"task_outcome": "timeout",
"stdout": _event(type="step_start", sessionID="ses_abc"),
}
)
assert "continue" in prompt.lower()
assert "do not restart" in prompt.lower()
def test_resume_prompt_asks_for_final_report_when_work_is_done_but_report_missing() -> None:
prompt = resume_prompt_for_result(
{
"task_outcome": "failed",
"assertion_failures": [
"could not extract challenge report for required_fields evaluation"
],
"stdout": _event(type="text", sessionID="ses_abc", text="run completed"),
}
)
assert "do not continue coding" in prompt.lower()
assert "challenge_report" in prompt
def test_build_resume_command_includes_attach_session_model_variant_and_prompt() -> None:
command = build_resume_command(
session_id="ses_abc",
attach_url="http://127.0.0.1:8192/",
model="opencode/deepseek-v4-flash-free",
variant="max",
prompt="continue?",
)
assert command == [
"opencode",
"run",
"--session",
"ses_abc",
"--attach",
"http://127.0.0.1:8192/",
"--format",
"json",
"--model",
"opencode/deepseek-v4-flash-free",
"--variant",
"max",
"continue?",
]
def test_resume_result_path_uses_next_resume_index(tmp_path: Path) -> None:
original = tmp_path / "trial.json"
original.write_text("{}", encoding="utf-8")
(tmp_path / "trial.resume-001.json").write_text("{}", encoding="utf-8")
assert resume_result_path(original).name == "trial.resume-002.json"
```
- [ ] **Step 2: Run tests to verify they fail**
Run:
```bash
uv run pytest tests/examples/test_agent_challenge_resume.py -q
```
Expected: fails because `examples.agent_challenges.opencode_resume` does not exist.
- [ ] **Step 3: Implement the helper module**
Create `examples/agent_challenges/opencode_resume.py`:
```python
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
FINAL_REPORT_PROMPT = (
"Your workflow attempt is over. Do not continue coding. Return only the "
"final challenge report using the required challenge_report YAML schema. "
"Include run_id, evidence, failed attempts, read flags, missed requirements, "
"and whether the run succeeded."
)
CONTINUE_PROMPT = (
"Continue this same trial from the current session. Do not restart in a new "
"workspace. If the workflow is already complete, stop and return only the "
"final challenge_report YAML using the required schema."
)
def _event_session_id(event: dict[str, Any]) -> str | None:
session_id = event.get("sessionID")
if isinstance(session_id, str) and session_id:
return session_id
part = event.get("part")
if isinstance(part, dict):
nested = part.get("sessionID")
if isinstance(nested, str) and nested:
return nested
return None
def extract_session_id(stdout: str) -> str | None:
"""Return the first OpenCode session id found in JSONL stdout."""
for line in stdout.splitlines():
if not line.strip():
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if not isinstance(event, dict):
continue
session_id = _event_session_id(event)
if session_id is not None:
return session_id
return None
def resume_prompt_for_result(result: dict[str, object]) -> str:
"""Choose a continuation prompt from the result failure shape."""
task_outcome = result.get("task_outcome")
assertion_failures = result.get("assertion_failures")
failures = assertion_failures if isinstance(assertion_failures, list) else []
if task_outcome == "timeout":
return CONTINUE_PROMPT
if any("could not extract challenge report" in str(item) for item in failures):
return FINAL_REPORT_PROMPT
if result.get("parsed") is None and result.get("stdout"):
return FINAL_REPORT_PROMPT
return CONTINUE_PROMPT
def build_resume_command(
*,
session_id: str,
attach_url: str | None,
model: str,
variant: str,
prompt: str,
) -> list[str]:
command = ["opencode", "run", "--session", session_id]
if attach_url is not None:
command.extend(["--attach", attach_url])
command.extend(["--format", "json", "--model", model, "--variant", variant, prompt])
return command
def resume_result_path(result_path: Path) -> Path:
stem = result_path.with_suffix("")
index = 1
while True:
candidate = stem.with_name(f"{stem.name}.resume-{index:03d}.json")
if not candidate.exists():
return candidate
index += 1
```
- [ ] **Step 4: Run tests to verify they pass**
Run:
```bash
uv run pytest tests/examples/test_agent_challenge_resume.py -q
```
Expected: all tests pass.
- [ ] **Step 5: Commit**
```bash
git add examples/agent_challenges/opencode_resume.py tests/examples/test_agent_challenge_resume.py
git commit -m "feat: add opencode resume helpers"
```
---
### Task 2: Store Resume Metadata In Raw V2 Results
**Files:**
- Modify: `examples/agent_challenges/runner.py`
- Test: `tests/examples/test_agent_challenge_harness_v2.py`
- [ ] **Step 1: Add failing runner tests**
Append to `tests/examples/test_agent_challenge_harness_v2.py`:
```python
def test_v2_runner_stores_opencode_resume_metadata(tmp_path: Path) -> None:
from subprocess import CompletedProcess
from examples.agent_challenges.models import InstructionProfile
from examples.agent_challenges.runner import run_v2_trial
challenge = load_challenge_manifest(_write_manifest(tmp_path / "challenge"))
stdout = (
'{"type":"step_start","sessionID":"ses_runner"}\n'
'{"type":"text","sessionID":"ses_runner","text":"```yaml\\n'
'challenge_report:\\n'
' used_product_path: true\\n'
' used_helper_script: false\\n'
' before_clicked: false\\n'
' after_clicked: true\\n'
' run_failed: false\\n'
' leftover_processes: false\\n'
'```"}\n'
)
def fake_run(*args: object, **kwargs: object) -> CompletedProcess[str]:
return CompletedProcess(args=args, returncode=0, stdout=stdout, stderr="")
result = run_v2_trial(
challenge,
profile=InstructionProfile.NONE,
model="opencode/deepseek-v4-flash-free",
variant="max",
index=1,
results_dir=tmp_path / "results",
workspaces_dir=tmp_path / "workspaces",
attach_url="http://127.0.0.1:8192/",
run_fn=fake_run,
)
opencode = result["opencode"]
assert opencode["session_id"] == "ses_runner"
assert opencode["attach_url"] == "http://127.0.0.1:8192/"
assert opencode["model"] == "opencode/deepseek-v4-flash-free"
assert opencode["variant"] == "max"
assert opencode["resume_command"][0:4] == [
"opencode",
"run",
"--session",
"ses_runner",
]
assert "challenge_report" in opencode["resume_prompt"]
def test_v2_runner_stores_null_session_when_stdout_has_no_session(tmp_path: Path) -> None:
from subprocess import CompletedProcess
from examples.agent_challenges.models import InstructionProfile
from examples.agent_challenges.runner import run_v2_trial
challenge = load_challenge_manifest(_write_manifest(tmp_path / "challenge"))
def fake_run(*args: object, **kwargs: object) -> CompletedProcess[str]:
return CompletedProcess(args=args, returncode=1, stdout="", stderr="boom")
result = run_v2_trial(
challenge,
profile=InstructionProfile.NONE,
model="opencode/deepseek-v4-flash-free",
variant="max",
index=1,
results_dir=tmp_path / "results",
workspaces_dir=tmp_path / "workspaces",
run_fn=fake_run,
)
opencode = result["opencode"]
assert opencode["session_id"] is None
assert opencode["resume_command"] is None
```
These tests use the existing `_write_manifest` helper and the already-imported
`load_challenge_manifest` function at the top of the file. Do not invent a
second fixture.
- [ ] **Step 2: Run tests to verify they fail**
Run:
```bash
uv run pytest tests/examples/test_agent_challenge_harness_v2.py::test_v2_runner_stores_opencode_resume_metadata tests/examples/test_agent_challenge_harness_v2.py::test_v2_runner_stores_null_session_when_stdout_has_no_session -q
```
Expected: fails because `result["opencode"]` is missing.
- [ ] **Step 3: Store metadata in `run_v2_trial`**
In `examples/agent_challenges/runner.py`, import helpers near the other challenge harness imports:
```python
from examples.agent_challenges.opencode_resume import (
build_resume_command,
extract_session_id,
resume_prompt_for_result,
)
```
After `report_paths` is defined and before `result` is built, add:
```python
opencode_session_id = extract_session_id(stdout)
```
After `result` is built, before optional `assertion_failures` are attached, add:
```python
resume_prompt = resume_prompt_for_result(result)
result["opencode"] = {
"attach_url": attach_url,
"command": command,
"model": model,
"variant": variant,
"session_id": opencode_session_id,
"resume_prompt": resume_prompt,
"resume_command": (
build_resume_command(
session_id=opencode_session_id,
attach_url=attach_url,
model=model,
variant=variant,
prompt=resume_prompt,
)
if opencode_session_id is not None
else None
),
}
```
Keep `model` and `variant` at top-level too; this is additive metadata for operators.
- [ ] **Step 4: Run tests to verify they pass**
Run:
```bash
uv run pytest tests/examples/test_agent_challenge_harness_v2.py::test_v2_runner_stores_opencode_resume_metadata tests/examples/test_agent_challenge_harness_v2.py::test_v2_runner_stores_null_session_when_stdout_has_no_session -q
```
Expected: both pass.
- [ ] **Step 5: Commit**
```bash
git add examples/agent_challenges/runner.py tests/examples/test_agent_challenge_harness_v2.py
git commit -m "feat: store opencode resume metadata"
```
---
### Task 3: Render Resume Metadata In Reports
**Files:**
- Modify: `examples/agent_challenges/report_models.py`
- Modify: `examples/agent_challenges/reports.py`
- Test: `tests/examples/test_agent_challenge_reports.py`
- [ ] **Step 1: Write failing report projection test**
Append to `tests/examples/test_agent_challenge_reports.py`:
```python
def test_trial_report_renders_opencode_resume_metadata(tmp_path: Path) -> None:
from examples.agent_challenges.report_models import build_trial_report
from examples.agent_challenges.reports import render_trial_report_markdown
result = _raw_result(tmp_path)
result["opencode"] = {
"attach_url": "http://127.0.0.1:8192/",
"command": ["opencode", "run", "--format", "json", "prompt"],
"model": "opencode/deepseek-v4-flash-free",
"variant": "max",
"session_id": "ses_report",
"resume_prompt": "continue?",
"resume_command": [
"opencode",
"run",
"--session",
"ses_report",
"--attach",
"http://127.0.0.1:8192/",
"--format",
"json",
"--model",
"opencode/deepseek-v4-flash-free",
"--variant",
"max",
"continue?",
],
}
report = build_trial_report(result, audit=None)
rendered = render_trial_report_markdown(report)
machine = report.model_dump(mode="json")
assert machine["opencode"]["session_id"] == "ses_report"
assert "## OpenCode Resume" in rendered
assert "ses_report" in rendered
assert "opencode run --session ses_report" in rendered
```
Use the existing `_raw_result` helper in the file. Do not create duplicate
minimal result setup.
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
uv run pytest tests/examples/test_agent_challenge_reports.py::test_trial_report_renders_opencode_resume_metadata -q
```
Expected: fails because `TrialReport` forbids extra `opencode`.
- [ ] **Step 3: Add report DTOs**
In `examples/agent_challenges/report_models.py`, add after `TrialIdentity`:
```python
class OpenCodeRunMetadata(StrictReportModel):
attach_url: str | None = None
command: list[str] = Field(default_factory=list)
model: str = ""
variant: str = ""
session_id: str | None = None
resume_prompt: str = ""
resume_command: list[str] | None = None
```
Add field to `TrialReport`:
```python
opencode: OpenCodeRunMetadata | None = None
```
Add helper:
```python
def _build_opencode_metadata(result: dict[str, object]) -> OpenCodeRunMetadata | None:
raw = result.get("opencode")
if not isinstance(raw, dict):
return None
resume_command = raw.get("resume_command")
return OpenCodeRunMetadata(
attach_url=_str_none(raw.get("attach_url")),
command=_list_str(raw.get("command")),
model=_str(raw.get("model")),
variant=_str(raw.get("variant")),
session_id=_str_none(raw.get("session_id")),
resume_prompt=_str(raw.get("resume_prompt")),
resume_command=_list_str(resume_command)
if isinstance(resume_command, list)
else None,
)
```
In `_build_trial_report`, pass:
```python
opencode=_build_opencode_metadata(result),
```
- [ ] **Step 4: Render markdown section**
In `examples/agent_challenges/reports.py`, add a helper near other render helpers:
```python
def _shell_join(command: list[str]) -> str:
return " ".join(command)
```
Then in `render_trial_report_markdown`, after the Outcome section and before Agent Self-Report, add:
```python
if report.opencode is not None:
lines.append("## OpenCode Resume")
lines.append("")
if report.opencode.session_id:
lines.append(f"- Session: `{report.opencode.session_id}`")
else:
lines.append("- Session: not captured")
if report.opencode.attach_url:
lines.append(f"- Attach URL: `{report.opencode.attach_url}`")
if report.opencode.resume_command:
lines.append("")
lines.append("```powershell")
lines.append(_shell_join(report.opencode.resume_command))
lines.append("```")
if report.opencode.resume_prompt:
lines.append("")
lines.append("Resume prompt:")
lines.append("")
lines.append("```text")
lines.append(report.opencode.resume_prompt)
lines.append("```")
lines.append("")
```
This uses simple shell rendering for readability. The authoritative machine form remains the JSON list in `*.report.json`.
- [ ] **Step 5: Run test to verify it passes**
Run:
```bash
uv run pytest tests/examples/test_agent_challenge_reports.py::test_trial_report_renders_opencode_resume_metadata -q
```
Expected: passes.
- [ ] **Step 6: Commit**
```bash
git add examples/agent_challenges/report_models.py examples/agent_challenges/reports.py tests/examples/test_agent_challenge_reports.py
git commit -m "feat: show opencode resume metadata in reports"
```
---
### Task 4: Add `resume_trial.py` CLI
**Files:**
- Create: `examples/agent_challenges/resume_trial.py`
- Test: `tests/examples/test_agent_challenge_resume.py`
- Modify: `docs/runbooks/agent-challenge-evaluation.md`
- [ ] **Step 1: Write failing CLI tests**
Append to `tests/examples/test_agent_challenge_resume.py`:
```python
def test_resume_trial_prints_resume_command(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
from examples.agent_challenges.resume_trial import main
result_path = tmp_path / "trial.json"
result_path.write_text(
json.dumps(
{
"model": "opencode/deepseek-v4-flash-free",
"variant": "max",
"opencode": {
"attach_url": "http://127.0.0.1:8192/",
"session_id": "ses_cli",
"resume_prompt": "continue?",
"resume_command": [
"opencode",
"run",
"--session",
"ses_cli",
"--attach",
"http://127.0.0.1:8192/",
"--format",
"json",
"--model",
"opencode/deepseek-v4-flash-free",
"--variant",
"max",
"continue?",
],
},
}
),
encoding="utf-8",
)
assert main(["--from-result", str(result_path), "--print-command"]) == 0
output = capsys.readouterr().out
assert "opencode run --session ses_cli" in output
def test_resume_trial_run_writes_resume_result(tmp_path: Path) -> None:
from subprocess import CompletedProcess
from examples.agent_challenges.resume_trial import resume_from_result
result_path = tmp_path / "trial.json"
result_path.write_text(
json.dumps(
{
"model": "opencode/deepseek-v4-flash-free",
"variant": "max",
"opencode": {
"attach_url": None,
"session_id": "ses_cli",
"resume_prompt": "continue?",
"resume_command": [
"opencode",
"run",
"--session",
"ses_cli",
"--format",
"json",
"--model",
"opencode/deepseek-v4-flash-free",
"--variant",
"max",
"continue?",
],
},
}
),
encoding="utf-8",
)
calls: list[list[str]] = []
def fake_run(command: list[str], **kwargs: object) -> CompletedProcess[str]:
calls.append(command)
return CompletedProcess(command, 0, stdout='{"type":"text","text":"done"}\n', stderr="")
output_path = resume_from_result(result_path, run_fn=fake_run)
assert output_path.name == "trial.resume-001.json"
assert calls[0][0:4] == ["opencode", "run", "--session", "ses_cli"]
payload = json.loads(output_path.read_text(encoding="utf-8"))
assert payload["source_result_path"] == str(result_path.resolve())
assert payload["stdout"].strip()
```
Add `import pytest` at the top of the file if missing.
- [ ] **Step 2: Run tests to verify they fail**
Run:
```bash
uv run pytest tests/examples/test_agent_challenge_resume.py::test_resume_trial_prints_resume_command tests/examples/test_agent_challenge_resume.py::test_resume_trial_run_writes_resume_result -q
```
Expected: fails because `resume_trial.py` does not exist.
- [ ] **Step 3: Implement CLI**
Create `examples/agent_challenges/resume_trial.py`:
```python
from __future__ import annotations
import argparse
import json
import subprocess
import sys
import time
from collections.abc import Callable
from pathlib import Path
from typing import Any
try:
from .opencode_resume import resume_result_path
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from examples.agent_challenges.opencode_resume import resume_result_path
RunFn = Callable[..., subprocess.CompletedProcess[str]]
def _load_command(result: dict[str, Any]) -> list[str]:
opencode = result.get("opencode")
if not isinstance(opencode, dict):
raise ValueError("result has no opencode metadata")
command = opencode.get("resume_command")
if not isinstance(command, list) or not all(isinstance(part, str) for part in command):
raise ValueError("result has no resume_command; session id may be missing")
return command
def _display_command(command: list[str]) -> str:
return " ".join(command)
def resume_from_result(
result_path: Path,
*,
run_fn: RunFn = subprocess.run,
) -> Path:
result = json.loads(result_path.read_text(encoding="utf-8"))
if not isinstance(result, dict):
raise ValueError("result file must contain a JSON object")
command = _load_command(result)
started = time.monotonic()
completed = run_fn(
command,
text=True,
capture_output=True,
check=False,
encoding="utf-8",
errors="replace",
)
payload = {
"harness_version": "v2-resume",
"source_result_path": str(result_path.resolve()),
"command": command,
"duration_seconds": round(time.monotonic() - started, 3),
"returncode": completed.returncode,
"stdout": completed.stdout or "",
"stderr": completed.stderr or "",
}
output_path = resume_result_path(result_path)
output_path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
return output_path
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Print or run an OpenCode trial resume command.")
parser.add_argument("--from-result", type=Path, required=True)
parser.add_argument("--print-command", action="store_true")
parser.add_argument("--run", action="store_true")
args = parser.parse_args(argv)
try:
result = json.loads(args.from_result.read_text(encoding="utf-8"))
if not isinstance(result, dict):
raise ValueError("result file must contain a JSON object")
command = _load_command(result)
if args.run:
print(resume_from_result(args.from_result).as_posix())
else:
print(_display_command(command))
except ValueError as exc:
parser.error(str(exc))
return 0
if __name__ == "__main__":
raise SystemExit(main())
```
Default behavior prints the command; `--print-command` is accepted for clarity but not required. Execution writes a sidecar `.resume-NNN.json`; it never overwrites the original result.
- [ ] **Step 4: Run tests to verify they pass**
Run:
```bash
uv run pytest tests/examples/test_agent_challenge_resume.py -q
```
Expected: all resume tests pass.
- [ ] **Step 5: Update runbook**
In `docs/runbooks/agent-challenge-evaluation.md`, add after “Save A Manual Audit”:
```md
## Resume An Incomplete OpenCode Trial
When a result captures an OpenCode `sessionID`, the report includes an
`OpenCode Resume` section. Print the resume command with:
```powershell
uv run python examples/agent_challenges/resume_trial.py `
--from-result examples/agent_challenges/browser_click_challenge/results/opencode_deepseek-v4-flash-free-trial-034.json
```
Run the resume and save a sidecar result with:
```powershell
uv run python examples/agent_challenges/resume_trial.py `
--from-result examples/agent_challenges/browser_click_challenge/results/opencode_deepseek-v4-flash-free-trial-034.json `
--run
```
The resume command writes `*.resume-001.json` beside the original result and
does not mutate the original raw result. Use manual audit to decide whether the
resumed output completes the trial or only provides additional evidence.
```
- [ ] **Step 6: Commit**
```bash
git add examples/agent_challenges/resume_trial.py tests/examples/test_agent_challenge_resume.py docs/runbooks/agent-challenge-evaluation.md
git commit -m "feat: add opencode trial resume cli"
```
---
### Task 5: Final Verification And Roadmap
**Files:**
- Modify: `docs/current_roadmap.md`
- Move: `docs/superpowers/plans/2026-06-30-opencode-resume-metadata.md` to `docs/historical/superpowers/plans/2026-06-30-opencode-resume-metadata.md`
- [ ] **Step 1: Update roadmap**
Add under the active agent evaluation area in `docs/current_roadmap.md`:
```md
- Completed: agent challenge results now record OpenCode session metadata and
resume commands, so incomplete provider runs can be continued without
mutating original raw evidence.
```
- [ ] **Step 2: Run focused tests**
Run:
```bash
uv run pytest tests/examples/test_agent_challenge_resume.py tests/examples/test_agent_challenge_harness_v2.py tests/examples/test_agent_challenge_reports.py -q
```
Expected: all selected tests pass.
- [ ] **Step 3: Run lint and typecheck**
Run:
```bash
uv run ruff check examples/agent_challenges tests/examples
uv run ruff format --check examples/agent_challenges tests/examples
uv run basedpyright --level error examples/agent_challenges tests/examples
```
Expected: ruff clean, format clean, basedpyright 0 errors.
- [ ] **Step 4: Smoke print against a real result**
Run:
```bash
uv run python examples/agent_challenges/resume_trial.py --from-result examples/agent_challenges/browser_click_challenge/results/opencode_deepseek-v4-flash-free-trial-034.json
```
Expected: prints an `opencode run --session ...` command if that result still exists and contains a session id. If the result was cleaned up locally, use any current V2 result that has `opencode.session_id`.
- [ ] **Step 5: Archive this plan**
Run:
```bash
git mv docs/superpowers/plans/2026-06-30-opencode-resume-metadata.md docs/historical/superpowers/plans/2026-06-30-opencode-resume-metadata.md
```
- [ ] **Step 6: Commit**
```bash
git add docs/current_roadmap.md docs/historical/superpowers/plans/2026-06-30-opencode-resume-metadata.md
git commit -m "docs: record opencode resume metadata support"
```
---
## Self-Review
- Spec coverage: The plan stores `attach_url`, `opencode_session_id`, exact command, resume prompt, and resume command. It handles “continue” versus “final report only” prompt selection from result state. It adds report visibility and a CLI for printing/running resume commands.
- Placeholder scan: No TODO/TBD placeholders remain. Each code-changing step includes concrete code and commands.
- Type consistency: The raw result key is `opencode`, with `session_id`, `attach_url`, `command`, `model`, `variant`, `resume_prompt`, and `resume_command`. The report DTO uses the same names. The CLI reads the same `opencode.resume_command`.