AI & Agents

Typed Decisions

Provider-neutral, versioned semantic choices for routing, model selection, and durable completion gates, with Jev as the first adapter.

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.

Hard policy + eligibility Bounded canonical state Typed evaluatorChoice · Noul · Score Strict validation + threshold Authoritative revalidation Existing durable action path

How this differs from inference

Inference proxyTyped decisions
JobGenerate the next response or actionSelect or classify from closed options
Wire APIProvider chat/messages APIsSystem One / Decisions API
OutputText, tool calls, usageTyped answer, distribution, confidence, usage
PolicyPinned model and tool/runtime boundsPinned question set, projection, options, thresholds
FailureAgent/runtime error policyDeterministic 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 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.

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.

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

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

Config or persisted policy—not 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.

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

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.

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:

Consumer projection Data-class egress Distributed tenant rate Budget reservation Bounded telemetry Concurrency + circuit 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

ConsumerProvider failure or low confidenceHistorical behavior
Recipient routingFollow the profile's declared deterministic pipeline fallback.original_lock reads the persisted slate and evidence with zero evaluator calls.
Agent model routingKeep 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 gateApply 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

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.