# Typed Decisions (/docs/ai/typed-decisions)



Ductor's existing inference proxy generates text, tool calls, and agent actions.
The typed-decision plane does something narrower: Ductor supplies bounded state
and an exact set of allowed answers, and the evaluator returns a typed answer,
probabilities, and confidence.

That distinction is the safety boundary. A decision model can rank choices that
Ductor has already authorized; it cannot create authority or execute anything.

```mermaid
flowchart LR
  P["Hard policy + eligibility"] --> S["Bounded canonical state"]
  S --> D["Typed evaluator<br/>Choice · Noul · Score"]
  D --> V["Strict validation + threshold"]
  V --> R["Authoritative revalidation"]
  R --> A["Existing durable action path"]
```

## How this differs from inference [#how-this-differs-from-inference]

|          | Inference proxy                      | Typed decisions                                      |
| -------- | ------------------------------------ | ---------------------------------------------------- |
| Job      | Generate the next response or action | Select or classify from closed options               |
| Wire API | Provider chat/messages APIs          | System One / Decisions API                           |
| Output   | Text, tool calls, usage              | Typed answer, distribution, confidence, usage        |
| Policy   | Pinned model and tool/runtime bounds | Pinned question set, projection, options, thresholds |
| Failure  | Agent/runtime error policy           | Deterministic fallback or review policy              |

The two layers work together. For example, typed model routing can choose an
already-enabled inference provider/model; the chosen model still runs through
the normal inference proxy.

## Jev without vendor lock-in [#jev-without-vendor-lock-in]

Jev is the first evaluator, not an application-level dependency. Routing and
agent packages depend on a provider-neutral evaluator contract. The only
Jev/System One-specific code is the replaceable infrastructure adapter.

<Callout type="info" title="OpenRouter is optional">
  Ductor can call TypeSafe directly at `https://api.typesafe.ai/v1/systemone`
  or OpenRouter at `https://openrouter.ai/api/alpha/decisions`. With the decision
  plane disabled, Ductor makes no such call and preserves deterministic behavior.
</Callout>

OpenRouter chat-completions configuration under `ai_inference` is separate.
The typed-decision adapter does not send Jev through the OpenAI-compatible chat
endpoint.

```yaml title="TypeSafe direct"
ai:
  decision:
    enabled: true
    provider: typesafe
    endpoint: https://api.typesafe.ai/v1/systemone
    api_key_env: TYPESAFE_API_KEY
    max_concurrent: 32
    allowed_data_classes: [public, internal]
    circuit_breaker:
      enabled: true
      consecutive_failures: 5
      open_timeout: 30s
    rate_limit:
      enabled: true
      requests_per_minute: 120
      burst: 20
```

```yaml title="OpenRouter Decisions"
ai:
  decision:
    enabled: true
    provider: openrouter
    endpoint: https://openrouter.ai/api/alpha/decisions
    api_key_env: OPENROUTER_API_KEY
```

Provider selection is explicit. Ductor never switches providers because it
happens to find a different credential.

## Shipped consumers [#shipped-consumers]

<Cards>
  <Card title="Semantic recipient routing" href="/docs/ai/semantic-routing">
    Rank an exact, deterministic recipient shortlist; routing still owns
    eligibility, capacity, assignment, and finalization.
  </Card>

  <Card title="Agent model routing" href="/docs/ai/durable-agent-runtime">
    Select among exact models already enabled by the inference configuration,
    then pin the choice and evidence into the immutable definition.
  </Card>

  <Card title="Completion gate" href="/docs/ai/durable-agent-runtime">
    Classify a hard-gate-clean turn as close, continue, or review, with a
    bounded retry count and durable receipt.
  </Card>
</Cards>

## Config or persisted policy—not provider code [#config-or-persisted-policynot-provider-code]

Provider config contains only endpoint, credential reference, timeout, retry,
and protocol limits. Consumer policy owns exact models, versions, question IDs,
objectives, projections, thresholds, and rollout mode.

For agents, Ductor copies configured policy into a definition before its first
immutable pin. A policy already present in a persisted definition wins. Changing
config never changes an active session.

```yaml title="Governed agent decisions"
ai:
  decision:
    agent:
      model_routing:
        mode: shadow
        policy_version: agent-model-policy-v1
        candidates:
          - { provider: openai, model: gpt-5-mini }
          - { provider: anthropic, model: claude-haiku }
        allow_fallback: true
        evaluator_model: typesafe/jev-1.13-20260917
        profile: agent-model
        profile_version: v1
        state_version: agent-model-state-v1
        question_set_version: agent-model-questions-v1
        question_id: select_model
        objective: Choose the safest capable model for the admitted turn.
        min_confidence: 0.8
        max_state_bytes: 16384
        data_class: internal
      completion:
        mode: shadow
        evaluator_model: typesafe/jev-1.13-20260917
        question_set_version: completion-v1
        risk_class: medium
        failure_policy: review
        min_close_confidence: 0.9
        max_continue_attempts: 2
        state_projection_version: completion-state-v1
        data_class: internal
```

