fix(pi): parse the opencode RPC event schema so turns actually deliver #81
No reviewers
Labels
No labels
epic
in-review
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
weiwen/evie!81
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fm/evie-pi-opencode-parser"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Evie's
pistdout parser (src/pi.rs) only understood pi's native streaming RPC schema:message_start/message_update/message_end+agent_end. It yielded a response only onmessage_end/agent_end; everything else fell through to theUnrecognized pi eventbranch and was dropped.The deployment runs pi 0.80.10 with
provider = "opencode"and streaming off, which emits pi's turn-oriented schema instead:agent_start,turn_start,turn_end,tool_execution_*,auto_retry_*,agent_settled,response. Verified against the live journal onmatsutake:agent_end/message_*occurred 0 times across Jul 6-30, whileturn_end/agent_settledaccounted for thousands of dropped events. Soread_agent_responsenever saw a recognized terminal event and could only exit via the 60s idle timeout, discarding 100% of turn output — the load-bearing cause of Evie not replying.The version question is resolved:
turn_*/agent_settledis pi 0.80.10's native RPC vocabulary (per its bundleddocs/rpc.md), not opencode-specific. Fixed in the parser rather than by pinning an oldpi.Fix
PiEvent/the parser now handle both schemas:turn_endcarries the turn's assistant message. A terminal turn (stopReasonstop/length) delivers its text as the final response; an intermediatetoolUseturn streams its thinking/text as live snapshots and keeps reading.agent_settledcompletes a run that produced nostopturn — the only completion signal on the pure-error path (retries exhausted).stopReason: "error"turn, an exhaustedauto_retry_end, or a rejectedpromptresponse(e.g. missing API key) records the error, returned as anErrthat flows through the existingexpose_errors-gated crash notice to the user.agent_settled(the benign marker trailing astopturn that already delivered) is skipped so the next turn can't desync.drain_abortedalso settles onagent_settled.message_*/agent_endpath is untouched and still works, so a streaming/native provider still functions.Recognized-but-noise events (
tool_execution_*,agent_start,queue_update,compaction_*, extension events) are matched explicitly soPiEvent::Other'sUnrecognized pi eventwarning stays a real schema-drift alarm.Regression test
The pre-existing tests asserted only the native
agent_end/message_endschema, so they passed while production was 100% broken. Added tests built from event streams captured verbatim from the production journal (pi 0.80.10, opencode):turn_start->tool_execution_*->turn_end[toolUse]->turn_end[stop]->agent_settled) asserts the final assistant text is delivered and the intermediate turn does not short-circuit;turn_end[error]x4 ->auto_retry_end{success:false}->agent_settled) asserts the error is surfaced;promptand a stray leadingagent_settled.All 479 tests pass; the native-schema tests still pass.
just check(fmt / clippy-D warnings/ test) is green.CI note: the initial run went red on a pre-existing failure unrelated to this change - session::tests::sandboxed_spawn_provisions_home_and_seeds_agents_md panics on the root Forgejo runner because provisioning resolves a non-root run-as target that fails closed under uid 0 (confirmed by reproducing under a uid-0 user namespace). Added a second commit that guards the test on a root runner, mirroring run_as::tests::resolve_maps_the_current_user_to_its_own_ids. This overlaps a fix in another in-flight branch; drop this commit if that lands in main first.