edge:
  id: "edge-example"
  name: "Example Edge"

server:
  listen: "0.0.0.0:9090"
  # advertise_host is the host other components (Node bootstrap, OpenAI client)
  # use to reach this edge. Empty means resolve automatically from interfaces.
  advertise_host: ""

# bootstrap is the artifact/bootstrap channel iop-edge can render in
# bootstrap commands and `env` output. Leave artifact_base_url empty until a
# field channel is provisioned.
bootstrap:
  artifact_base_url: ""

tls:
  enabled: false

logging:
  level: "info"
  pretty: true
  # path is the edge log file. Empty falls back to <binary-dir>/logs/edge.log
  # for the bundled dev binary; explicit values win.
  path: ""

metrics:
  port: 19092

# control_plane connects this Edge to a Control Plane instance.
# Set enabled: true and wire_addr to the Control Plane TCP endpoint to
# activate the outbound connector. Leave enabled: false (default) for
# standalone / local-only Edge deployments.
control_plane:
  enabled: false
  wire_addr: ""
  reconnect_interval_sec: 5

# refresh enables the Edge-local admin API used by `iop-edge config refresh`.
# Keep it loopback-only unless an operator-specific access control layer is added.
refresh:
  enabled: false
  listen: "127.0.0.1:19093"

# Requests with an estimated input token count at or above this value are
# classified as long-context for admission policy.
long_context_threshold_tokens: 100000

# provider_pool is the canonical, shared queue policy owner for every provider
# candidate in the pool. Legacy per-provider max_queue/queue_timeout_ms are
# compatibility fields only; once provider_pool is set, those legacy values
# are ignored by effective policy resolution.
provider_pool:
  max_queue: 16
  queue_timeout_ms: 30000

a2a:
  enabled: false
  listen: "0.0.0.0:8081"
  path: "/a2a"
  node: ""
  adapter: "cli"
  session_id: "a2a"
  timeout_sec: 120
  bearer_token: ""

openai:
  enabled: false
  listen: "0.0.0.0:18081"
  bearer_token: ""
  # principal_tokens maps IOP bearer-token-operation identities to hashed
  # OpenAI-compatible bearer tokens. Raw tokens are never stored here; only
  # a SHA-256 hash (lowercase hex, 64 chars) of each issued token is kept.
  # When set, callers authenticate via hash match against this list; the
  # legacy single bearer_token above still works as an unmapped fallback.
  # Example (values below are illustrative hashes, not real tokens):
  #   principal_tokens:
  #     - token_ref: "iop-tok-alice"
  #       token_hash_sha256: "<sha256 hex of the issued token>"
  #       principal_ref: "user:alice"
  #       principal_alias: "alice"
  principal_tokens: []
  # provider_auth forwards a caller-supplied raw provider token to the selected
  # OpenAI-compatible provider tunnel. The raw token is read only from the
  # request header and must not be stored in config, docs, or logs.
  provider_auth:
    enabled: false
    # Defaults when enabled:
    # from_header: "X-IOP-Provider-Authorization"
    # target_header: "Authorization"
    # scheme: "Bearer"
    # required: true
  # The selected provider determines the execution path: OpenAI-compatible
  # providers use passthrough, while Ollama/CLI/native providers use normalized
  # execution. Caller metadata does not select the route or response shape.
  node: ""
  adapter: "ollama"
  target: ""
  models: []
  # Legacy/compatibility model_routes (discouraged for new deploys).
  # model_routes was the adapter/target route catalog that mapped external model
  # ids to adapter + target. It is superseded by the provider-pool approach:
  #   1. Define canonical routing keys with models[].
  #   2. Declare provider candidates with nodes[].providers[].
  #   3. Link them via models[].providers[provider_id] = served_model.
  # For details see the Provider-pool section below.
  # This block is a backward-compat fallback only. New deploys should use
  # top-level models[] + nodes[].providers[].
  session_id: "openai"
  timeout_sec: 120
  strict_output: true
  strict_stream_buffer: false

