Skip to main content

Symfony Configuration

The public config root for packages/symfony is reserved as:
instructor:
# @doctest id="a273"
The initial subtree map is:
  • connections
  • embeddings
  • extraction
  • http
  • events
  • agent_ctrl
  • agents
  • sessions
  • telemetry
  • logging
  • testing
  • delivery
This is intentionally defined before the runtime wiring lands so later tasks can extend one coherent config tree instead of introducing multiple unrelated roots.

Baseline Runtime Shapes

The package now translates the core runtime subtrees into typed config objects owned by:
  • Cognesy\Polyglot\Inference\Config\LLMConfig
  • Cognesy\Polyglot\Embeddings\Config\EmbeddingsConfig
  • Cognesy\Instructor\Config\StructuredOutputConfig
  • Cognesy\Http\Config\HttpClientConfig
Preferred Symfony-oriented shapes are:
instructor:
  connections:
    default: openai
    items:
      openai:
        driver: openai
        api_key: '%env(OPENAI_API_KEY)%'
        model: gpt-4o-mini

  embeddings:
    default: openai
    connections:
      openai:
        driver: openai
        model: text-embedding-3-small

  extraction:
    output_mode: tools
    max_retries: 2

  http:
    driver: symfony
    timeout: 20
    connect_timeout: 5
# @doctest id="843e"
For migration safety, the translator also tolerates flatter connection maps such as instructor.connections.openai and legacy llm.presets.* lookups. The current Symfony config tree is strict about subtree shape for the shipped core runtime surface:
  • connections, embeddings, extraction, http, and events must be arrays
  • flat connection maps are still normalized into items or connections internally
  • provider-specific extra connection keys such as temperature are preserved and forwarded into runtime options
  • malformed subtree shapes now fail during Symfony config processing instead of silently degrading to defaults

HTTP Transport Rules

The baseline transport rules are:
  • instructor.http.driver: symfony uses the bundle-owned HTTP contract and prefers Symfony’s http_client service when it is available
  • instructor.http.driver: framework and instructor.http.driver: http_client are normalized to the same Symfony transport
  • other driver values such as curl or guzzle keep using the configured bundled HTTP driver explicitly
This keeps transport selection explicit while avoiding request-lifecycle assumptions in CLI, worker, and test contexts.

Event Bridge Rules

The bundle now owns the CanHandleEvents service and uses a package-owned dispatcher with an optional Symfony parent bridge.
  • instructor.events.dispatch_to_symfony: true keeps framework listeners connected through Symfony’s event dispatcher
  • instructor.events.dispatch_to_symfony: false keeps runtime events inside the package-owned bus only
This keeps runtime listeners and wiretaps package-local while still allowing Symfony applications to observe the same event stream when they want to. See packages/symfony/docs/delivery.md for the distinction between internal wiretaps, the projected progress bus, Symfony listener delivery, Messenger flows, and CLI observation helpers.

Session Persistence Config

The package now defines an explicit instructor.sessions subtree:
instructor:
  sessions:
    store: memory # memory | file

    file:
      directory: '%kernel.cache_dir%/instructor/agent-sessions'
# @doctest id="89cd"
Rules:
  • store: memory keeps the native-agent runtime process-local and ephemeral
  • store: file enables the first supported persisted adapter for Symfony
  • file.directory controls where JSON session payloads and lock files are stored
  • applications that need a custom backend should replace Cognesy\Agents\Session\Contracts\CanStoreSessions in the container rather than expecting packages/agents to own Symfony-specific storage policy
Compatibility aliases currently accepted by the config tree:
  • sessions.driver -> sessions.store
  • sessions.session_store -> sessions.store
  • sessions.directory -> sessions.file.directory
See packages/symfony/docs/sessions.md for the storage conventions, resume flow, and conflict semantics.

Telemetry Config

The package now defines a typed instructor.telemetry subtree that matches the shared telemetry package model instead of leaving exporter composition to application glue:
instructor:
  telemetry:
    enabled: false
    driver: null # null | otel | langfuse | logfire | composite
    service_name: symfony

    projectors:
      instructor: true
      polyglot: true
      http: true
      agent_ctrl: true
      agents: true

    http:
      capture_streaming_chunks: false

    drivers:
      composite:
        exporters: [otel, langfuse]

      otel:
        endpoint: '%env(default::INSTRUCTOR_TELEMETRY_OTEL_ENDPOINT)%'
        headers: { }

      langfuse:
        host: '%env(default::INSTRUCTOR_TELEMETRY_LANGFUSE_HOST)%'
        public_key: '%env(default::INSTRUCTOR_TELEMETRY_LANGFUSE_PUBLIC_KEY)%'
        secret_key: '%env(default::INSTRUCTOR_TELEMETRY_LANGFUSE_SECRET_KEY)%'

      logfire:
        endpoint: '%env(default::INSTRUCTOR_TELEMETRY_LOGFIRE_ENDPOINT)%'
        write_token: '%env(default::INSTRUCTOR_TELEMETRY_LOGFIRE_WRITE_TOKEN)%'
        headers: { }
