V3 Issue #235 Typed Tool Results Implementation Plan
For agentic workers: Execute this plan task by task with test-first changes and a whole-branch independent review before delivery.
Goal: Complete R024, R025 and R031: tool outcomes and stable failures are machine-readable, while legacy text output is a presentation-only view.
Architecture: Add immutable ToolError/ToolResult contracts and a single renderer. Bind typed executors in each workspace registry while preserving text-facing built-in methods and mappings. Make the runtime consume ToolResult for receipts, observers, learning and MCP status; preserve existing authorization, scope and evidence gates.
Tech Stack: Python 3.12–3.13; stdlib dataclasses/JSON; current unittest suite; no new dependencies or paid provider calls.
Spec: GitHub issue #235 and the supplied OpenKyrozen V3 architecture proposal (R024, R025, R031); only #235 is in scope.
Global Constraints
- Preserve
CommandResult, built-in text APIs, MCP text content, approval requirements, workspace and subagent boundaries, usage accounting, receipt/evidence verification, and canonical registry metadata. - Native typed argument migration, provider serialization, receipt identity expansion, parallel scheduling and retries stay in their assigned issues.
- A custom/dynamic executor must return
ToolResult; invalid return values fail terminally withcontract_errorafter one invocation. - Never infer success from result text. Child work status is distinct from orchestration-call status.
Review Focus
- Successful content beginning with
Error:remains successful; failed Git/command results remain failed even when their text omits error markers. - Exceptions and returned
ToolErrorretain stable codes without exposing credentials or request bodies. - A tool that mutates state and returns an invalid result is not run a second time.
- Child failure reports do not make an orchestration query itself fail; malformed coordinator requests do.
- Changing display rendering cannot change authorization, receipts, observer events, learning statistics, MCP
isError, or plan acceptance.
Task 1: Result and error contracts
Files: openkyrozen/tools/models.py, openkyrozen/tools/__init__.py, tests/test_tool_results.py.
- [x] Write failing tests for frozen
ToolError/ToolResult,success, required code/message, JSON-safe data/metadata, defensive copies, and render behavior. - [x] Verify RED, then implement immutable records, validation, and
render_tool_result(result) -> strwhile retainingCommandResultunchanged. - [x] Define and document stable codes for invalid arguments, not found, permission/approval denial, unavailable dependency, timeout, cancellation, command failure, stale/conflict, execution error and contract error. Default retries to false; no retry behavior is added.
- [x] Run the focused tool-result contract tests; all pass on Python 3.12.
Task 2: Typed registry bindings and tool producers
Files: openkyrozen/tools/manifest.py, tool adapter modules, openkyrozen/agent/runtime.py, openkyrozen/agent/delegation_runtime.py, openkyrozen/memory/retrieval.py, tests/test_tool_registry.py, tests/test_tools.py.
- [x] Add regressions proving all 48 canonical specs bind typed executors; built-in text projections, registry metadata, and workspace isolation remain intact.
- [x] Add failure regressions for error-prefixed valid content, command status, browser snapshot failure, and invalid custom returns.
- [x] Migrate producers at the point status is known, preserving text and structured data and converting
CommandResultfields explicitly. - [x] Represent delegation invocation status separately from child statuses.
- [x] Bind typed executors in
ToolSpec; preserve text projections and require typed custom/dynamic results in the existing restricted namespace. - [x] Add terminal contract errors for wrong return types and assert single execution.
- [x] Run focused tool, registry, dynamic-tool, browser, and task suites on Python 3.12; 127 tests pass (one browser integration skip).
Task 3: Runtime and surface status propagation
Files: openkyrozen/agent/executor.py, openkyrozen/agent/planner.py, openkyrozen/agent/subagent_runtime.py, openkyrozen/learning/failures.py, openkyrozen/interfaces/mcp/server.py, CLI/Web render boundaries, relevant tests.
- [x] Add regressions showing result wording cannot change receipt, plugin, or learning status.
- [x] Derive runtime receipt success and error code, observer and learning status, and MCP
isErrorfrom typed outcomes while retaining text projections. - [x] Preserve independent effect and acceptance verification.
- [x] Remove tool status classification from shared execution; leave unused legacy text helper for compatibility.
- [x] Run focused executor, receipt, plugin, learning, MCP, subagent and surface suites on Python 3.12.
Task 4: Integration, docs and delivery
Files: docs/extensions.md, generated/API docs as needed, focused and integration tests.
- [x] Document the typed result contract, stable codes, custom/dynamic migration, text compatibility and non-retriable mutation failures; index the implementation plan.
- [x] Python 3.12
make test-core: 625 passed, 14 optional skips.make check,make lint,make docs-check, and offline agent/subagent acceptance pass. - [x] Python 3.13 focused tool/runtime suites: 123 pass; the three mocked browser manager tests also pass. Browser-enabled
make testreached 625 tests but its three browser flows require the unavailable Chromium executable. - [x] Independent review found a browser click/snapshot status-loss defect; fixed it and added a regression. Await automated PR reviews for any further findings.
- [ ] Create one signed PR, verify signatures locally and on GitHub, attach it, and wait for final-head CI/review. Leave the PR open and unmerged.
Assumptions and defaults
Built-in string contracts remain available to current direct callers; internal registry execution is typed. Unknown custom/dynamic return types fail closed after one call. retriable defaults false, with no automatic retry in this issue. Tool success, child-task success and verified acceptance remain three separate states.
Interface and compatibility
ToolResult exposes data, text, optional ToolError, metadata and derived success; ToolError exposes stable code, message, retriable and metadata. Existing built-in string methods and AVAILABLE_TOOLS calls continue through rendering wrappers. Custom/dynamic string-returning callables must migrate to ToolResult. Existing receipt/client text fields remain compatible; machine status comes from the typed result.