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

Laravel Keenable Laravel Package

subscope-ai/laravel-keenable

Subscope AI's Laravel Keenable package integrates Keenable with Laravel for simple feature flag management. Configure the client, check flags through a facade, and enable or roll out features per user, environment, or request with minimal setup and Blade-friendly helpers.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require subscope-ai/laravel-keenable

Requires PHP 8.3+ and Laravel 12 or 13 — check both before adding to an existing app, since the framework floor is aggressive and will block installation on older projects.

First touch point is the facade, which gives you the shortest possible path to validating credentials and connectivity:

use SubscopeAi\Keenable\Facades\Keenable;

$results = Keenable::search('laravel best practices');
$page    = Keenable::fetch('https://example.com/article');

Both return typed DTOs (SearchResponse, FetchResponse, SearchResult) rather than arrays, so start by dumping the shape once and then code against the properties instead of data_get().

Before writing application code, do these three things in order:

  1. Decide your billing mode. The package supports an API key, keyless public endpoints, and pay-per-request x402 paths. Confirm which one your key/endpoint combination implies and set the corresponding env vars. Keyless mode is the fastest way to get a green result locally; the API key path is what you want in production.
  2. Publish/inspect any config. Run php artisan vendor:publish --provider="SubscopeAi\Keenable\KeenableServiceProvider" (or list providers) to see whether a config file with base URL, key, and timeout lands in config/keenable.php. Keep the config file out of git only if it holds secrets — otherwise commit it and read secrets from env.
  3. Smoke test in a tinker session with a single search() and a single fetch() so you see the real DTO shape and confirm the response codes map onto the typed exceptions you'll be catching.

The first genuinely useful use case is an agent that can answer questions about current information: register KeenableSearchTool and KeenableFetchTool on an agent, ask it something time-sensitive, and watch the tool-call trace. That single exercise tells you more about latency, cost per turn, and result quality than any amount of reading.

Implementation Patterns

Search as the default retrieval primitive. Reach for search() whenever you need ranked, citable URLs, and use fetch() as the second step to read the page. This two-phase pattern (discover then read) is cheaper than fetching speculative URLs and gives you a natural place to filter domains before spending fetch credits.

$response = Keenable::search(
    query: 'laravel queue supervisor configuration',
    mode: SearchMode::Pro,   // quality over freshness
    // count / date filters / snippet length as needed
);

foreach ($response->results as $result) {
    // $result->url, $result->title, snippet...
}

Pick the mode deliberately. pro for anything where accuracy matters (documentation lookups, research, RAG seeding); realtime for news, changelogs, pricing pages, "is X down" questions. Encode the choice in your prompt or in a per-feature config value rather than letting it default silently — mode is the single biggest lever on result quality.

Control cost at the query boundary, not downstream. Result count, snippet length, and mode all multiply cost per call. Set a conservative default (small count, short snippets) and expand only when a downstream step signals insufficiency (e.g. no result above a relevance threshold). Snippet length doubles as your token budget when you're stuffing results into an LLM prompt.

Use fetch() with prompt to collapse two steps into one. For "summarise the key points of this page" style tasks, fetch($url, prompt: '...') returns LLM-extracted content instead of raw page text. This is a meaningful simplification: you avoid shipping a second LLM call, chunking logic, and HTML cleanup. Reserve raw fetches for when you need the full text (indexing, diffing, re-extraction with different prompts later).

Fetch is not restricted to search results. It works on pages that aren't indexed yet — useful for user-supplied URLs, sitemaps, freshly deployed docs. Treat that as a feature and a hazard: every URL a user can influence becomes an outbound request and a billable call.

Agent wiring is the headline integration. Both tools implement Laravel\Ai\Contracts\Tool, so registration is declarative:

$agent = MyAgent::make()->withTools([
    new KeenableSearchTool(),
    new KeenableFetchTool(),
]);

Because they're plain classes, you can also compose: subclass or decorate them to enforce a domain allowlist, cap tool calls per conversation, log every query for evals, or swap the result formatter. If you need that, implement Tool yourself and delegate to the packaged class — that keeps you on the supported API while adding guardrails.

Prompt the tool selection explicitly. Agents left alone will over-fetch. Add a line to the system prompt like "use search to discover URLs; use fetch only on URLs returned by search or supplied by the user; never fetch more than three pages for one question." This measurably reduces spend and latency.

