PHP code example of milpa / tool-runtime

1. Go to this page and download the library: Download milpa/tool-runtime library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

milpa / tool-runtime example snippets


use Milpa\ToolRuntime\Attributes\Param;
use Milpa\ToolRuntime\Attributes\Tool;
use Milpa\ToolRuntime\ToolResult;

final class NoteTools
{
    #[Tool('list_notes', 'List saved notes', scopes: ['notes:read'])]
    public function listNotes(
        #[Param('Page number', clamp: [1, 1000])] int $page = 1
    ): ToolResult {
        return ToolResult::success(['notes' => [], 'page' => $page]);
    }
}

use Milpa\ToolRuntime\Contracts\ToolContext;
use Milpa\ToolRuntime\ToolRegistry;
use Milpa\ToolRuntime\ToolScanner;
use Psr\Log\NullLogger;

$registry = new ToolRegistry(new NullLogger());
(new ToolScanner($registry))->scan(new NoteTools());

$result = $registry->call('list_notes', ['page' => 1], ToolContext::cli());

$result->success;  // true
$result->data;     // ['notes' => [], 'page' => 1]
$result->toJson();  // {"success":true,"data":{...},"message":null,"error":null,"meta":{...}}

#[Tool('update_post', 'Update fields on a post')]
public function updatePost(
    int $post_id,
    #[Param('Fields to update', type: 'object', properties: [
        'title' => ['type' => 'string'],
        'body' => ['type' => 'string'],
    ])]
    array $updates
): ToolResult {
    // $updates arrives as a plain associative array — ['title' => ..., 'body' => ...] —
    // no manual json_decode() needed; the host's JSON transport already decoded it that way.
    return ToolResult::success(['post_id' => $post_id, 'updates' => $updates]);
}

use Milpa\Events\InterceptionSlot;
use Milpa\ToolRuntime\Events\ToolExecutingEvent;
use Milpa\ToolRuntime\ToolResult;

$dispatcher->subscribe('tool.executing', function (string $eventName, array $payload): void {
    /** @var ToolExecutingEvent $event */
    $event = $payload['event'];
    /** @var InterceptionSlot $slot */
    $slot = $payload['slot'];

    $cached = $myCache->get($event->name, $event->args);
    if ($cached !== null) {
        // Short-circuit: the real callback never runs; ToolRegistry::call() returns this
        // result instead. tool.executed STILL fires afterwards, marked cacheServed: true —
        // a cache hit is never invisible to audit/metrics listeners.
        $slot->shortCircuit(ToolResult::success($cached));
    }
});

$registry = new ToolRegistry($logger, $dispatcher);

use Milpa\ToolRuntime\Verification\HumanVerifier;
use Milpa\ToolRuntime\Verification\VerificationTool;

(new VerificationTool(new HumanVerifier()))->register($registry);

$request = $registry->call('request_verification', [
    'subject' => 'gate:report.publish',
], $ctx);
// -> ToolResult success, data: [
//      'subject' => 'gate:report.publish', 'policy' => 'single',
//      'request_id' => '06a1dda5-...',
//    ]
// handleRequest() ran on THIS call — HumanVerifier::verify() ran and dispatched
// `verification.requested`. No confirm_token anywhere: the registry gate never ran.

$registry->call('resolve_verification', [
    'request_id' => $request->data['request_id'],
    'decision' => 'grant',
    'principal' => 'agent:claude',
], $ctx);
// -> ToolResult success, data: [
//      'status' => 'passed', 'reason' => null, 'verifier' => 'human_verifier',
//      'principal' => 'agent:claude', 'missing' => [], 'metadata' => [],
//    ]
// HumanVerifier::grant() ran and dispatched `verification.granted`.

use Milpa\ToolRuntime\Contracts\ToolContext;
use Milpa\ToolRuntime\Verification\HumanVerifier;
use Milpa\ToolRuntime\Verification\VerificationTool;

// request_verification stays open (empty scopes, the pre-split default); resolve_verification
// ipal: 'agent:reviewer',
    channel: 'mcp',
    scopes: ['tasks:write', 'verification:resolve'],
);

$request = $registry->call('request_verification', ['subject' => 'gate:report.publish'], $worker);
// -> success: $worker has no 'verification:resolve' scope, but request_verification never checks it.

$registry->call('resolve_verification', [
    'request_id' => $request->data['request_id'], 'decision' => 'grant', 'principal' => 'agent:worker',
], $worker);
// -> FORBIDDEN: "Missing 

use Milpa\ToolRuntime\Verification\HumanVerifier;
use Milpa\ToolRuntime\Verification\VerificationTool;

$tool = new VerificationTool(new HumanVerifier($eventDispatcher));

$request = $tool->handleRequest(['subject' => 'gate:report.publish']);
// -> ToolResult::confirmation(), $request->data['request_id'] === '06a1dda5-...'
// HumanVerifier::verify() ran and dispatched `verification.requested`.

$tool->handleResolve([
    'request_id' => $request->data['request_id'],
    'decision' => 'grant',
    'principal' => 'agent:claude',
]);
// -> ToolResult::success(), data: ['status' => 'passed', 'principal' => 'agent:claude', ...]
// HumanVerifier::grant() ran and dispatched `verification.granted`.

$dispatcher->dispatch('verification.requested', ['event' => $requestedEvent]);
// $requestedEvent instanceof Milpa\Events\VerificationRequestedEvent

$dispatcher->dispatch('verification.granted', ['event' => $grantedEvent]);
// $grantedEvent instanceof Milpa\Events\VerificationGrantedEvent

$dispatcher->dispatch('verification.rejected', ['event' => $rejectedEvent]);
// $rejectedEvent instanceof Milpa\Events\VerificationRejectedEvent