> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instructorphp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

# Symfony Configuration

The public config root for `packages/symfony` is reserved as:

```yaml theme={null}
instructor:
# @doctest id="8239"
```

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:

```yaml theme={null}
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="eab3"
```

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:

```yaml theme={null}
instructor:
  sessions:
    store: memory # memory | file

    file:
      directory: '%kernel.cache_dir%/instructor/agent-sessions'
# @doctest id="493f"
```

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:

```yaml theme={null}
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="6ed0"
```

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:

```yaml theme={null}
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="b62b"
```

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:

```yaml theme={null}
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="7651"
```

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:

```yaml theme={null}
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="ac81"
```

## 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.
