Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ai Pogocache Message Store Laravel Package

symfony/ai-pogocache-message-store

Pogocache message store integration for Symfony AI Chat, enabling chat message persistence through Pogocache. Includes documentation links for the Pogocache HTTP protocol, authentication, project resources, and contributing to Symfony AI.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a thin bridge, not a framework. Treat it as one small adapter class you wire into a Laravel service provider, and budget your reading time on symfony/ai-chat rather than on this repository.

Install

composer require symfony/ai-pogocache-message-store:^0.14 symfony/ai-chat symfony/http-client symfony/serializer

The package is a Symfony component, so Laravel's package auto-discovery does not apply. You get a class, not a service provider.

Where to look first, in order

  1. composer.json — the pinned ranges for symfony/ai-chat and symfony/http-client, plus the PHP floor and any symfony/polyfill-* entries, tell you which interface version and runtime you are coding against. As of v0.14 the package declares symfony/polyfill-php83 because it calls json_validate(), so it will install on PHP 8.1/8.2 as well as 8.3+.
  2. src/ — the concrete store class (constructor arguments are your integration surface: HTTP client, serializer, endpoint, credentials).
  3. tests/ — the fastest way to see the exact request path, HTTP verb, payload shape, and auth header.
  4. Pogocache's HTTP protocol docs — for endpoint layout, auth, and TTL semantics.
  5. symfony/ai-chat's MessageStoreInterface — the contract you actually type-hint in your own code.

Wire it up

// app/Providers/AiChatServiceProvider.php
public function register(): void
{
    $this->app->singleton(MessageStoreInterface::class, function ($app) {
        return new PogocacheMessageStore(
            $app->make(HttpClientInterface::class),   // symfony/contracts client
            $app->make(SerializerInterface::class),  // symfony/serializer
            config('services.pogocache.endpoint'),
            config('services.pogocache.password'),
            config('services.pogocache.ttl'),
        );
    });
}
// config/services.php
'pogocache' => [
    'endpoint' => env('POGOCACHE_ENDPOINT', 'https://pogocache.local:8080'),
    'password' => env('POGOCACHE_PASSWORD'),
    'ttl'      => (int) env('POGOCACHE_TTL', 3600),
],

Verify constructor order against the class before committing — the package has had several releases with "no significant changes," which usually means the API is stable, but also means nobody documented it for you.

First use case

A support bot where you persist the running transcript and replay a truncated window into the model on each turn:

public function reply(string $conversationId, string $userText): string
{
    $store = app(MessageStoreInterface::class);

    $store->save(new Message($conversationId, role: 'user', content: $userText));

    $history = $store->load($conversationId); // last N messages

    $response = $this->agent->chat($history);

    $store->save(new Message($conversationId, role: 'assistant', content: $response));

    return $response;
}

If that reads cleanly against your symfony/ai-chat version, you are done — the bridge is doing its job.

Implementation Patterns

Type-hint the interface, never the concrete class. Every call site in your app should depend on MessageStoreInterface. The concrete class is a deployment detail; your tests, local dev, and any future migration all depend on being able to swap it.

// tests/TestCase.php or a dedicated provider
$this->app->singleton(MessageStoreInterface::class, InMemoryMessageStore::class);

Keep an in-memory implementation in tests/Support/ from day one. It costs 20 lines and removes HTTP from every unit test.

Decorator for cross-cutting concerns. Rather than sprinkling retries, logging, and tenant prefixing across your chat service, wrap the store:

final class ResilientMessageStore implements MessageStoreInterface
{
    public function __construct(
        private MessageStoreInterface $inner,
        private LoggerInterface $log,
    ) {}

    public function save(Message $message): void
    {
        try {
            $this->inner->save($message);
        } catch (TransportExceptionInterface $e) {
            $this->log->warning('pogocache.save_failed', ['id' => $message->id, 'err' => $e->getMessage()]);
            // write-behind to the database / queue for later replay
            SaveMessageToDatabase::dispatch($message);
        }
    }
}

This is where you put tenant key prefixes, TTL policy, and metrics. The bridge stays dumb; your policy lives in your code.

Conversation keying. Pogocache is a key-value store. Design your keys explicitly, because you will be the one doing the modelling — there is no schema to lean on:

chat:{tenant}:{conversation}:msg:{ulid}      → message body
chat:{tenant}:{conversation}:index           → ordered list of message ids
chat:{tenant}:{conversation}:meta            → participants, created_at, model

If the bridge's own key scheme does not match yours, decorate it: transform the conversation id before delegating, or bypass the bridge and implement MessageStoreInterface yourself using the same HTTP client. The bridge is small enough that reimplementing it is a legitimate option if you need custom keying.

Persist asynchronously for streamed responses. If you stream tokens to the browser, do not hold the HTTP request open waiting on a Pogocache write. Buffer the assistant message and flush it from a queued job when the stream closes:

StreamedResponse::class => function () {
    // ... stream ...
    PersistAssistantMessage::dispatch($conversationId, $buffer)->onQueue('chat');
};

Truncate on read, not on write. Store the whole transcript, load a window:

$history = array_slice($store->load($conversationId), -20);

