Skip to content

Long protocol runs on opencode have no context recovery path #60

Description

@pseudoseed

What happened

builder-spir-52 (SPIR, 4 phases, --harness opencode --model xai/grok-4.6) hit porch's context-refresh task at the phase_1 → phase_2 boundary and reported:

Context refresh refused at plan-phase:phase_2: harness opencode has no in-session reset. Continuing into phase 2.

That refusal is correct behaviour — afx refresh documents that it requires a harness with in-session context clearing and aborts loudly otherwise. The problem is what happens next.

Why it matters

Porch emits refresh tasks at phase boundaries because a multi-phase SPIR is expected to exhaust a context window. On Claude that is handled. On opencode it is not, and the protocol continues anyway, so the run proceeds with no mechanism to reclaim context and no plan for what happens when it runs out.

The recovery options are all blocked or unhelpful:

  • afx spawn <id> --resume reattaches the same conversation, so it does not help with exhaustion — that is exactly what afx refresh exists for.
  • afx cleanup + respawn is explicitly forbidden (irreversible-acts rule; it destroys the worktree).
  • So the only real fallback is a human decision, on a run that may be hours deep.

This matters more now rather than less: opencode is the lane you reach for when the Claude account is constrained, which means the longest, least-supervised runs are the ones most likely to be on the harness with no recovery path.

What I did as mitigation

Told the builder to commit and push at every phase boundary and keep codev/state/spir-52_thread.md current enough for a stranger, on the reasoning that a pushed branch and an honest thread log are the only artifacts that survive the builder. That is a workaround, not a fix, and it depends on the builder complying while its context is already degrading.

Options worth considering

  1. Refuse at spawn, not at the boundary. If a protocol has more than N phases and the harness has no in-session clear, say so at afx spawn time when the choice is still free, rather than mid-run when it is not.
  2. A supported handoff. A way to end one builder and start a fresh one on the same worktree and branch, carrying the thread log, without destroying anything. This is the missing primitive: --resume keeps the conversation, cleanup destroys the worktree, and nothing keeps the work while dropping the conversation.
  3. Warn loudly at the first refused refresh rather than logging and continuing, so the architect knows the run is now unprotected.

Option 2 looks like the real fix. Options 1 and 3 are cheap and would have surfaced this before a four-phase run was underway.

Found while running spec 52 (#52) on opencode.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/porchProtocol orchestratorarea/towerTower, afx, terminals, messaging

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions