Skip to content

[codex] Add toolset governance - #92

Merged
delete-cloud merged 7 commits into
mainfrom
codex-toolset-governance-pr3
May 3, 2026
Merged

delete-cloud merged 7 commits into
mainfrom
codex-toolset-governance-pr3

Conversation

@delete-cloud

@delete-cloud delete-cloud commented May 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add agentkit Toolset as the generic tool governance boundary for schema validation, approval, execution options, and result envelopes
  • route Pipeline tool schema loading, approval, and execution through one Toolset initialized for the run context
  • scope single-tool retries to the provider hook that failed, and document the UNHANDLED_TOOL_RESULT no-side-effect contract
  • fail closed when approval hook lookup/call/await or directive execution fails
  • document tool_timeout_seconds, tool_max_retries, approval fail-closed behavior, and the grep_search schema drift exposed by validation
  • update integration fixtures to use grep_search directory instead of the legacy path argument required before schema validation

Verification

  • uv run pytest tests/agentkit/tools/test_toolset.py::test_toolset_retries_only_the_failing_tool_provider tests/agentkit/runtime/test_pipeline.py::TestPipeline::test_mount_initializes_toolset_once -q
  • uv run pytest tests/agentkit/tools/test_toolset.py tests/agentkit/runtime/test_pipeline.py::TestPipeline::test_mount_initializes_toolset_once tests/integration/test_pipeline_e2e.py::TestPipelineE2E::test_large_tool_result_truncated -q
  • uv run pytest tests/agentkit/ -q
  • uv run pytest tests/coding_agent/test_pipeline_adapter.py tests/coding_agent/plugins/test_approval.py tests/integration/test_pipeline_e2e.py::TestPipelineE2E::test_large_tool_result_truncated tests/integration/test_pipeline_e2e.py::TestPipelineE2E::test_parallel_tools_execute_concurrently -q
  • uv run python -m py_compile src/agentkit/tools/toolset.py src/agentkit/runtime/pipeline.py tests/agentkit/tools/test_toolset.py tests/agentkit/runtime/test_pipeline.py tests/integration/test_pipeline_e2e.py
  • git diff --check

Known Existing Failure

  • uv run pytest tests/coding_agent/test_pipeline_adapter.py tests/coding_agent/plugins/test_approval.py tests/integration/test_pipeline_e2e.py -q still fails on test_subagent_turn_persisted_tape_flows_into_eval_adapter because coding-agent/data/eval/golden/parent-child-subagent-001.yaml is absent in this worktree. This PR does not touch that fixture path.

@coderabbitai

coderabbitai Bot commented May 2, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Pipeline tool handling was refactored around a new Toolset: pipeline stages now create/use ctx.toolset, convert streamed tool-call dicts into ToolCallRequest, validate and approve each call, and execute approved calls via Toolset.execute_tools (with timeout/retry options). Validation/approval failures emit immediate error tool_result entries.

Changes

Tool Governance & Pipeline Integration

Layer / File(s) Summary
Contracts & Types
coding-agent/src/agentkit/tools/toolset.py
Adds UNHANDLED_TOOL_RESULT, ToolProvider, ToolApprovalPolicy, ToolCallRequest, ToolValidationError, ToolApprovalResult, ToolExecutionOptions, ToolExecutionResult, and Toolset (schema collection, validation, approval, batch/solo execution, timeouts/retries, result envelopes).
Package Exports
coding-agent/src/agentkit/tools/__init__.py
Re-exports new toolset types and UNHANDLED_TOOL_RESULT by expanding __all__.
Pipeline Context & Loading
coding-agent/src/agentkit/runtime/pipeline.py
Adds PipelineContext.toolset; _stage_load_state initializes ctx.toolset and populates ctx.tool_schemas via toolset.collect_schemas() rather than runtime get_tools normalization.
Model Stage: Validation / Approval
coding-agent/src/agentkit/runtime/pipeline.py
_stage_run_model ensures ctx.toolset, converts streamed dicts → ToolCallRequest, runs toolset.validate_tool_call and toolset.approve_tool_call; validation/approval failures immediately append error tool_result and emit ToolResultEvent(is_error=True).
Model Stage: Execution & Results
coding-agent/src/agentkit/runtime/pipeline.py
Executes approved calls via toolset.execute_tools(..., ToolExecutionOptions) (timeout/max_retries from config); formats results, appends tape tool_result entries and emits ToolResultEvent using result.tool_call_id, result.name, and result.is_error; incremental-round checkpoint compares against len(executable_calls).
Hook Runtime Accessor
coding-agent/src/agentkit/runtime/hook_runtime.py
Adds HookRuntime.get_hooks(hook_name) -> list[Callable[..., Any]] to expose registered hook callables.
Core Plugins: unhandled sentinel
coding-agent/src/coding_agent/plugins/...
CoreToolsPlugin, MCP, and Skills plugins updated to return UNHANDLED_TOOL_RESULT for unknown tools (replacing None), enabling toolset-level missing/unhandled semantics.
Unit Tests: Toolset behavior
coding-agent/tests/agentkit/tools/test_toolset.py
Adds comprehensive tests covering single/batch execution envelopes, missing-tool marking, approval semantics (directives, executor integration, async hooks), schema validation (required/extra, types/enums/ranges, nested), retries, timeouts, None-result preservation, batch exceptions, and batch→single fallback.
Unit Tests: Pipeline validation
coding-agent/tests/agentkit/runtime/test_pipeline.py
Adds SchemaValidatedToolPlugin and test_run_turn_rejects_tool_call_with_invalid_schema_arguments to assert schema validation blocks execution and records validation errors (plugin not invoked).
Integration test updates
coding-agent/tests/integration/test_pipeline_e2e.py
Changes grep_search call args from path → directory; refactors large-result test to wrap core_tools.execute_tool via hook-index override instead of patching pipeline runtime.

Sequence Diagram

sequenceDiagram
    participant Pipeline as Pipeline (stage_run_model)
    participant Toolset as Toolset
    participant Runtime as HookRuntime
    participant Executor as DirectiveExecutor

    Pipeline->>Toolset: ensure/create toolset & collect_schemas()
    Pipeline->>Runtime: receive streamed model tool-call dicts
    loop per tool call
        Pipeline->>Pipeline: dict → ToolCallRequest
        Pipeline->>Toolset: validate_tool_call(request)
        alt validation fails
            Pipeline->>Pipeline: append tool_result (validation error) + emit ToolResultEvent(is_error)
        else
            Pipeline->>Toolset: approve_tool_call(request)
            Toolset->>Runtime: call approve_tool_call hooks
            Runtime-->>Toolset: Directive / decision
            alt directive present
                Toolset->>Executor: execute directive (if executor provided)
                Executor-->>Toolset: approval outcome
            end
            alt approval denied
                Pipeline->>Pipeline: append tool_result (approval error) + emit ToolResultEvent(is_error)
            else
                Pipeline->>Toolset: execute_tools([requests], options)
                alt batch hook used
                    Toolset->>Runtime: execute_tools_batch(payload)
                else
                    loop per call
                        Toolset->>Runtime: execute_tool(name, args)
                    end
                end
                Runtime-->>Toolset: execution results
                Toolset-->>Pipeline: ToolExecutionResult[]
                Pipeline->>Pipeline: normalize & append tool_result entries + emit ToolResultEvent
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I found a set of tools today,

Validated first, then sent away.
Approvals, retries, and timeouts too,
Each result taped — both false and true.
Hooray, the toolset hops into view!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title '[codex] Add toolset governance' directly summarizes the main change: introduction of a tool governance boundary (Toolset) that coordinates schema validation, approval, and execution.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex-toolset-governance-pr3

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coding-agent/src/agentkit/tools/toolset.py`:
- Around line 278-289: The conversion in _envelope_raw_result currently treats
any None as "missing", which misclassifies valid None results; change the
unhandled-case signal to an explicit sentinel or dedicated exception (e.g.,
UNHANDLED = object() or UnhandledToolResult) and update the logic in
_envelope_raw_result to set ToolExecutionResult.missing=True only when result is
that sentinel/exception, leaving actual None return values to be returned as
successful results in ToolExecutionResult.result; refer to ToolExecutionResult
and the _envelope_raw_result function (and ensure callers that previously used
None for unhandled now return the chosen sentinel/exception).
- Around line 214-223: The batch path currently calls
self._runtime.call_first("execute_tools_batch", ...) and awaits it via
self._await_if_needed without converting exceptions/timeouts into
ToolExecutionResult, so wrap the call/await to execute_tools_batch in a
try/except that mirrors _execute_one's error handling: catch exceptions and
timeouts from self._await_if_needed and construct a list of
ToolExecutionResult(error=...) entries (one per call) instead of letting the
exception bubble, ensuring batch_results is always a list of ToolExecutionResult
objects; reference the symbols execute_tools_batch, _runtime.call_first,
_await_if_needed, _execute_one, and ToolExecutionResult when implementing this
conversion and also handle unexpected non-list/None responses by mapping them to
error ToolExecutionResult instances.
- Around line 178-193: The approve_tool_call result is not awaited before
type-checking, so an async hook returned by
self._runtime.call_first("approve_tool_call", ...) will be treated as
non-Directive and fail-closed; update the code around self._runtime.call_first
to await the result (e.g., assign directive = await
self._runtime.call_first(...)) before checking isinstance(directive, Directive)
and before returning ToolApprovalResult instances, ensuring the async hook path
is handled the same as sync hooks and preserving existing checks involving
Directive and self._directive_executor.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ebaed29-e0a4-4534-9d5a-9d4ac6d75178

📥 Commits

Reviewing files that changed from the base of the PR and between 1799df2 and e65c672.

📒 Files selected for processing (6)
  • coding-agent/src/agentkit/runtime/pipeline.py
  • coding-agent/src/agentkit/tools/__init__.py
  • coding-agent/src/agentkit/tools/toolset.py
  • coding-agent/tests/agentkit/runtime/test_pipeline.py
  • coding-agent/tests/agentkit/tools/test_toolset.py
  • coding-agent/tests/integration/test_pipeline_e2e.py

Comment thread coding-agent/src/agentkit/tools/toolset.py Outdated
Comment thread coding-agent/src/agentkit/tools/toolset.py Outdated
Comment thread coding-agent/src/agentkit/tools/toolset.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coding-agent/src/agentkit/tools/toolset.py`:
- Around line 348-350: The current early-return treats any directive as approved
when self._directive_executor is None, which bypasses Reject/AskUser directives;
change the logic in the approval branch that references
self._directive_executor, directive, and ToolApprovalResult so that if
_directive_executor is None you fail-closed instead of approving—return a
ToolApprovalResult with approved=False (and include the directive and an
explanatory reason/message) or raise a clear exception indicating the missing
DirectiveExecutor, and otherwise continue to call await
self._directive_executor.execute(directive).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 651872a1-a716-4697-8164-1ddf3870916a

📥 Commits

Reviewing files that changed from the base of the PR and between 4896614 and edf35ff.

📒 Files selected for processing (2)
  • coding-agent/src/agentkit/tools/toolset.py
  • coding-agent/tests/agentkit/tools/test_toolset.py

Comment thread coding-agent/src/agentkit/tools/toolset.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
coding-agent/src/agentkit/tools/toolset.py (1)

418-422: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return error envelopes instead of raising on batch result-count mismatch.

On Line 419, this ValueError escapes execute_tools() and aborts the round, while other batch failures are already normalized via _error_results(...). Keep failure handling consistent and per-call.

Suggested fix
             if len(raw_results) != len(calls):
