PHPackages                             blundergoat/strands-php-client - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. blundergoat/strands-php-client

ActiveLibrary[HTTP &amp; Networking](/categories/http)

blundergoat/strands-php-client
==============================

PHP client for consuming Strands AI agents over HTTP - invoke, stream via SSE, manage sessions.

v1.4.0(3mo ago)317.6k↑219%[1 PRs](https://github.com/blundergoat/strands-php-client/pulls)Apache-2.0PHPPHP &gt;=8.2CI passing

Since Feb 16Pushed 4w agoCompare

[ Source](https://github.com/blundergoat/strands-php-client)[ Packagist](https://packagist.org/packages/blundergoat/strands-php-client)[ Docs](https://github.com/blundergoat/strands-php-client)[ RSS](/packages/blundergoat-strands-php-client/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (5)Dependencies (16)Versions (7)Used By (0)

Strands PHP Client
==================

[](#strands-php-client)

[![CI](https://github.com/blundergoat/strands-php-client/actions/workflows/ci.yml/badge.svg)](https://github.com/blundergoat/strands-php-client/actions/workflows/ci.yml)[![Latest Version](https://camo.githubusercontent.com/65c1a6fc67d9fceef51290531f67e9cfef81a56b39b74489f31a78d2de9ea6b2/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f626c756e646572676f61742f737472616e64732d7068702d636c69656e742e737667)](https://packagist.org/packages/blundergoat/strands-php-client)[![PHP Version](https://camo.githubusercontent.com/c3930b5807dcc1c1d63a7b931ce089d1a476f8cb5a7b1192ad7210c9bfb98f9d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f626c756e646572676f61742f737472616e64732d7068702d636c69656e742f7068702e737667)](https://packagist.org/packages/blundergoat/strands-php-client)[![License](https://camo.githubusercontent.com/906912965a41b747c710f99f7349ba41540092de409268852b24f68e715af6db/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f626c756e646572676f61742f737472616e64732d7068702d636c69656e742e737667)](https://github.com/blundergoat/strands-php-client/blob/main/LICENSE)

PHP client library for consuming [Strands](https://github.com/strands-agents/strands-agents) AI agents via HTTP. Invoke agents, stream responses via SSE, manage sessions -without running an agentic loop in PHP.

Works with **any PHP framework** -Laravel, Symfony, Slim, or vanilla PHP.

*"Your PHP app doesn't need to become an AI platform. It just needs to talk to one."*

```
composer require blundergoat/strands-php-client

```

Why This Exists
---------------

[](#why-this-exists)

[Strands Agents](https://github.com/strands-agents/strands-agents) is an open-source Python SDK from AWS for building autonomous AI agents. It handles the hard parts -reasoning loops, tool orchestration, model routing (Claude, Nova, GPT, Ollama) -and exposes agents over HTTP.

But most web applications aren't written in Python. If your product runs on Symfony or any PHP framework, you need a way to **consume** those agents without reimplementing the agentic loop in PHP. That's what this library does.

Your Python agents handle the AI. Your PHP app handles the product. This client is the bridge.

 ```
graph LR
    A["PHP ApplicationLaravel · Symfony · Slim"] -->|"invoke() / stream()"| B["strands-php-client"]
    B -->|"HTTP + SSE"| C["Strands AgentPython · FastAPI"]
    C -->|"Reasoning Loop"| D["LLM ProviderBedrock · Ollama · OpenAI"]
    C -->|"Tool Calls"| E["Tools & APIsDB · Search · Custom"]

    style A fill:#7c3aed,color:#fff,stroke:none
    style B fill:#2563eb,color:#fff,stroke:none
    style C fill:#059669,color:#fff,stroke:none
    style D fill:#d97706,color:#fff,stroke:none
    style E fill:#d97706,color:#fff,stroke:none
```

      Loading For a full walkthrough with real-world examples, see the [Usage Guide](docs/usage-guide.md).

Quick Start
-----------

[](#quick-start)

### Symfony (with auto-detection)

[](#symfony-with-auto-detection)

If `symfony/http-client` is installed, the transport is auto-detected -just create a client and go:

```
use StrandsPhpClient\StrandsClient;
use StrandsPhpClient\Config\StrandsConfig;

$client = new StrandsClient(
    config: new StrandsConfig(endpoint: 'http://localhost:8081'),
);

$response = $client->invoke('Should we migrate to microservices?');

echo $response->text;
echo $response->agent;               // Which agent handled it (e.g. "analyst")
echo $response->usage->inputTokens;
```

For Symfony projects, the bundle adds YAML config and autowiring -see [Symfony Bundle Integration](#symfony-bundle-integration) below.

### PSR-18 (Guzzle, Buzz, etc.)

[](#psr-18-guzzle-buzz-etc)

Any PSR-18 client works via `PsrHttpTransport` (invoke only, no streaming):

```
use StrandsPhpClient\StrandsClient;
use StrandsPhpClient\Config\StrandsConfig;
use StrandsPhpClient\Http\PsrHttpTransport;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;

$client = new StrandsClient(
    config: new StrandsConfig(endpoint: 'http://localhost:8081'),
    transport: new PsrHttpTransport(
        httpClient: new Client(['timeout' => 120]),
        requestFactory: new HttpFactory(),
        streamFactory: new HttpFactory(),
    ),
);

$response = $client->invoke('Should we migrate to microservices?');
echo $response->text;
```

Features
--------

[](#features)

### Rich Input (AgentInput)

[](#rich-input-agentinput)

Send multi-modal content to agents - images, documents, S3 locations - alongside your text message:

```
use StrandsPhpClient\Context\AgentInput;

// Text with an image
$input = AgentInput::text("What's in this image?")
    ->withImage(base64_encode($imageBytes), 'image/png');

$response = $client->invoke(message: $input);

// Text with a document from S3
$input = AgentInput::text('Summarise this report')
    ->withDocumentFromS3('s3://my-bucket/report.pdf', 'pdf', 'report');

// Resume after an interrupt (human-in-the-loop)
$input = AgentInput::interruptResponse($interruptId, ['approved' => true]);
```

When no content blocks are attached, `AgentInput::text()` serializes as a plain string - fully backward compatible. See [docs/rich-input.md](docs/rich-input.md) for the full API reference.

### Invoke (blocking)

[](#invoke-blocking)

```
use StrandsPhpClient\Context\AgentContext;

$response = $client->invoke(
    message: 'Analyse this proposal',
    context: AgentContext::create()
        ->withMetadata('persona', 'analyst')
        ->withSystemPrompt('Be concise.'),
    sessionId: 'session-001',
);

$response->text;                         // Agent's response
$response->agent;                        // Agent name (e.g. "analyst")
$response->sessionId;                    // Session ID for follow-ups
$response->usage;                        // Token usage (inputTokens, outputTokens)
$response->usage->totalTokens();         // Total tokens (input + output)
$response->toolsUsed;                    // Tools the agent called
$response->metadata;                     // Unrecognised response fields (forward-compat)

// Interrupt handling (human-in-the-loop)
if ($response->isInterrupted()) {
    foreach ($response->interrupts as $interrupt) {
        echo $interrupt->toolName;       // Tool that needs approval
        echo $interrupt->reason;         // Why the agent paused
    }
}

// Guardrail traces (content safety)
if ($response->guardrailTrace !== null) {
    echo $response->guardrailTrace->action;  // 'INTERVENED' or 'NONE'
}
```

### Stream (SSE)

[](#stream-sse)

Real-time token-by-token streaming via Server-Sent Events. Requires `symfony/http-client`.

`stream()` returns a `StreamResult` with the accumulated text, session info, and usage stats:

```
use StrandsPhpClient\Streaming\StreamEvent;
use StrandsPhpClient\Streaming\StreamEventType;

$result = $client->stream(
    message: 'Explain quantum computing',
    onEvent: function (StreamEvent $event) {
        match ($event->type) {
            StreamEventType::Text     => print($event->text),
            StreamEventType::ToolUse  => print("[Using tool: {$event->toolName}]"),
            StreamEventType::Complete => print("\n[Done]"),
            StreamEventType::Error    => print("Error: {$event->errorMessage}"),
            default                   => null,
        };
    },
    sessionId: 'session-001',
);

echo $result->text;                          // Full accumulated text
echo $result->sessionId;                     // Session ID
echo $result->usage->inputTokens;            // Token usage
echo $result->usage->totalTokens();          // Total tokens (input + output)
echo $result->textEvents;                    // Number of text chunks received
echo $result->totalEvents;                   // Total events received
echo $result->timeToFirstTextTokenMs;        // Client-measured TTFT in ms
echo $result->isInterrupted() ? 'yes' : 'no'; // Whether the agent was interrupted
```

> **Note:** SSE streaming requires `symfony/http-client` via `SymfonyHttpTransport`. PSR-18 clients only support `invoke()` -this is a limitation of the PSR-18 spec, not this library.

### Custom Endpoints (postJson / streamSse)

[](#custom-endpoints-postjson--streamsse)

For agent endpoints with custom request/response schemas, use `postJson()` and `streamSse()`. These bypass the standard invoke/stream contract and work with arbitrary payloads:

```
// Synchronous - returns raw decoded JSON array
$result = $client->postJson('/file-summarise', [
    'file_base64' => base64_encode($fileBytes),
    'file_name' => 'report.pdf',
], timeout: 120);

// Streaming - delivers raw decoded JSON arrays to the callback
$client->streamSse('/file-summarise-stream', [
    'file_base64' => base64_encode($fileBytes),
], function (array $event) {
    match ($event['type'] ?? '') {
        'text'     => print($event['content']),
        'complete' => print("\n[Done]"),
        default    => null,
    };
}, timeout: 120);
```

Both methods accept optional `timeout:` overrides and stream callbacks can return `false` to cancel.

### Error Handling

[](#error-handling)

`AgentErrorException` carries the full response body for debugging structured errors:

```
use StrandsPhpClient\Exceptions\AgentErrorException;

try {
    $client->postJson('/validate', $payload);
} catch (AgentErrorException $e) {
    $e->statusCode;    // 422
    $e->getMessage();  // "Validation failed"
    $e->responseBody;  // ['detail' => '...', 'errors' => [...]]
}
```

### Sessions &amp; Context

[](#sessions--context)

Pass `sessionId` for multi-turn conversations -the server manages all state:

```
$r1 = $client->invoke('Draft a referral letter', sessionId: 'consult-001');
$r2 = $client->invoke('Make it more formal', sessionId: 'consult-001');
```

Use the immutable `AgentContext` builder to pass application context:

```
$context = AgentContext::create()
    ->withMetadata('clinic_id', 'CL-789')
    ->withSystemPrompt('You are a clinical documentation assistant.')
    ->withPermission('read:patients')
    ->withDocument('referral.pdf', base64_encode($pdfBytes), 'application/pdf');
```

### Retries, Timeouts &amp; Logging

[](#retries-timeouts--logging)

```
$config = new StrandsConfig(
    endpoint: 'https://api.example.com/agent',
    maxRetries: 3,          // Retry up to 3 times (invoke/postJson only)
    retryDelayMs: 500,      // 500ms → 1s → 2s (exponential backoff with jitter)
    connectTimeout: 5,      // Fail fast if server is down
);

$client = new StrandsClient(config: $config, logger: $yourPsr3Logger);
```

Retries apply to `invoke()` and `postJson()`. Streaming requests are not retried. The Symfony bundle injects Monolog automatically.

Transport
---------

[](#transport)

Transport`invoke()``stream()`Dependency`SymfonyHttpTransport`YesYes`symfony/http-client``PsrHttpTransport`YesNoAny PSR-18 client**Auto-detection:** If no transport is passed to the constructor, the client checks for `symfony/http-client` and uses `SymfonyHttpTransport` automatically. If Symfony isn't available, it throws with guidance to pass a `PsrHttpTransport`.

**Timeout:** `SymfonyHttpTransport` uses the `timeout` from `StrandsConfig` (default 120s). `connectTimeout` (default 10s) controls how long to wait for the initial TCP connection -separate from the read timeout so a down server fails fast without affecting slow LLM generation. For `PsrHttpTransport`, configure timeout on your PSR-18 client directly (e.g. `new GuzzleHttp\Client(['timeout' => 120])`).

Auth Strategies
---------------

[](#auth-strategies)

StrategyUse CaseStatus`NullAuth`Local dev via Docker ComposeAvailable`ApiKeyAuth`API Gateway / reverse proxy with API keysAvailable`SigV4Auth`AWS service-to-service (IAM)Available```
use StrandsPhpClient\Auth\NullAuth;
use StrandsPhpClient\Auth\ApiKeyAuth;

// No auth (default -local development)
$config = new StrandsConfig(
    endpoint: 'http://localhost:8081',
);

// API key auth (production)
$config = new StrandsConfig(
    endpoint: 'https://api.example.com/agent',
    auth: new ApiKeyAuth('sk-your-api-key'),
);

// Custom header (e.g. X-API-Key instead of Authorization: Bearer)
$config = new StrandsConfig(
    endpoint: 'https://api.example.com/agent',
    auth: new ApiKeyAuth('sk-your-api-key', headerName: 'X-API-Key', valuePrefix: ''),
);

// AWS SigV4 auth (agents behind API Gateway with IAM)
use StrandsPhpClient\Auth\SigV4Auth;

$config = new StrandsConfig(
    endpoint: 'https://abc123.execute-api.us-east-1.amazonaws.com/prod',
    auth: new SigV4Auth(
        accessKeyId: 'AKIA...',
        secretAccessKey: 'wJalr...',
        region: 'us-east-1',
    ),
);

// SigV4 from environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
$config = new StrandsConfig(
    endpoint: 'https://abc123.execute-api.us-east-1.amazonaws.com/prod',
    auth: SigV4Auth::fromEnvironment(region: 'us-east-1'),
);
```

For details on writing custom auth strategies, see [docs/auth.md](docs/auth.md).

Symfony Bundle Integration
--------------------------

[](#symfony-bundle-integration)

The Symfony bundle adds YAML config, autowired named agents, and automatic PSR-3 logging:

```
# config/packages/strands.yaml
strands:
    agents:
        analyst:
            endpoint: '%env(AGENT_ENDPOINT)%'
            timeout: 300
        skeptic:
            endpoint: '%env(AGENT_ENDPOINT)%'
            timeout: 300
            auth:
                driver: api_key
                api_key: '%env(AGENT_API_KEY)%'
```

```
use StrandsPhpClient\StrandsClient;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class ChatController extends AbstractController
{
    public function __construct(
        #[Autowire(service: 'strands.client.analyst')]
        private readonly StrandsClient $analyst,

        #[Autowire(service: 'strands.client.skeptic')]
        private readonly StrandsClient $skeptic,
    ) {}
}
```

Bundle registration:

```
// config/bundles.php
return [
    StrandsPhpClient\Integration\Symfony\StrandsBundle::class => ['all' => true],
];
```

For the full configuration reference, see [docs/symfony-config.md](docs/symfony-config.md).

Laravel Integration
-------------------

[](#laravel-integration)

The Laravel service provider adds config-driven agent registration, dependency injection, and a facade:

```
// config/strands.php (publish with: php artisan vendor:publish --tag=strands-config)
return [
    'default' => env('STRANDS_DEFAULT_AGENT', 'default'),

    'agents' => [
        'default' => [
            'endpoint' => env('STRANDS_ENDPOINT', 'http://localhost:8081'),
            'auth' => [
                'driver' => env('STRANDS_AUTH_DRIVER', 'null'),
                'api_key' => env('STRANDS_API_KEY'),
            ],
            'timeout' => (int) env('STRANDS_TIMEOUT', 120),
        ],
        'analyst' => [
            'endpoint' => env('AGENT_ENDPOINT'),
            'auth' => ['driver' => 'api_key', 'api_key' => env('AGENT_API_KEY')],
            'timeout' => 300,
        ],
    ],
];
```

Inject by type-hint or resolve named agents:

```
use StrandsPhpClient\StrandsClient;

class ChatController extends Controller
{
    public function __construct(
        private readonly StrandsClient $client, // default agent
    ) {}
}

// Named agent via container
$analyst = app('strands.client.analyst');
```

Use the facade for quick calls:

```
use StrandsPhpClient\Integration\Laravel\Facades\Strands;

$response = Strands::invoke('Analyse this proposal');
```

Auto-discovery is configured via `composer.json` -no manual provider registration needed.

For the full configuration reference, see [docs/laravel-config.md](docs/laravel-config.md).

Requirements
------------

[](#requirements)

- PHP 8.2+
- One of:
    - `symfony/http-client` ^6.4 or ^7.0 -for full support (invoke + streaming), auto-detected
    - Any PSR-18 HTTP client (e.g. `guzzlehttp/guzzle`) -for invoke only, via `PsrHttpTransport`

Installation
------------

[](#installation)

**Laravel projects:**

```
composer require blundergoat/strands-php-client
php artisan vendor:publish --tag=strands-config
```

**Symfony projects:**

```
composer require blundergoat/strands-php-client
# symfony/http-client is likely already installed -transport auto-detects
```

**PSR-18 / Guzzle projects:**

```
composer require blundergoat/strands-php-client guzzlehttp/guzzle
```

**Want streaming too?**

```
composer require blundergoat/strands-php-client symfony/http-client
```

Documentation
-------------

[](#documentation)

DocumentDescription[Usage Guide](docs/usage-guide.md)Real-world patterns and examples[Authentication](docs/auth.md)Auth strategies, custom drivers, framework config[Rich Input](docs/rich-input.md)Multi-modal input: images, documents, S3 locations[Interrupts &amp; Guardrails](docs/interrupts-and-guardrails.md)Human-in-the-loop and content safety[Laravel Config](docs/laravel-config.md)Full PHP config reference with every option[Symfony Config](docs/symfony-config.md)Full YAML config reference with every option[Changelog](CHANGELOG.md)Version history and breaking changes[Contributing](CONTRIBUTING.md)How to set up dev, run tests, submit PRsTesting
-------

[](#testing)

```
composer install
vendor/bin/phpunit
```

All tests use mocked HTTP responses -no Docker, no API keys, no network calls.

Related
-------

[](#related)

RepoWhat[the-summit-chatroom](https://github.com/blundergoat/the-summit-chatroom)Demo app - three AI agents debating your decisions[terraform-aws-strands](https://github.com/blundergoat/terraform-aws-strands)Terraform module for deploying Strands agents on AWSMaintained by [Matthew Hansen](https://www.blundergoat.com/about).

###  Health Score

48

—

FairBetter than 94% of packages

Maintenance88

Actively maintained with recent releases

Popularity31

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 96% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

5

Last Release

107d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/15ded8f2e9bdda9babe694ec25852a406fac982de4802c8cc82867b34a1afbc7?d=identicon)[blundergoat](/maintainers/blundergoat)

---

Top Contributors

[![mattyhansen](https://avatars.githubusercontent.com/u/4113603?v=4)](https://github.com/mattyhansen "mattyhansen (24 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

phpsymfonylaravelsdkpsr-18aistreamingAgentssebedrockllmstrands

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/blundergoat-strands-php-client/health.svg)

```
[![Health](https://phpackages.com/badges/blundergoat-strands-php-client/health.svg)](https://phpackages.com/packages/blundergoat-strands-php-client)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k12](/packages/tempest-framework)[cakephp/cakephp

The CakePHP framework

8.8k19.1M1.7k](/packages/cakephp-cakephp)[claude-php/claude-php-sdk

A universal, framework-agnostic PHP SDK for the Anthropic Claude API with PSR compliance

14640.4k2](/packages/claude-php-claude-php-sdk)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6941.5M396](/packages/drupal-core-recommended)[deepseek-php/deepseek-php-client

deepseek PHP client is a robust and community-driven PHP client library for seamless integration with the Deepseek API, offering efficient access to advanced AI and data processing capabilities.

46784.5k5](/packages/deepseek-php-deepseek-php-client)[mozex/anthropic-php

PHP client for the Anthropic API: messages, streaming, tool use, thinking, web search, code execution, batches, and more.

47480.9k16](/packages/mozex-anthropic-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
