Files
cleveragents-core/tools/controller
drew bcc59d38af fix(controller): trial-path output channel — harvest inline JSON + legacy-shape adapter
Trial run-1 surfaced the real failure mode: the existing agent
prompts emit their result as a single JSON object in the FINAL
assistant message (the legacy dispatch_implementer/dispatch_review
contract). They DON'T call the controller's response-builder MCPs
(not registered in opencode.json) and they DON'T write a file. The
worker's canonical-output poller timed out every attempt.

Fix (two pieces):

1. INLINE JSON HARVEST
   ``_opencode_worker.run_session_blocking`` already extracts the
   last JSON object from the agent's final response into
   ``SessionResult.parsed_json``. The controller's opencode_session
   adapter was ignoring it.
   - ``opencode_session.py:run_opencode_session`` now accepts an
     ``inline_output_callback`` kwarg. After the session completes,
     if parsed_json is set, the callback fires.
   - ``agent_runner.py`` provides the callback: writes the parsed
     JSON to the canonical-output path so the existing poller picks
     it up uniformly with the MCP + file-write channels.

2. LEGACY → V1 SHAPE ADAPTER (``worker/legacy_adapter.py``)
   The legacy JSON shape doesn't match V1 contracts:
   - estimator: legacy {recommended_tier, is_confident, reasoning}
     vs V1 {output_version, recommended_tier, is_metadata_only,
     confidence, reasoning, wallclock_seconds}
   - implementer: legacy {outcome: resolved|unresolved, ...} vs V1
     {outcome: resolved|rebase-failed|blocked|noop|competence-failure,
     commit_shas, blockers, used_tier, ...}
   - reviewer: legacy {verdict, ...} vs V1 {output_version, verdict,
     blocking_issues, suggested_next_action, ...}

   ``adapt_to_v1(role, payload, tier, wallclock_seconds)`` normalizes
   each role's legacy shape into the corresponding V1 dict:
   - adds ``output_version="V1"``
   - maps outcome enums (``unresolved`` → ``blocked``)
   - synthesizes missing required fields with sensible defaults
   - coerces singular commit_sha → list[commit_shas]
   - converts legacy is_confident bool → confidence string

   Already-V1 payloads pass through unchanged. The agent_runner
   wraps the inline_output_callback to run the adapter before
   writing to the canonical path.

Tests (+12 in test_legacy_adapter.py):
- Each role's adapt: legacy in, V1-validating-via-strict_parse out
- Already-V1 passthrough
- Outcome enum mappings + invalid-outcome fallback
- Singular commit_sha normalization
- Non-dict / unknown-role passthrough (lets strict_parse raise)

Total: 738 controller tests pass (+12 net), 0 regressions.

Long-term: update agent prompts to emit V1 directly, register the
MCPs in opencode.json. For the trial, this adapter lets the existing
agents flow through the new controller unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:56:47 -04:00
..