multek / laravel-agno-os
A Laravel client and JWT token factory for Agno AgentOS 3.
Requires
- php: ^8.2
- firebase/php-jwt: ^7.1
- illuminate/contracts: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- laravel/pint: ^1.29
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-14 19:37:45 UTC
README
A focused Laravel client for the Agno AgentOS 3 REST API. It generates scoped JWTs, supports AgentOS service-account/security-key tokens, and wraps the run lifecycle without coupling your application to a concrete User model.
Installation
composer require multek/laravel-agno-os php artisan vendor:publish --tag=agno-os-config
Configure asymmetric signing. Laravel receives the private key; AgentOS receives only the matching public key:
AGNO_OS_URL=http://127.0.0.1:7777 AGNO_OS_AUTH_DRIVER=jwt AGNO_OS_JWT_ALGORITHM=RS256 AGNO_OS_JWT_SIGNING_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----" AGNO_OS_JWT_KEY_ID=primary-2026-09 AGNO_OS_JWT_AUDIENCE=my-agent-os
HS256 remains available for local or legacy installations, but shares token-minting authority with AgentOS. For machine-to-machine calls, prefer a revocable agno_pat_... service-account token using AGNO_OS_AUTH_DRIVER=token and AGNO_OS_TOKEN.
On AgentOS 3, configure validation, scope enforcement, claim injection, audience verification, and user isolation together:
from agno.os import AgentOS from agno.os.middleware.jwt import AuthMiddleware agent_os = AgentOS( id="my-agent-os", agents=[assistant], ) app = agent_os.get_app() app.add_middleware( AuthMiddleware, verification_keys=[PUBLIC_KEY], algorithm="RS256", authorization=True, verify_audience=True, audience="my-agent-os", user_isolation=True, dependencies_claims=["tenant_id", "role"], session_state_claims=["locale"], )
Usage
use Multek\AgnoOS\Facades\AgnoOS; use Multek\AgnoOS\Runs\AgentRunOptions; $response = AgnoOS::forUser( auth()->user(), claims: ['tenant_id' => 'org-1', 'role' => 'member'], scopes: ['agents:support:run', 'sessions:read'], )->runAgent( agentId: 'support', message: 'Summarize my open requests.', sessionId: 'conversation-123', options: new AgentRunOptions( dependencies: ['locale' => 'pt-BR'], sessionState: ['draft_id' => 'draft-42'], metadata: ['source' => 'dashboard'], knowledgeFilters: ['category' => 'support'], outputSchema: [ 'type' => 'object', 'properties' => ['summary' => ['type' => 'string']], 'required' => ['summary'], ], background: false, factoryInput: ['persona' => 'concise'], ), ); $result = $response->json();
Run a workflow with the same options (background: true returns 202 with the run id):
$response = AgnoOS::forSystem()->runWorkflow( workflowId: 'negotiation', message: 'Start the negotiation.', sessionId: 'conversation-123', options: new AgentRunOptions(background: true), );
Follow workflow runs the same way as agent runs: getWorkflowRun(), workflowRuns(), cancelWorkflowRun(), continueWorkflowRun() (pass step_requirements in $options) and resumeWorkflowRun().
Attach files you already hold in memory without writing them to disk:
AgnoOS::forSystem()->runAgent('support', 'Describe this photo.', files: [ ['contents' => $bytes, 'name' => 'photo.jpg', 'mime' => 'image/jpeg'], ]);
Generate a token without making a request when a frontend or another service needs the credential:
$token = AgnoOS::tokens()->forUser( auth()->user(), scopes: ['agents:support:run', 'sessions:read'], );
AgentRunOptions models the AgentOS 3 runtime fields. Its extra argument accepts newly introduced form fields without waiting for a package release, but cannot override fields owned by the typed method. The client always requests non-streaming JSON in runAgent(), runWorkflow(), and continueRun().
AgentOS answers failed runs with HTTP 200 and status: "ERROR" or "CANCELLED", putting the raw provider error in content. runAgent(), runWorkflow(), continueRun(), continueWorkflowRun() and resumeWorkflowRun() throw Multek\AgnoOS\Exceptions\AgentRunFailed in that case, so the error text never reaches end users as a reply. The exception exposes uri, runId, status, content (for logs only) and response. HTTP-level failures still throw RequestException as before.
Extensibility
Use withTimeout(90) (or withTimeout(90, connectTimeout: 5)) to override agno-os.http.timeout for one call, e.g. to stay under a queued job's own timeout.
The package has three layers. Use typed methods for normal application code, extra for future run fields, and rawRequest() when complete HTTP control is required:
use Illuminate\Support\Str; $client = AgnoOS::withHeaders([ 'X-Tenant-Context' => 'tenant-42', 'X-Correlation-ID' => (string) Str::uuid(), ])->withHttpOptions([ 'timeout' => 120, ]); $response = $client->rawRequest('POST', '/future/endpoint', [ 'query' => ['version' => 'next'], 'headers' => ['X-Feature' => 'experimental'], 'json' => ['any_payload' => ['is' => 'accepted']], ]);
rawRequest() passes Laravel HTTP client / Guzzle options through unchanged, including query, json, form_params, multipart, body, headers, certificates, and transport settings. withHeader(), withHeaders(), withToken(), withTimeout(), and withHttpOptions() return cloned clients, so request-specific customization does not mutate the singleton used by later calls.
The regular convenience API also exposes:
AgnoOS::config(); AgnoOS::agents(); AgnoOS::sessions(['user_id' => '42']); AgnoOS::getRun('support', $runId, $sessionId); AgnoOS::continueRun('support', $runId, $sessionId, ['input' => 'Go deeper']); AgnoOS::cancelRun('support', $runId); AgnoOS::request('GET', '/teams');
JWT sub is emitted as a string and becomes the trusted AgentOS user identity when user isolation is enabled. User tokens have no default scopes; grant resource scopes such as agents:support:run explicitly. Profile claims such as email and name are opt-in through agno-os.auth.identity_claims because JWT payloads are readable by their holder.
Values supplied in AgentRunOptions are client-controlled runtime context. Do not use dependencies, knowledge_filters, metadata, or factory_input for authorization or tenant isolation. Put identifiers and roles in signed claims, extract them with AuthMiddleware, and use ctx.trusted.claims / ctx.trusted.scopes inside AgentOS factories and tools.
Admin token issuance is disabled by default. If an explicitly authorized server-side flow needs it, set AGNO_OS_ALLOW_ADMIN_TOKENS=true and call forAdmin() only after enforcing the application's own policy. Audience validation is required by this package unless AGNO_OS_JWT_REQUIRE_AUDIENCE=false is set deliberately.
Testing
vendor/bin/pest vendor/bin/pint --test
Streaming SSE is intentionally outside the typed API: resumeWorkflowRun() buffers the event stream until the run ends. To consume events live, use the escape hatch with Guzzle's stream option and read the body line by line:
$body = AgnoOS::forSystem()->rawRequest('POST', '/workflows/negotiation/runs', [ 'multipart' => [['name' => 'message', 'contents' => 'Go'], ['name' => 'stream', 'contents' => 'true']], 'stream' => true, ])->toPsrResponse()->getBody();