# === Provider-pool (models[] / nodes[].providers[]) — recommended ===
# Top-level models[] defines canonical routing keys and their provider-pool mapping.
# Each entry id is the external model id; providers map maps provider id → served model.
#
# Example:
#   models:
#     - id: "qwen3.6:35b"
#       display_name: "Qwen 3.6 35B"
#       context_window_tokens: 262144
#       providers:
#         vllm-gpu: "nvidia/Qwen3.6-35B-A3B-NVFP4"
#         ollama-local: "qwen3.6:35b"
#
# nodes[].providers[] defines each provider candidate with catalog and execution fields (Provider-First).
# nodes[].providers[].id is the stable provider identity referenced by models[].providers keys.
# nodes[].providers[].type — runtime type (vllm, ollama, lemonade, sglang, openai_api,
#   seulgivibe_claude, seulgivibe_openai, cli).
# nodes[].providers[].category — "api", "cli", or "local_inference".
# nodes[].providers[].endpoint / base_url / command — type-specific execution fields.
# nodes[].providers[].models — served model names this provider can serve.
# nodes[].providers[].health — observed health state string.
# nodes[].providers[].capacity — provider-pool max concurrent execution slots; 0 is not dispatchable.
# nodes[].providers[].total_context_tokens — provider runtime total KV/context budget used for
#   long-context admission. Must satisfy total_context_tokens >= context_window_tokens *
#   long_context_capacity for every model group referencing this provider. Live-apply on refresh.
# nodes[].providers[].long_context_capacity — concurrent long-context slots (context_window-sized
#   requests). Long requests occupy a normal slot and a long slot; 0 means no dedicated long limit.
#   Live-apply on refresh.
# nodes[].providers[].max_queue — legacy per-provider queue depth (compatibility only).
#   Effective policy is owned by the root provider_pool.max_queue.
#   When provider_pool is set, per-provider values are ignored.
# nodes[].providers[].queue_timeout_ms — legacy per-provider queue timeout (compatibility only).
#   Effective policy is owned by the root provider_pool.queue_timeout_ms.
#   When provider_pool is set, per-provider values are ignored.
# nodes[].providers[].lifecycle_capabilities — coarse lifecycle capabilities list.
# nodes[].providers[].enabled — on/off dispatch switch; omit or true = enabled, false = excluded from
#   dispatch pool. Disabled providers appear in status with status=disabled, capacity=0. Does not
#   stop adapter processes. Classified as live-apply (no restart required) on config refresh.

console:
  adapter: "cli"
  target: "codex"
  session_id: "default"
  background: false
  timeout_sec: 240

# Top-level models[] defines canonical routing keys and their provider-pool mapping.
# models[].id is the external model id; providers maps provider id → served model.
models:
  - id: "qwen3.6:35b"
    display_name: "Qwen 3.6 35B"
    context_window_tokens: 262144
    default_max_tokens: 32768
    min_max_tokens: 32768
    default_thinking_token_budget: 1024
    providers:
      mac-mlx-vllm: "mlx-community/Qwen3.6-35B-A3B-4bit"
      # Same model group can keep a normalized-only Ollama provider as a low
      # capacity/high priority fallback candidate. Clients do not select this
      # execution path; capacity + priority + availability choose the provider.
      ollama-local: "qwen3.6:35b"
  - id: "local-llama3"
    display_name: "Local Llama 3"
    context_window_tokens: 8192
    providers:
      # Ollama-only model groups are valid and run through normalized execution.
      ollama-local: "llama3.1:8b"
  # Example: CLI providers (codex, codex-exec) do not serve models.
  # They are declared in nodes[].providers[] for dispatch, not in models[].
  #   - id: "codex-task"
  #     providers:
  #       codex: "app-server"
  #       codex-exec: "exec"
  # Example: Seulgivibe static catalog. Endpoint and raw token values belong to
  # environment-specific private config/request headers, not tracked files.
  #   - id: "claude-sonnet-4-5"
  #     providers:
  #       seulgivibe-claude: "claude-sonnet-4-5"
  #   - id: "claude-opus-4-8"
  #     providers:
  #       seulgivibe-claude: "claude-opus-4-8"
  #   - id: "claude-fable-5"
  #     providers:
  #       seulgivibe-claude: "claude-fable-5"
  #   - id: "gpt-5.1"
  #     providers:
  #       seulgivibe-openai: "gpt-5.1"
  #   - id: "gpt-5.5"
  #     providers:
  #       seulgivibe-openai: "gpt-5.5"

nodes:
  # id is the stable node identity; omitting it falls back to an auto UUID (dev only).
  # agent_kind selects the registration kind; omitting it defaults to "generic-node".
  # Allowed values: "generic-node" (default).
  #
  # Provider-First example (recommended):
  - id: "node-example-01"
    alias: "example-node"
    token: "<node-token>"
    agent_kind: "generic-node"
    providers:
      - id: "claude-tui"
        type: "cli"
        category: "cli"
        command: "claude"
        args:
          - "--dangerously-skip-permissions"
        env:
          - "TERM=xterm-256color"
        mode: "persistent-lazy"
        capacity: 1
      - id: "codex"
        type: "cli"
        category: "cli"
        command: "codex"
        args:
          - "app-server"
        mode: "codex-app-server"
        capacity: 1
      - id: "codex-exec"
        type: "cli"
        category: "cli"
        command: "codex"
        args:
          - "exec"
          - "--json"
        resume_args:
          - "exec"
          - "resume"
          - "--json"
        output_format: "codex-json"
        mode: "codex-exec"
        capacity: 1
      # Mac MLX vLLM provider (local GPU inference).
      # Tracked secrets (API key, etc.) are not stored in this file.
      - id: "mac-mlx-vllm"
        type: "openai_api"
        category: "local_inference"
        endpoint: "http://127.0.0.1:8002/v1"
        models:
          - "mlx-community/Qwen3.6-35B-A3B-4bit"
        health: "healthy"
        capacity: 2
        priority: 0
        # Long-context admission budget: total_context_tokens >= 262144 * 1.
        # mac-mlx-vllm runtime KV budget is 262144 (max_kv_size / max_request_tokens),
        # so only one full 262144-window long request fits at a time. long_context_capacity
        # is the long-slot count, not the normal capacity (2); do not conflate them.
        total_context_tokens: 262144
        long_context_capacity: 1
      # Ollama provider kept in the provider pool with explicit low concurrency.
      # It is not removed from mixed groups; operators weight it with capacity
      # and priority instead.
      - id: "ollama-local"
        type: "ollama"
        category: "local_inference"
        base_url: "http://127.0.0.1:11434"
        models:
          - "qwen3.6:35b"
          - "llama3.1:8b"
        health: "healthy"
        capacity: 1
        priority: 50
      # Seulgivibe OpenAI-compatible provider examples. Keep endpoint values
      # illustrative and provide user tokens per request via openai.provider_auth.
      # - id: "seulgivibe-claude"
      #   type: "seulgivibe_claude"
      #   category: "api"
      #   endpoint: "https://seulgivibe.example.invalid/anthropic/v1"
      #   models:
      #     - "claude-sonnet-4-5"
      #     - "claude-opus-4-8"
      #     - "claude-fable-5"
      #   capacity: 4
      # - id: "seulgivibe-openai"
      #   type: "seulgivibe_openai"
      #   category: "api"
      #   endpoint: "https://seulgivibe.example.invalid/openai/v1"
      #   models:
      #     - "gpt-5.1"
      #     - "gpt-5.5"
      #   capacity: 4
    # Legacy adapters configuration (compat override example):
    # adapters:
    #   cli:
    #     enabled: true
    #     profiles:
    #       claude-tui:
    #         command: "claude"
    #         args:
    #           - "--dangerously-skip-permissions"
    #         env:
    #           - "TERM=xterm-256color"
    #         persistent: true
    #         terminal: true
    #         response_idle_timeout_ms: 5000
    #         startup_idle_timeout_ms: 5000
    #         mode: "persistent-lazy"
    #       codex:
    #         command: "codex"
    #         args:
    #           - "app-server"
    #         mode: "codex-app-server"
    #       codex-exec:
    #         command: "codex"
    #         args:
    #           - "exec"
    #           - "--json"
    #         resume_args:
    #           - "exec"
    #           - "resume"
    #           - "--json"
    #         output_format: "codex-json"
    #         mode: "codex-exec"
    runtime:
      concurrency: 1

# === Provider-pool example (recommended) ===
# Full example node with provider pool (Provider-First).
# nodes[].providers[].id is the stable provider identity referenced by models[].providers keys.
# nodes:
#   - id: "node-gpu-01"
#     alias: "gpu-node"
#     providers:
#       - id: "vllm-gpu"
#         type: "vllm"
#         category: "api"
#         endpoint: "http://127.0.0.1:8000/v1"
#         models:
#           - "nvidia/Qwen3.6-35B-A3B-NVFP4"
#         health: "healthy"
#         capacity: 4
#         priority: 1
#         request_timeout_ms: 120000
#         lifecycle_capabilities: ["scale_up", "scale_down"]
#         # enabled: false  # exclude from dispatch pool (live-apply)
#       - id: "ollama-local"
#         type: "ollama"
#         category: "local_inference"
#         base_url: "http://127.0.0.1:11434"
#         models:
#           - "qwen3.6:35b"
#         health: "healthy"
#         capacity: 2
#         priority: 2
#
# === Legacy adapters (compat override example — discouraged for new deploys) ===
# Only use when explicit adapter instance override or compatibility before compilation is required.
#   - id: "node-vllm-01"
#     alias: "vllm-gpu-node"
#     token: "<node-token>"
#     adapters:
#       openai_compat_instances:
#         - name: "vllm-gpu"
#           enabled: true
#           provider: "vllm"
#           endpoint: "http://127.0.0.1:8000/v1"
#           capacity: 4
#           max_queue: 16
#           queue_timeout_ms: 30000
#     providers:
#       - id: "vllm-gpu-legacy"
#         type: "vllm"
#         category: "api"
#         adapter: "vllm-gpu"
#         models:
#           - "nvidia/Qwen3.6-35B-A3B-NVFP4"