Use exact versioned models for enforcement. Moving aliases such as
`~typesafe/jev-latest` are appropriate only for development or shadow
evaluation because they can change without a Ductor policy version changing.

## Enterprise controls [#enterprise-controls]

Decision calls use Ductor's DNS-rebind-safe egress transport, reject
unclassified or disallowed state before network I/O, apply a Redis-backed
per-tenant rate gate, cap concurrent calls, and isolate provider failures with
a circuit breaker. They can also reserve tenant spend before provider I/O,
reject an estimated call above its ceiling, record durable
request/input/output/cost usage, and settle the reservation from actual input
tokens.

```yaml title="Fail-closed metering"
ai:
  decision:
    metering:
      enabled: true
      monthly_budget_usd: 100
      max_cost_per_decision_usd: 0.01
      reservation_ttl: 5m
      pricing_version: openrouter-2026-09-18
      input_price_usd_per_million:
        typesafe/jev-1.13-20260917: 0.042
```

The price map must contain every requested and possible resolved model ID.
Missing tenant identity, pricing, durable usage, or cross-replica budget
coordination fails before the provider call.

Each consumer declares `public`, `internal`, `confidential`, or `restricted`.
Only classes present in `allowed_data_classes` may leave the deployment;
confidential and restricted projections require an explicit operator opt-in.

The call boundary is ordered so cheap, authoritative denials happen first:

```mermaid
flowchart LR
  C["Consumer projection"] --> E["Data-class egress"]
  E --> R["Distributed tenant rate"]
  R --> B["Budget reservation"]
  B --> O["Bounded telemetry"]
  O --> X["Concurrency + circuit"]
  X --> P["TypeSafe / OpenRouter"]
```

An egress, rate, pricing, budget, or configuration denial makes no provider
call. Provider retries remain inside one bounded total deadline. Only transport,
timeout, overload, server, and malformed-provider failures count against the
circuit; caller validation failures do not.

Receipts retain versioned identifiers, hashes, probabilities, confidence, and
bounded outcome classes. They do not retain raw projected state, credentials,
provider response bodies, free-form errors, or latency.

## Failure, replay, and rollout [#failure-replay-and-rollout]

| Consumer            | Provider failure or low confidence                                               | Historical behavior                                                                                           |
| ------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Recipient routing   | Follow the profile's declared deterministic pipeline fallback.                   | `original_lock` reads the persisted slate and evidence with zero evaluator calls.                             |
| Agent model routing | Keep the static pinned model-policy result and record a bounded fallback class.  | The immutable definition reuses its model receipt; active sessions are not re-routed.                         |
| Completion gate     | Apply the pinned complete-or-review failure policy and bounded recovery counter. | The journal replays the committed completion receipt and ordered `text_reset`; it does not repeat evaluation. |

Roll out one consumer and one exact model/profile version at a time:

1. Enable the provider boundary while leaving consumers `off`.
2. Configure data classes, tenant rate limits, exact pricing, and durable metering.
3. Run in `shadow` and compare recommendations with deterministic outcomes.
4. Promote only after latency, cost, fallback, false-route or false-close, and
   review targets pass.
5. Roll back the affected consumer to `off`; immutable historical evidence and
   already pinned definitions remain readable.

## Where it should—and should not—go next [#where-it-shouldand-should-notgo-next]

Good future fits are case-assignee ranking, opt-in workflow variant selection,
governed tool/connector disambiguation, advisory route-authoring review, and
RFQ worker shortlisting. Each starts from an exact eligible set and ends at an
existing authoritative boundary.

Do not use typed decisions for authentication, authorization, fraud or
compliance enforcement, pricing or budget arithmetic, locks or leases, schema
validation, idempotency, promotion, deployment, or exact replay.

## Related [#related]

<Cards>
  <Card title="Semantic Routing" href="/docs/ai/semantic-routing">
    Governed recipient shortlists, thresholds, fallbacks, durable evidence, and replay.
  </Card>

  <Card title="Durable Agent Runtime" href="/docs/ai/durable-agent-runtime">
    Immutable model decisions and close/continue/review lifecycle behavior.
  </Card>

  <Card title="Inference Proxy" href="/docs/ai/inference-proxy">
    The separate generative provider path selected by agent model policy.
  </Card>
</Cards>