# @doctest id="32d1"
Rules:
  • enabled: false keeps telemetry fully optional in the baseline package path
  • driver selects exactly one exporter strategy for the package-owned telemetry service graph
  • driver: composite fans out to the ordered drivers.composite.exporters list
  • projectors controls which runtime surfaces should attach to the shared telemetry bridge once telemetry wiring is enabled
  • http.capture_streaming_chunks exists because chunk capture is useful for debugging but too noisy for a hard-coded default
This gives later service-wiring and lifecycle-hook tasks a stable public root without forcing Symfony adopters to invent their own exporter config conventions first. See packages/symfony/docs/telemetry.md for the runtime bridge, projector ownership, and lifecycle behavior that now hangs off this subtree. See packages/symfony/docs/operations.md for how the same telemetry surface behaves in web, API, Messenger, and CLI app shapes.

AgentCtrl Config Model

The package now defines the public instructor.agent_ctrl config subtree and uses it to drive both builder registration and runtime-context policy. Preferred shape:
instructor:
  agent_ctrl:
    enabled: false
    default_backend: claude_code

    defaults:
      timeout: 300
      working_directory: '%kernel.project_dir%'
      sandbox_driver: host

    execution:
      transport: sync
      allow_cli: true
      allow_http: false
      allow_messenger: true

    continuation:
      mode: fresh
      session_key: agent_ctrl_session_id
      persist_session_id: true
      allow_cross_context_resume: true

    backends:
      claude_code:
        model: claude-sonnet-4-20250514
      codex:
        model: codex
      opencode:
        model: anthropic/claude-sonnet-4-20250514
      pi:
        enabled: false
      gemini:
        enabled: false
# @doctest id="60dc"
Notes:
  • defaults carries the shared builder options that map cleanly onto AgentCtrlConfig: model, timeout, working_directory, and sandbox_driver
  • execution captures framework policy for sync versus Messenger-triggered execution and whether CLI, HTTP, or Messenger entrypoints are allowed
  • continuation captures the default continuation mode, session-key naming, and whether explicit resume_session handoff is allowed across runtime contexts
  • backends holds per-agent overrides for claude_code, codex, opencode, pi, and gemini
Runtime services driven by this config:
  • Cognesy\Instructor\Symfony\AgentCtrl\SymfonyAgentCtrl is the backend-aware builder entrypoint
  • Cognesy\Instructor\Symfony\AgentCtrl\SymfonyAgentCtrlRuntimes builds context adapters for cli, http, and messenger
  • named service IDs exist for instructor.agent_ctrl.runtime.cli, instructor.agent_ctrl.runtime.http, and instructor.agent_ctrl.runtime.messenger
  • CLI and HTTP runtimes only allow inline execute() / executeStreaming() when execution.transport is sync
  • the Messenger runtime can still execute inline inside a worker process even when the app-level transport is messenger
  • runtime adapters expose continuationPolicy(), continuation(), continueLast(), resumeSession(), and handoff() so Symfony code can create same-context continue_last references locally and pass explicit resume_session handoff references from web or CLI entrypoints into Messenger workers
Compatibility aliases currently accepted by the config tree:
  • default_agent -> default_backend
  • directory -> working_directory
  • sandbox -> sandbox_driver
  • top-level backend keys such as agent_ctrl.codex.* are normalized into agent_ctrl.backends.codex.*
This keeps the public Symfony shape explicit while still tolerating the flatter Laravel-style config keys during migration and early package adoption.

Delivery Config

The package now defines a typed instructor.delivery subtree for progress projection, optional CLI observation, and Messenger-backed async work:
instructor:
  delivery:
    progress:
      enabled: true
    cli:
      enabled: false
      use_colors: true
      show_timestamps: true
    messenger:
      enabled: true
      bus_service: message_bus
      observe_events:
        - App\Runtime\Event\ProjectCompleted
        - Cognesy\Agents\Session\Events\SessionSaved