Cheap with a cache-backed store, and it keeps summaries/analytics possible without a second write path.

Octane and long-lived workers. The store should be stateless — construct it once as a singleton and it will be reused across requests under Octane. Do not stash per-conversation state on the store instance; pass it as arguments. symfony/http-client reuses connections, which is the main reason to use it here over Http:: per call.

Feature-flag the backend. A config toggle lets you run the old DB store in production and Pogocache in staging without a deploy:

$this->app->singleton(MessageStoreInterface::class, fn ($app) =>
    config('chat.store') === 'pogocache'
        ? new ResilientMessageStore($app->make(PogocacheMessageStore::class), $app->make('log'))
        : $app->make(DatabaseMessageStore::class)
);

Observability hook. Drop a Laravel middleware around your HTTP client (or a thin HttpClientInterface decorator) that logs method, path, status, and duration. You will need this the first time a chat hangs and you cannot tell whether it is the model call or the store.

Gotchas and Tips

This package has essentially no adoption signal. One star, zero dependents, and a long run of releases logged as "no significant changes." That is not a red flag on quality — it is a red flag on support. v0.14.0 is the first release in a while with an actual fix (a symfony/polyfill-php83 declaration for json_validate()), which is a good sign someone is still watching the build, but it is still not a package you want floating on ^0.14. Pin an exact version in composer.json ("symfony/ai-pogocache-message-store": "0.14.*") and read the diff before bumping. There is no community to answer questions.

A cache is not a system of record. Pogocache is a caching layer. It may evict under memory pressure regardless of TTL. If chat transcripts are needed for audit, compliance, dispute resolution, or billing, write them to your database too and treat Pogocache as the hot read path only. Decide this before your first deploy, not after your first eviction.

PHP version floor is now implicit. v0.14 declares symfony/polyfill-php83 because the code calls json_validate() (a PHP 8.3 function). That means the package technically installs on PHP 8.1/8.2, but if your composer.json pins symfony/polyfill-php83 out for policy reasons, or if you run on a distro PHP where polyfills are stripped, you will hit a Call to undefined function json_validate() at runtime — not at install time. Check composer show symfony/polyfill-php83 on your build image, and if you are on PHP < 8.3 add it to your own require so you are not relying on it transitively.

Symfony Serializer is not Laravel's serializer. symfony/serializer needs normalizers registered to handle your message objects properly — especially if you have enums, backed enums, DateTimeImmutable, or circular references from a Conversation → messages → conversation graph. If you see CircularReferenceException or objects serializing to {}, register ObjectNormalizer, DateTimeNormalizer, and BackedEnumNormalizer in a dedicated SerializerInterface binding used only for this store. Do not reuse it for API responses without checking that it produces the same JSON you expect.

Authentication is HTTP-based and mandatory. Pogocache's auth is a password/token over its HTTP protocol. Over plain http:// that is a credential in clear text on your network. Require TLS, put the secret in config/services.php reading from env(), and if you run in a Kubernetes/ECS environment wire it from your secret manager rather than .env. Also confirm whether the bridge sends the credential as a header or query parameter — if it is the latter, it will end up in access logs.

Timeouts and retries are your job, not the package's. The bridge will throw TransportExceptionInterface on network failure. Set explicit timeouts on the HTTP client (a 30-second default will make a slow Pogocache look like a slow model, and you will debug the wrong layer):

HttpClient::create(['timeout' => 2.0, 'max_duration' => 5.0]);

Then wrap with a retry for idempotent reads only. Never blind-retry a save() unless your message ids are deterministic and the store upserts.

Don't assume read-after-write consistency. With a distributed cache, a save() followed immediately by load() in the same request can come back stale. Pass the just-written message through in memory for the current turn, and read from the store starting the next turn.

Missing messages look like a broken prompt, not like an error. When history silently comes back empty, the symptom is the model losing context mid-conversation — which reads as a prompt engineering bug. Add an assertion or log line when load() returns fewer messages than the conversation's known turn count.

Local development friction. There is no SQLite equivalent for Pogocache. Bind the in-memory store in local and testing environments so php artisan serve and CI do not require a running Pogocache instance. Reserve the real bridge for staging and production.

Extension points are limited, so decorate. The public surface is the store class and the MessageStoreInterface contract. If you need per-conversation TTLs, key namespacing, or event emission on store/load, do it in a decorator — do not fork the package, because you will be maintaining a fork of a package nobody else uses.

Version alignment is the real risk. The bridge depends on symfony/ai-chat and symfony/http-client, and symfony/ai-chat is itself young and moving. v0.14 of the bridge was tagged alongside symfony/ai-* 0.14, but the constraint in composer.json is likely still >=0.9 for compatibility — which means an unrelated composer update can float ai-chat forward and break the bridge without touching this package at all. Run composer why symfony/ai-chat periodically and pin it in composer.json too.

Not a query engine. No full-text search, no aggregation, no joins, no "find all conversations where the user complained." If product asks for that, the answer is a database or Elasticsearch behind it, with Pogocache serving only the hot per-conversation read. If you find yourself writing a conversation id index and a scan loop to answer a question, stop — that is the signal you picked the wrong store for that access pattern.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console