get2knowio / get2knowio/airframe
Capability-matching surface: provider selection (intersection) + routing (union) over a requirements/profile model
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
A design discussion crystallized two first-class consumer use cases that Airframe doesn't yet serve directly, and a single primitive that underlies both: **capability-set algebra between app-declared requirements and provider-declared capability profiles.** This issue captures the reasoning and proposes an additive `airframe.capabilities` surface.
Today Airframe abstracts the *mechanics* of talking to each provider and exposes honest per-capability negotiation (`supports(Feature)`), but it doesn't help a consumer answer the actual product question: **"given the capabilities my app needs, which providers can serve them — and how do I pick/route?"**
## The two use cases (one primitive, two quantifiers)
Both are capability matching between *what the app's features require* (`R(f)`) and *what each provider supplies* (`caps(P)`). What differs is who chooses and how many providers are in play.
**1. Routing / union — e.g. the GitHub Action.** The developer holds all credentials and picks per task. For task `t`, find *some* provider where `caps(P) ⊇ R(t)`. Different tasks → different providers. Quantifier: **∃ a provider per task.** Exploits the union.
**2. Embedded BYO-subscription / intersection — e.g. Musaic (local media app; each user brings their own Claude / Copilot / etc. subscription).** One user-chosen provider must power *every* AI feature. The offerable picker set is `⋂_f { P : caps(P) ⊇ R(f) }` over all the app's features — providers whose caps cover the union of all feature requirements. Quantifier: **∃ a single provider covering all tasks.** This is a genuine intersection constraint, and likely the more common embedding pattern (let users bring AI they already pay for; no per-user API cost to the app developer).
These are not competing philosophies — they're two policies over the same matching engine. Routing and picker-filtering call the same function over `supports()`.
## Refinements that make it real
- **Hard vs soft requirements.** All-or-nothing intersection is too blunt. Hard requirements filter the *picker*; soft/per-feature requirements admit the provider but **gate individual app features in the UI** (graceful degradation). So requirement declaration should be **per-feature**, not one app-wide blob.
- **Emulation raises the floor — and it matters most in the intersection case.** A single-provider (BYO) user can't route around a missing capability, so faithful Tier-A emulation (e.g. structured output via prompt+validate) directly **widens the funnel of compatible subscriptions** — more of an app's users qualify with whatever they have. (In the routing case emulation is less critical; you can route to a native provider.)
- **Emulation must be observable.** `supports()` stays native-only (honesty invariant). Emulation appears only via an explicit query, and the picker can choose native-only or native+emulated (tagging fallbacks). A call that actually emulated should flag it on the result.
- **Static vs dynamic.** `supports()` is documented cheap/pure/static, so the picker can be rendered **pre-auth** (before the user plugs in credentials). Per-model capability (vision on some models; the user's subscription may expose only some models) is a **post-auth** refinement via `list_models()` / `ModelInfo`.
## Proposed surface (additive — `airframe.capabilities`)
Sits on top of existing `supports()`, `discovery.list_providers()`/`runtime_for()`, the `Feature` enum, and `ModelInfo`. Changes none of them.
```python
class CapabilityLevel(StrEnum):
NATIVE = "native"
EMULATED = "emulated" # faithful Tier-A polyfill only
UNSUPPORTED = "unsupported"
EMULABLE: dict[Feature, EmulationStrategy] = {
Feature.STRUCTURED_OUTPUT_JSON_SCHEMA: PromptAndValidateJSON(),
# Feature.COUNT_TOKENS: ApproxTokenizer(), # later, labelled "approximate"
}
def capability_level(runtime, feature, *, model=None, allow_emulated=True) -> CapabilityLevel: ...
@dataclass(frozen=True, slots=True)
class FeatureRequirement:
name: str # app-facing id, e.g. "auto_tag_art"
requires: frozenset[Feature]
optional: bool = False # soft → gate UI, don't exclude provider
@dataclass(frozen=True, slots=True)
class ProviderCandidate:
provider_id: str
levels: dict[Feature, CapabilityLevel]
score: float
@property
def all_native(self) -> bool: ...
@property
def uses_emulation(self) -> bool: ...
def eligible_providers(required, *, allow_emulated=False, installed_only=True, rank=None) -> list[ProviderCandidate]: ...
def feature_availability(runtime, requirements, *, allow_emulated=True, model=None) -> dict[str, CapabilityLevel]: ...
```
Usage:
```python
# Musaic picker (intersection) — pre-auth, static
hard = frozenset().union(*(f.requires for f in MUSAIC if not f.optional))
for cand in eligible_providers(hard, allow_emulated=True):
show_in_picker(cand.provider_id, emulated=cand.uses_emulation)
# After the user picks + authenticates — per-feature UI gating
status = feature_availability(runtime_for(chosen)(...creds...), MUSAIC)
# {"rename": NATIVE, "auto_tag_art": UNSUPPORTED} -> gray out auto_tag
# GitHub Action (union/routing) — same function, native-only
provider = eligible_providers(task_requires, allow_emulated=False)[0].provider_id
```
## Open design questions
1. **Static vs constructed `supports()`** — call on a credential-less probe instance, or lift caps to a classmethod/ClassVar table for pre-auth eligibility?
2. **Per-model refinement** — two-phase (static provider filter → post-auth model-level gating via `ModelInfo`).
3. **`EMULABLE` registry + faithfulness bar** — admission test for Tier-A; how a call signals it actually emulated (observable on `RuntimeResult`).
4. **Ranking** — default `all_native > fewer emulations > cost/latency`? Keep the scorer injectable.
5. **Surface placement** — new `airframe.capabilities` module vs extending `discovery`.
## Relationship to the analogy
JDBC was the right grounding for the mechanics-abstraction phase, but it assumes a *fat* portable core; for agent SDKs the honest intersection is thin (≈ "send a prompt, get text"). Better mental models for this phase: GPU **feature levels** (Vulkan/D3D — query caps, engine adapts), **LLVM lowering** (faithful emulation where correctness is preserved; feature flags + escape hatches where not), and **POSIX/`pathlib`** (in-library smoothing only where faithful). The end-state value is three layers: (1) a consistent programming model, (2) faithful floor-raising for Tier-A capabilities, (3) capability-aware selection/routing — with honest escape hatches for the rest.
## Tier-A emulation (the polyfill discipline this depends on)
Separable but related: introduce faithful, **opt-in, observable** emulation for capabilities latent in every competent model, starting with **structured output** (prompt + validate + retry). Discipline: emulate only where correctness is preserved and only quality/cost varies predictably; never flip native `supports()` to True. Consider splitting into its own implementation issue once this design lands.
## Related
- Builds on the existing `supports()` capability-negotiation surface, `airframe.discovery`, the `Feature` enum, `ModelInfo`.
- Context: the `airframe` CLI (`feat/cli-entrypoint`) and the GitHub Action scaffold (`feat/github-action`) are the first concrete consumers of the union/routing pattern.
- Adjacent: #56 (CLI `--cwd` + read-only tool mode).
- Phasing: fits the project's "lock names early, light up capabilities deliberately" approach (`dev-docs/feature-roadmap.md`).
Contributor guide
Research direction
Start by reading the existing supports() surface, airframe.discovery, the Feature enum, ModelInfo, and dev-docs/feature-roadmap.md. Then resolve the proposed airframe.capabilities placement, pre-auth versus per-model capability checks, emulation signaling, and ranking behavior. Done means an agreed additive design for provider eligibility and per-feature availability, with implementation scope clear.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100