# @doctest id="bc2c"
Rules:
  • delivery.progress.enabled: true keeps the projected progress bus attached to the package-owned runtime bus
  • delivery.cli.enabled: true auto-attaches the built-in console observer to the projected progress bus
  • delivery.cli.use_colors and delivery.cli.show_timestamps control the package-owned CLI printer
  • delivery.messenger.enabled: true enables the package-owned Messenger delivery seam
  • delivery.messenger.bus_service points the observation bridge at the Symfony bus service that should receive runtime observation messages
  • delivery.messenger.observe_events is an explicit allow-list of runtime event classes or interfaces that should be forwarded as RuntimeObservationMessage envelopes
The package-owned delivery entrypoints are:
  • Cognesy\Instructor\Symfony\Delivery\Progress\Contracts\CanHandleProgressUpdates
  • Cognesy\Instructor\Symfony\Delivery\Progress\RuntimeProgressUpdate
  • Cognesy\Instructor\Symfony\Delivery\Cli\SymfonyCliObservationFormatter
  • Cognesy\Instructor\Symfony\Delivery\Cli\SymfonyCliObservationPrinter
  • Cognesy\Instructor\Symfony\Delivery\Messenger\ExecuteAgentCtrlPromptMessage
  • Cognesy\Instructor\Symfony\Delivery\Messenger\ExecuteNativeAgentPromptMessage
  • Cognesy\Instructor\Symfony\Delivery\Messenger\RuntimeObservationMessage
  • Cognesy\Instructor\Symfony\Delivery\Messenger\ExecuteAgentCtrlPromptMessageHandler
  • Cognesy\Instructor\Symfony\Delivery\Messenger\ExecuteNativeAgentPromptMessageHandler
This keeps queue dispatch and progress projection explicit:
  • applications dispatch package-owned execution messages instead of treating Symfony listeners as an async transport
  • applications can consume CanHandleProgressUpdates when they need transport-agnostic lifecycle or streaming updates for SSE, WebSockets, Mercure, or polling
  • CLI output stays opt-in and builds on the same projected progress bus instead of formatting the raw runtime bus directly
  • the internal event bus remains the source of truth and can optionally forward selected runtime events into Messenger for queued observation work
  • Messenger delivery remains separate from the projected progress bus so async work does not depend on console-formatting assumptions
See packages/symfony/docs/operations.md for the recommended production shape for web, API, Messenger, and CLI applications. See packages/symfony/docs/migration.md for moving existing app-local Symfony delivery wiring onto this supported subtree.

Native Agent Extension Rules

The native-agent surface now supports two complementary extension modes. Autoconfiguration:
  • services implementing Cognesy\Agents\Tool\Contracts\ToolInterface are autoconfigured into the native tool registry
  • services implementing Cognesy\Agents\Builder\Contracts\CanProvideAgentCapability are autoconfigured into the capability registry
  • services whose class is Cognesy\Agents\Template\Data\AgentDefinition are autoconfigured into the definition registry
  • services whose class is Cognesy\Instructor\Symfony\Agents\SchemaRegistration are autoconfigured into the schema registry
Manual tags remain supported through Cognesy\Instructor\Symfony\Agents\AgentRegistryTags:
  • AgentRegistryTags::TOOLS
  • AgentRegistryTags::CAPABILITIES
  • AgentRegistryTags::DEFINITIONS
  • AgentRegistryTags::SCHEMAS
Use autoconfiguration when the service type already makes the contribution obvious. Use manual tags when you need an explicit override, especially for capability-name overrides via the alias tag attribute. Example:
services:
  App\Ai\Tools\SearchDocsTool:
    autowire: true
    autoconfigure: true

  app.ai.capability.review:
    class: App\Ai\Capability\ReviewCapability
    autowire: true
    tags:
      - { name: instructor.agent.capability, alias: review }
# @doctest id="4219"

Ownership Boundaries

packages/symfony owns:
  • Symfony bundle registration
  • config normalization and framework-facing defaults
  • container wiring and service aliases
  • migration of scattered Symfony glue into one supported framework package
Other packages keep their core responsibilities:
  • packages/events keeps reusable event-bus primitives, including the raw Symfony bridge
  • packages/logging keeps reusable logging primitives and factories
  • core runtime packages keep domain logic; packages/symfony only provides framework integration
This boundary keeps the Symfony package batteries-included without duplicating core runtime logic. Use packages/symfony/docs/migration.md when moving older event, logging, telemetry, or delivery glue under that boundary.