-                raise ValueError(
-                    "execute_tools_batch returned "
-                    f"{len(raw_results)} results for {len(calls)} tool calls"
-                )
+                return self._error_results(
+                    calls,
+                    ValueError(
+                        "execute_tools_batch returned "
+                        f"{len(raw_results)} results for {len(calls)} tool calls"
+                    ),
+                )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coding-agent/src/agentkit/tools/toolset.py` around lines 418 - 422, The
current length-check in execute_tools_batch raises a ValueError when
len(raw_results) != len(calls), which aborts the round; instead build and return
per-call error envelopes using the existing _error_results helper so failures
remain normalized. Replace the raise with code that constructs a clear error
message referencing raw_results and calls, then return _error_results(calls,
error_message) (or otherwise produce one error envelope per call) so
execute_tools sees consistent per-call results rather than an exception.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coding-agent/src/agentkit/tools/toolset.py`:
- Around line 397-407: The batch execution currently calls each batch hook only
once and returns on first failure; update the loop that invokes
hook(tool_calls=raw_payload, ctx=ctx) to honor ToolExecutionOptions.max_retries
by retrying the hook invocation up to options.max_retries times (matching the
retry behavior used by _execute_one): on exception catch and retry (optionally
with the same delay/backoff mechanism used by single-call paths if such
utilities exist), only call return self._error_results(calls, exc) after all
retry attempts are exhausted, and keep using self._await_if_needed(raw_results,
timeout_seconds=options.timeout_seconds) for each attempt; reference symbols to
change: raw_payload, hooks loop, hook(...) call, self._await_if_needed,
options.timeout_seconds, options.max_retries, and self._error_results(calls,
exc).

---

Duplicate comments:
In `@coding-agent/src/agentkit/tools/toolset.py`:
- Around line 418-422: The current length-check in execute_tools_batch raises a
ValueError when len(raw_results) != len(calls), which aborts the round; instead
build and return per-call error envelopes using the existing _error_results
helper so failures remain normalized. Replace the raise with code that
constructs a clear error message referencing raw_results and calls, then return
_error_results(calls, error_message) (or otherwise produce one error envelope
per call) so execute_tools sees consistent per-call results rather than an
exception.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2fb3546c-75f8-42be-80bc-33392398c3b8

📥 Commits

Reviewing files that changed from the base of the PR and between edf35ff and d90c555.

📒 Files selected for processing (2)
  • coding-agent/src/agentkit/tools/toolset.py
  • coding-agent/tests/agentkit/tools/test_toolset.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • coding-agent/tests/agentkit/tools/test_toolset.py

Comment thread coding-agent/src/agentkit/tools/toolset.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coding-agent/src/agentkit/tools/toolset.py`:
- Around line 329-360: Wrap all calls to approval hooks (the call to hook(...)
and the subsequent await via _await_if_needed) and the call to
self._directive_executor.execute(directive) with try/except so any exception is
caught locally; on exception return ToolApprovalResult(approved=False,
reason="policy") (or use str(getattr(directive,"reason","policy")) if the
exception occurs during directive execution and a directive exists).
Specifically, protect the invocation site of
self._runtime.get_hooks("approve_tool_call")/hook and the await of directive in
the loop, and protect the await of self._directive_executor.execute(directive);
log a warning including the exception type/name and tool name where helpful, but
ensure the function always returns a fail-closed ToolApprovalResult rather than
letting exceptions propagate. Ensure existing checks for isinstance(directive,
Directive) and missing self._directive_executor remain unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c9b62ee-772a-42bd-b22e-82cc85bfa867

📥 Commits

Reviewing files that changed from the base of the PR and between d90c555 and 5b0fb58.

📒 Files selected for processing (2)
  • coding-agent/src/agentkit/tools/toolset.py
  • coding-agent/tests/agentkit/tools/test_toolset.py

Comment thread coding-agent/src/agentkit/tools/toolset.py Outdated
Approval hook and directive executor exceptions should deny the tool call locally instead of aborting the turn.
Scope single-tool retries to the provider hook that failed, initialize Toolset once per run context, and document the governance semantics surfaced by PR review.
@delete-cloud
delete-cloud merged commit 08ab502 into main May 3, 2026
1 check passed
@delete-cloud
delete-cloud deleted the codex-toolset-governance-pr3 branch May 3, 2026 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant