survos / ai-workflow-bundle
Symfony workflow primitives for async AI preparation, observation, analysis, and publishing.
Package info
github.com/survos/ai-workflow-bundle
Type:symfony-bundle
pkg:composer/survos/ai-workflow-bundle
Fund package maintenance!
Requires
- php: ^8.5
- aws/aws-sdk-php: ^3.388
- survos/claims-bundle: ^2.5
- survos/data-contracts: ^2.5
- survos/kit-bundle: ^2.33
- survos/menu-bundle: ^2.42.7
- survos/workflow-extras-bundle: ^2.42.8
- survos/workflow-messenger-bundle: ^2.42.9
- symfony/ai-agent: ^1.0.0-beta1
- symfony/ai-bundle: ^1.0.0-beta1
- symfony/ai-mistral-platform: ^1.0.0-beta1
- symfony/ai-open-ai-platform: ^1.0.0-beta1
- symfony/dependency-injection: 8.2.*@dev
- symfony/framework-bundle: 8.2.*@dev
- symfony/http-client: 8.2.*@dev
- symfony/messenger: 8.2.*@dev
- symfony/service-contracts: ^3.6
- symfony/twig-bundle: 8.2.*@dev
- symfony/workflow: 8.2.*@dev
- twig/twig: ^3.0|^4.0
Requires (Dev)
- phpunit/phpunit: ^13.0
- survos/tabler-bundle: ^2.42.9
Suggests
- survos/imgproxy-bundle: Signed imgproxy URL generation — implement AiThumbnailProviderInterface using ImgproxyUrlBuilder
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 2.44.0
- 2.43.23
- 2.43.14
- 2.43.1
- 2.42.14
- 2.42.13
- 2.41.9
- 2.34.31
- 2.34.1
- 2.33.2
- 2.33.0
- 2.31.14
- 2.31.12
- 2.31.11
- 2.31.9
- 2.31.8
- 2.28.3
- 2.28.2
- 2.28.1
- 2.26.9
- 2.19.0
- 2.18.20
- 2.18.19
- 2.18.18
- 2.17.0
- 2.15.21
- 2.15.15
- 2.11.4
- 2.11.3
- 2.11.0
- 2.10.19
- 2.10.18
- 2.10.17
- 2.10.16
- 2.10.15
- 2.10.14
- 2.10.13
- 2.10.12
- 2.10.11
- 2.10.10
- 2.10.9
- 2.10.8
- 2.10.7
- 2.10.6
- 2.10.5
- 2.10.4
- 2.10.3
- 2.10.2
- 2.10.1
- 2.10.0
- 2.9.4
- 2.9.3
- 2.9.2
- 2.9.1
- 2.9.0
- 2.8.4
- 2.8.3
- 2.8.2
- 2.8.1
- 2.8.0
- 2.7.23
- 2.7.22
- 2.7.21
- 2.7.20
- 2.7.19
- 2.7.18
- 2.7.17
- 2.7.16
- 2.7.15
- 2.7.14
- 2.7.13
- 2.7.12
- 2.7.11
- 2.7.10
- 2.7.9
- 2.7.8
- 2.7.7
- 2.7.6
- 2.7.5
- 2.7.4
- 2.7.3
- 2.7.2
- 2.7.1
- 2.7.0
- 2.6.0
- 2.5.8
- 2.5.7
- 2.5.6
- 2.5.5
- 2.5.3
- 2.5.2
- 2.5.1
- 2.5.0
- 2.4.4
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.0
- 2.2.5
- 2.2.4
- 2.2.3
- 2.2.2
- 2.2.1
- 2.2.0
This package is auto-updated.
Last update: 2026-10-27 13:38:23 UTC
README
Symfony 8.2 workflow primitives for async AI work over any subject.
The bundle defines a small shared lifecycle:
new -> prepared -> observed -> analyzed -> reviewed -> published
The important split is conceptual:
prepare: make source content and context available.observe: create neutral evidence from the source. For images this is usually thumbnail observation plus optional high-resolution reads; for text it may be dense summaries, language, or structure.analyze: apply application meaning to observed evidence and context.review: app-defined approval after analysis. This can be automatic policy, human review, or another app-specific gate.publish: project the result into fields, indexes, exports, webhooks, or other app outputs.
The bundle does not decide what those transitions do. Applications provide workflow listeners, task policy, and publishing behavior.
The bundle may define reusable tasks and prompt templates. Treat those as a prompt/task repository, not app policy: applications still decide which tasks to queue, when to append follow-up tasks, and what publish means.
Core Classes
Workflow\SubjectFlow: native attribute-based workflow definition.Contract\WorkflowSubjectInterface: minimal subject id plus queue/lock shape for entities. Claim subject type defaults to the PHP class name.Task\TaskInterface: callable workflow task that returns claims.Task\TaskResult: claims plus optional follow-up tasks.Task\TaskRegistry: lazy task lookup by name.Task\TaskRunner: consumes one queued task and records claims throughsurvos/claims-bundle.
Task outputs are not stored as opaque JSON result blobs. They are recorded as ClaimRun and Claim rows. Apps can still denormalize claim projections onto their own entities during publish.
Queueing Tasks
Applications queue and run tasks from workflow listeners. Use task constants instead of string literals so built-in task names stay refactorable:
<?php namespace App\Workflow; use App\Entity\GalleryImage; use Doctrine\ORM\EntityManagerInterface; use Survos\AiWorkflowBundle\Task\EnrichFromThumbnailTask; use Survos\AiWorkflowBundle\Task\TaskRunner; use Survos\AiWorkflowBundle\Workflow\SubjectFlow; use Symfony\Component\Workflow\Attribute\AsTransitionListener; use Symfony\Component\Workflow\Event\TransitionEvent; final class GalleryWorkflow { public function __construct( private readonly EntityManagerInterface $entityManager, private readonly TaskRunner $taskRunner, ) { } #[AsTransitionListener(SubjectFlow::WORKFLOW_NAME, SubjectFlow::TRANSITION_PREPARE)] public function onPrepare(TransitionEvent $event): void { $image = $this->getImage($event); if ($image->getWorkflowQueue() === []) { $image->setWorkflowQueue([EnrichFromThumbnailTask::TASK]); } $this->entityManager->flush(); } #[AsTransitionListener(SubjectFlow::WORKFLOW_NAME, SubjectFlow::TRANSITION_OBSERVE)] public function onObserve(TransitionEvent $event): void { $image = $this->getImage($event); $this->taskRunner->runNext($image); $this->entityManager->flush(); } private function getImage(TransitionEvent $event): GalleryImage { $image = $event->getSubject(); \assert($image instanceof GalleryImage); return $image; } }
TaskRunner::runNext() removes one task from the queue, records claims through survos/claims-bundle, and appends any follow-up tasks returned by the task. For image workflows, EnrichFromThumbnailTask::TASK is usually the first observation task; it can append higher-resolution observation tasks such as OcrMistralTask::TASK or TranscribeHandwritingTask::TASK.
Naming
Inside Survos\AiWorkflowBundle, class names intentionally avoid the Ai prefix where the namespace already carries it:
SubjectFlowTaskInterfaceTaskRegistryTaskRunner
Failure Model
There is no failed place in the initial workflow. Failures should be recorded on task runs, claims, or app-specific fields while the subject remains at its current milestone. Apps can add a failure place later if they need one.
Workflow integration
Enable survos/workflow-extras-bundle and survos/workflow-messenger-bundle,
with workflow: ~ in your application configuration. State-bundle is no longer
required. The AI workflow supports subjects implementing PendingStepsInterface;
other workflow subjects, such as media records, retain their own lifecycle.
Doctrine subjects keep a scalar marking property and the usual Symfony marking
store accessors. Existing persisted place values are unchanged.
Configure Messenger transports named ai_subject.prepare, ai_subject.observe,
ai_subject.analyze, ai_subject.review, and ai_subject.publish. Transition
metadata selects these transports explicitly. The application's transport DSNs
control how the jobs are delivered; the bundle does not create queues implicitly.
Observation repeats while observation tasks remain. Analysis begins after observation drains and repeats only while analysis tasks remain. Review waits for analysis to drain; automatic continuation then publishes once. Applications can add guard listeners for human approval or other review policy. Manual review and publish transitions remain repeatable. Pending tasks retain FIFO ordering.
Menu integration uses MenuSlot::ADMIN_NAVBAR_MENU through AsMenuListener.
Typed document assessments and cached operations
DocumentAssessment is a shared answer contract for source rules, humans, and AI:
handwritten, printed, and correspondence are nullable booleans, with a required
basis. Unknown is distinct from false. AssessmentEvidence attaches a non-AI
origin (source, human, or tool) and provenance reference outside the model schema.
The describe_source task extracts visual evidence without choosing follow-up tasks.
assess_document uses the application's ai.agent.assessment, requests
response_format: DocumentAssessment::class, and returns its hydrated DTO in
TaskResult::output. It needs supplied text evidence. Applications supply the
workflow policy, model configuration, persistence, and any trusted source rules.
AbstractPromptTask now consumes class-based outputs through asObject($class).
Only tasks without a response class use the existing text/JSON fallback. Existing
claim mappers still receive arrays, converted from the hydrated DTO without text
parsing. outputClass() exposes the output contract for schema-aware cache keys.
OperationCache::execute($identity, $operation) stores successful object results for
24 hours by default. The caller must include all relevant inputs, prompt/schema
versions, model/provider options, content fingerprints, and scope in $identity.
Exceptions are not cached. The returned CachedExecution separates cache-hit status
and current elapsed time from the original result's creation time and duration.
The service uses cache.app; a consumer can override its cache argument.
Idea: OpenRouter as an optional platform (not adopted)
OpenRouter is one OpenAI-compatible endpoint in front of hundreds of models, with
fallbacks, per-token pass-through pricing and cross-provider cost reporting. symfony/ai-open-router-platform
(v1.0.0-BETA1) is a ready-made symfony/ai bridge, so using it is cheaper than writing our own multi-model router.
Worth trying for the text analysis tasks (SummarizeTask, GenerateTitleTask, ExtractMetadataTask, ...):
register it as another ai.platform.* and A/B models per task with config only. Keep recording the resolved model
in RunMeta so claims stay reproducible.
Not a fit for:
OcrMistralTaskandPeriodicalStructureTask— they pin a model and call Mistral's dedicated endpoints directly.BatchableTaskInterface— batching relies on provider-native batch APIs (mistral,openai,anthropic).- Bulk corpus work, where a gateway adds a hop and possibly a fee.
- Image AI, which goes through mediary.
Before relying on it, check from the API docs: the fee on top of provider rates, structured-output and tool-calling support per model, and data-retention terms. Only the homepage was reviewed (2026-10-27).