Testing. The client is built on Laravel's HTTP client, so Http::fake() and Http::assertSent() work as usual. Record one real response to each endpoint, trim it into a fixture, and fake by URL pattern:

Http::fake([
    '*/search*' => Http::response($searchFixture, 200),
    '*/fetch*'  => Http::response($fetchFixture, 200),
]);

Keenable::search('anything');

Http::assertSent(fn ($request) => $request['mode'] === 'pro');

Test the exception mapping too — fake a 401, 402, 403, 429, and 500 and assert your app's fallback behaviour. That's where most integrations quietly break in production.

Caching and idempotency. Search results are cheap to cache and safe to reuse for short windows; page fetches are stable enough to cache by URL + prompt hash. Cache the DTOs (serialise to array) rather than the raw response so a package upgrade doesn't invalidate your cache format unexpectedly.

Queue the slow paths. Fetch plus LLM extraction is seconds, not milliseconds. In request/response flows, dispatch a job and stream or poll the result; reserve synchronous calls for agent tool use where the latency is expected.

Gotchas and Tips

The framework floor is a hard blocker. PHP 8.3 and Laravel 12/13 exclude a large share of existing applications. Verify composer why-not subscope-ai/laravel-keenable before promising an upgrade timeline.

Know which exception you're catching. The package ships distinct types for authentication, credits, forbidden keys, rate limits, validation, and server errors. Catch specific types where behaviour differs:

  • Credits exhausted → degrade gracefully (skip enrichment, serve cached results) and alert.
  • Rate limit → retryAfter() and limit metadata are exposed; feed them into Laravel's RateLimiter or a retry() on the job rather than a fixed sleep.
  • Validation errors → your bug, not a transient failure. Don't retry them; they'll never succeed and they pollute your queue with noise.
  • Auth/forbidden key → configuration problem; fail fast at boot or in a health check rather than per-request.

Also confirm whether a common base exception exists and catch it at the outermost layer as a safety net. Letting a raw ConnectionException escape into an agent loop is the classic way to get a 500 in the middle of a conversation.

Billing mode confusion is the top support issue. Keyless, API-key, and x402 paths behave differently around headers, error codes, and rate limits. Decide once, put it in config, and document it in your .env.example. Don't mix modes between local and production — you'll debug a "bug" that's actually a different auth path.

Http::fake() needs the right pattern. The client is HTTP-client based, so unmatched requests will hit the network when a fake exists (depending on your preventStrayRequests setting). Faking '*' hides mistakes; fake the specific host and enable Http::preventStrayRequests() in tests to catch accidental real calls. If your base URL is configurable, derive your fake patterns from the same config value so they don't drift.

Watch out for double-faking agent tool tests. If the Laravel AI SDK also makes HTTP calls, a broad fake will satisfy both your Keenable client and the SDK's own transport. Be specific about hosts, and assert on request bodies (query string, mode) to prove the right tool fired.

Timeouts and retries aren't documented in the release notes. Assume sane defaults but verify — set an explicit timeout for fetch() (it fetches arbitrary pages and can be slow) and consider a single retry with backoff for 429/5xx only. Never blanket-retry: you pay per request, and retrying validation errors is pure waste.

User-supplied URLs are an SSRF surface. If fetch() ever receives a URL from an end user, validate scheme and host, block private IP ranges and metadata endpoints, and consider an allowlist. The package cannot do this for you — it's a search client, not a security boundary.

Version-pin the DTO surface. Typed DTOs are great until the maintainer adds/renames a property in a patch release. Pin to ^1.0 and rely on tests that assert DTO property access, so a shape change fails loudly in CI rather than at runtime in an agent trace.

Extension points to prefer, in order: config (base URL, key, defaults) → decorating the tool classes → catching typed exceptions in a service wrapper around the facade. Avoid reaching into the HTTP client internals or overriding the container binding; both break on minor upgrades. If you need custom retry/timeout policy, wrap Keenable in your own service class and keep the package as a transport detail — that's the cleanest seam and it makes the rest of your app testable without touching the client.

Build a small eval set early. Ten representative queries with expected "is this answerable / which URLs" outcomes will tell you whether pro vs realtime, snippet length, or count needs tuning — and it's the only way to notice a silent quality regression after a package update.

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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo