cleatsquad / php-prompt-policy
Situational prompt composition and policy engine for PHP
Requires
- php: >=8.4
Requires (Dev)
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-08-28 05:27:50 UTC
README
Situational prompt composition for PHP. A prompt is built from independent
policies, each deciding on its own whether it has something to say about the
current turn — instead of one function growing a new if per situation.
MIT · PHP >= 8.4 · no runtime dependency
The problem
A system prompt assembled by hand becomes a single function that knows about memory, language, capabilities, conversation history and identity at once. Every new situation adds a branch, the branches start to contradict each other, and nothing states which instruction wins when two apply.
This package splits that function into policies. Each one answers two questions about a context object it does not own — does this concern me? and what do I contribute? — and the engine assembles what comes back, in a defined order.
Install
composer require cleatsquad/php-prompt-policy
Use
A policy implements three methods. The context is any object: the package never inspects it, so the shape belongs to the caller.
use Cleatsquad\PromptPolicy\PromptPolicyInterface; use Cleatsquad\PromptPolicy\PromptSection; final readonly class LanguagePolicy implements PromptPolicyInterface { public function applies(object $context): bool { return $context->language !== null; } public function priority(): int { return 20; } public function render(object $context): ?PromptSection { return new PromptSection( content: "Answer in {$context->language}.", name: 'language', ); } }
Register the policies and evaluate one context:
use Cleatsquad\PromptPolicy\PromptPolicyEngine; $engine = new PromptPolicyEngine([ new IdentityPolicy(), new MemoryPolicy(), new LanguagePolicy(), ]); $sections = $engine->evaluate($turnContext); $prompt = implode("\n\n", array_map( static fn (PromptSection $section): string => $section->content, $sections, ));
evaluate() returns a list<PromptSection> sorted by descending priority. A
policy contributes nothing when applies() is false, or when render() returns
null — the second case covers a policy that concerns the turn but finds it has
no content for it.
API
| Type | Role |
|---|---|
PromptPolicyInterface |
applies(), priority(), render() — one situation, one implementation |
PromptSection |
readonly value object: content, name |
PromptPolicyEngine |
holds the policies, register() adds one, evaluate() returns the ordered sections |
Ordering is deterministic: same policies and same context produce the same
sections in the same order, which is what makes a composed prompt testable byte
for byte. PromptPolicyInterface::priority() is the single source of truth for
that order — PromptPolicyEngine sorts on it directly, and PromptSection
carries no priority of its own.
Test
composer install composer test # PHPUnit composer analyse # PHPStan, max level
License
MIT. See LICENSE.