Search by

multek / laravel-agno-os

rodrigocoliveira

A Laravel client and JWT token factory for Agno AgentOS 3.

Package info

github.com/Multek-Company/laravel-agno-os

pkg:composer/multek/laravel-agno-os

Statistics

Installs: 51

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.4.0 2026-10-14 19:37 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();