Binding Selection

June 4, 2026 · View on GitHub

Overview

Every capability in the system is fulfilled by a binding — a contract that connects a capability to a concrete service (OpenAI API, local Python function, MCP server, etc.). Most capabilities ship with two official bindings:

Binding typeProtocolWhen it shines
OpenAI (openapi)HTTPS → OpenAI Chat / EmbeddingsBest quality for LLM-dependent tasks (summarise, classify, translate, generate, etc.)
Python baseline (pythoncall)In-process function callZero external dependencies — works offline, fast, but produces basic results for tasks that truly need an LLM

The runtime automatically detects which credentials are available and selects the best binding at execution time. No manual configuration is needed.


Resolution Policy (v2)

When a capability is invoked the resolver walks through the following steps in order and stops at the first match:

1. Local override          .agent-skills/active_bindings.json
       ↓ (if not set)
2. Environment preferred   Auto-detect credentials → pick best binding
       ↓ (if no preference)
3. Official default        policies/official_default_selection.yaml
       ↓ (if not found)
4. Error                   BindingResolutionError

Step 2 — Environment-Preferred Selection

ConditionEffect
OPENAI_API_KEY is set and non-emptyPrefer official OpenAI bindings (openapi protocol, service id contains openai).
No recognised credential is presentPrefer official pythoncall bindings so no external HTTP call is attempted.

This means:

  • With an API key: you get full LLM-powered quality for every capability that has an OpenAI binding.
  • Without an API key: you get the local Python baseline automatically — no errors, no wasted HTTP calls.

After Step 2 picks the primary binding, the fallback chain in the executor still applies, but with a selection-source guard:

  • local_selection: runtime appends the official default as a terminal safety net.
  • environment_preferred and official_default: runtime follows explicit fallback_binding_id metadata only (no forced terminal default).

This avoids escalating to credential-dependent providers when no matching credential is available.


Runtime-managed required outputs

Capability outputs require an execution envelope. The runtime synthesizes the following required fields when missing:

  • status
  • rationale
  • trace_ref

Bindings should focus on domain outputs and are not required to map these envelope fields explicitly.


Quick Start

  1. Copy .env.example.env.
  2. Add your OpenAI API key:
    OPENAI_API_KEY=sk-…
    
  3. Run any skill — the runtime selects OpenAI bindings automatically.

If you don't set the key the system still works. Capabilities that depend heavily on an LLM (summarise, translate, classify, entity extraction, etc.) will use their Python baseline which produces valid but simplified results.


Overriding the Auto-Detection

If you need to force a specific binding regardless of environment, use the explicit override mechanism:

# .agent-skills/overrides.yaml
overrides:
  - capability_id: text.content.summarize
    binding_id: python_text_summarize   # force local even when key is set

Then activate:

python -m cli.main activate

Local overrides always take priority over environment detection and official defaults.


Python Baselines

Every capability ships with a Python baseline that works without external services. For capabilities that are inherently algorithmic (JSON parsing, text extraction, template rendering, keyword extraction, etc.) the Python baseline is full quality — no LLM needed.

For capabilities where an LLM is the natural fit (summarise, translate, generate, classify, detect language, embed, extract entities, transform, extract response) the Python baseline provides a functional but basic implementation:

CapabilityBaseline behaviour
text.content.summarizeTruncates to first N sentences
text.content.translateReturns text unchanged (identity)
text.content.generateEchoes the instruction
text.content.classifyReturns first candidate category
text.content.embedHash-based pseudo-embedding
perception.entity.extractRegex-based extraction (type = OTHER)
text.language.detectDefaults to en
text.content.transformWraps text with the goal directive
text.response.extractReturns first sentence of context

model.* domain baselines

The model.* domain has two kinds of capabilities:

Deterministic (always pythoncall — no LLM needed):

CapabilityBaseline behaviour
model.output.sanitizeRegex-based deep PII/harmful/leakage removal
model.prompt.template${var} substitution with unresolved tracking

LLM-dependent (OpenAI preferred when key is set):

CapabilityBaseline behaviour
model.output.generateMock / OpenAI only (no pythoncall baseline)
evaluation.response.validateStructural check: empty fields, non-dict detection
model.embedding.generateHash-based pseudo-embedding (128-dim default)
model.output.classifyKeyword frequency + field-name heuristics
evaluation.output.scoreWord overlap, sentence length, length ratio proxies
evaluation.risk.scorePattern matching for toxicity, bias, injection markers

These baselines ensure the system never crashes — but for production use with LLM-dependent capabilities, setting OPENAI_API_KEY is strongly recommended.

agent.* domain baselines

Three agent.* capabilities use OpenAI for production quality but fall back to deterministic baselines. Two are always pythoncall-only.

CapabilityBaseline behaviour
agent.input.routeKeyword match against agent names, first-agent fallback
agent.option.generate4 template archetypes (conservative/balanced/aggressive/alternative)
agent.plan.generate3-step stub plan (analyse → execute → verify)
agent.plan.createscaffold_service — LLM if available, template otherwise
agent.task.delegateAlways accepts; returns deterministic delegation_id

Adding Support for Other Providers

The environment detection mechanism is extensible. The mapping of env vars to service preferences lives in runtime/binding_resolver.py:

_ENV_SERVICE_PREFERENCES = [
    ("OPENAI_API_KEY", "openai"),
    # ("ANTHROPIC_API_KEY", "anthropic"),  # future
]

To add a new provider: create service descriptors, bindings, and append an entry to this list. The resolver will prefer that provider's bindings when the corresponding key is detected.