-
Notifications
You must be signed in to change notification settings - Fork 3.5k
feat(inference): TinyAgents model-interface inversion — Motion A complete + managed-backend crate cutover (Motion B) #4769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
senamakel
merged 40 commits into
tinyhumansai:main
from
senamakel:feat/inference-crate-provider-cutover
Jul 11, 2026
Merged
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
6824129
fix(ci): unblock release workflow actions (#4610)
senamakel 03a05be
chore(staging): v0.58.13 [skip ci]
github-actions[bot] 81df38f
chore(staging): v0.58.14 [skip ci]
github-actions[bot] c2c2036
docs(tinyagents): add P1-8 model-layer inversion drift row
senamakel b8eda9d
refactor(inference): migrate one-shot callers to crate ChatModel
senamakel 8090f13
docs(tinyagents): record P1-8/P1-9 inversion findings
senamakel eea3a7e
docs(tinyagents): record P1-9 harness-inversion blocker
senamakel 9ff3cab
docs(tinyagents): Phase 3 router-registry design + premise correction
senamakel 9701b86
feat(docs): add referral system documentation
senamakel b1e2f3c
Merge remote-tracking branch 'upstream/main' into feat/inference-mode…
senamakel 30c7dfd
refactor(agent): channel/bus turn path holds TurnModelSource, not Pro…
senamakel 0f487f7
docs(tinyagents): record Motion A progress — channel/bus path migrated
senamakel 8db8887
refactor(agent): subagent turn path holds TurnModelSource, not Provider
senamakel 11166cd
docs(tinyagents): record subagent-runner path migrated (Motion A)
senamakel 6106ced
refactor(agent): extract_from_result tool holds TurnModelSource, not …
senamakel 9112330
refactor(agent): Agent session path holds TurnModelSource, not Provider
senamakel 0d4a219
docs(tinyagents): Motion A structurally complete (all turn paths migr…
senamakel 4116f22
style(agent): cargo fmt the TurnModelSource migration
senamakel d313a2c
chore: drop unrelated e2e spec churn from upstream-sync merge
senamakel e8afef1
chore(vendor): bump tinyagents to main w/ OpenAiModel auth styles (ti…
senamakel 0c85113
chore(vendor): pin tinyagents to openai-host-compat integration (auth…
senamakel c4ea1cc
feat(inference): scaffold crate-native OpenAiModel client builder (Mo…
senamakel 10e719c
chore(vendor): re-pin tinyagents to released main (auth+temp+merge me…
senamakel 8d2321d
feat(inference): factory-level crate OpenAiModel builder (Motion B cu…
senamakel b6e95c1
feat(inference): managed OpenHuman backend as host ChatModel (Motion B)
senamakel 7e98c1b
feat(inference): route managed create_chat_model to crate-native back…
senamakel 8ad2a15
feat(inference): crate-native local-runtime ChatModel builder + tinya…
senamakel 034fac3
Merge remote-tracking branch 'upstream/main' into feat/inference-crat…
senamakel 6ce3a6f
test(inference): managed-vs-local create_chat_model routing (Motion B)
senamakel 85f7c4e
fix(agent): repair Motion A test-debt + bump tinyagents to v1.8.0 (#4…
senamakel 5109b2a
fix(test): repair async device-tool calls in orchestration e2e (unblo…
senamakel 82ccb3f
fix(tests,cron): repair stale behavior-level tests + surface provider…
senamakel 9e3f748
fix: resolve remaining coverage-job failures (3 merge-debt tests + cr…
senamakel b1594d6
test(inference): serialize create_chat_provider tests with the global…
senamakel 48815f4
docs(tinyagents): record PR #4769 CI-green milestone
senamakel e226aba
feat(inference): cut local runtimes to crate-native ChatModel (Motion B)
senamakel dfe9403
docs(tinyagents): local runtimes cut over; BYOK cloud deferred to Pha…
senamakel 9a0aa1b
Merge remote-tracking branch 'upstream/main' into feat/inference-crat…
senamakel 36c1dca
fix(cron): defer loopback-offline to upstream's "stays retryable" beh…
senamakel aa06b44
test(raw_coverage): restore turn_model_source in tests taken from ups…
senamakel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| # Phase 3 — RouterProvider → crate ModelRegistry: design & ground truth | ||
|
|
||
| **Status:** design (starting Phase 3). Grounded in a full read of both the crate | ||
| (`vendor/tinyagents/src/harness/model`, `harness/agent_loop`, `harness/retry`, | ||
| `registry/`) and the host seam (`src/openhuman/tinyagents/{mod,routes,model}.rs`, | ||
| `inference/provider/{router,factory}.rs`). | ||
| **Relates to:** `docs/tinyagents-inference-migration-plan.md` (Phase 3), | ||
| `docs/tinyagents-drift-ledger.md` (P1-9), issue #4249. | ||
|
|
||
| --- | ||
|
|
||
| ## 1. The premise correction (important) | ||
|
|
||
| The plan framed Phase 3 as "implement RouterProvider → crate ModelRegistry in | ||
| `vendor/tinyagents`." **Investigation shows the registry migration is already | ||
| done at the seam, and the crate already does everything the host router needs — | ||
| there is no hard upstream gap.** Specifically, `assemble_turn_harness` | ||
| (`tinyagents/mod.rs:1240-1353`) already: | ||
|
|
||
| - Registers the primary and every workload-tier route into the crate | ||
| `ModelRegistry` (`harness.register_model(name, model)`; primary via | ||
| `set_default_model`). | ||
| - Wires cross-route fallback as a **crate** `RunPolicy.fallback = | ||
| routes::route_fallback_policy(model)` — traversed natively by | ||
| `agent_loop::invoke_model_resolving`. | ||
| - Gates vision via the crate: `RequiredCapabilitiesMiddleware` stamps | ||
| `image_in` onto the `ModelRequest`, and the crate's `resolve_request` filters | ||
| named candidates by `ModelProfile::satisfies`. | ||
| - Emits the `FallbackSelected` parity event via `FallbackObserverMiddleware`. | ||
|
|
||
| The two things the crate *lacks* (per the crate audit) are **not needed** by the | ||
| host: | ||
|
|
||
| - **Capability-based *selection*** (scan the registry for a capable model): the | ||
| host never does this — the **caller picks the tier** upstream | ||
| (`subagent_runner/ops/graph.rs` sets `model = vision-v1` for image turns); the | ||
| middleware only *validates/enforces*, it doesn't select. | ||
| - **Capability-aware *fallback***: the host's fallback chains are hand-built to | ||
| be capability-safe (`routes::same_family_fallbacks`: `vision-v1 → []`; the | ||
| same-family text alternates are all text-capable), so the crate's | ||
| non-capability-filtered `next_after` is benign here. | ||
|
|
||
| **Conclusion:** RouterProvider is *already* projected onto the crate registry. | ||
| What remains is host-side, and needs no crate release. | ||
|
|
||
| ## 2. What actually survives (the real remaining work) | ||
|
|
||
| `RouterProvider` (`inference/provider/router.rs`) survives only as the **per-call | ||
| BYOK alias resolver *inside* the wrapped `Provider`** — at dispatch it maps a | ||
| tier alias (`reasoning-v1`, …) → concrete `(provider, model)` (issue #2079: raw | ||
| aliases 400 on OpenAI/DeepSeek). The registered tier models are still | ||
| `ProviderModel`s that wrap this host `Provider`. So the harness holds a | ||
| `Provider` for exactly one reason: **`build_turn_models` builds the per-turn | ||
| primary + routes + summarizer `ProviderModel`s from one `Provider` handle** | ||
| (`tinyagents/mod.rs:1139-1175`, `routes::build_route_models`). | ||
|
|
||
| Two independent motions remain, in priority order: | ||
|
|
||
| ### Motion A — Harness holds crate `ChatModel`s (this Phase; host-only) | ||
| Move `build_turn_models` construction from the harness turn path | ||
| (`agent/harness/graph.rs::run_channel_turn_via_graph`, session turn path) to the | ||
| **producer/factory boundary**, so `agent/` holds crate model types, not | ||
| `Provider`. The harness turn path today reads four `Provider` methods before | ||
| building — all are already available without the trait: | ||
|
|
||
| | harness reads today | crate-native source | | ||
| | --- | --- | | ||
| | `provider.supports_native_tools()` | `TurnModels.primary.profile().tool_calling` | | ||
| | `provider.supports_vision()` | `…profile().modalities.image_in` | | ||
| | `provider.effective_context_window(model).await` | resolved at build time → `…profile().max_input_tokens` | | ||
| | `provider.telemetry_provider_id()` | new `TurnModels.provider_id: String` | | ||
|
|
||
| So `TurnModels` becomes the unit the harness holds. Design points: | ||
|
|
||
| 1. **Extend `TurnModels`** with `provider_id: String` and small accessors | ||
| (`native_tools()`, `supports_vision()`, `context_window()`) reading | ||
| `primary.profile()`. Removes every raw-`Provider` read in the harness graph. | ||
| 2. **New factory entry** `inference::provider::factory::create_turn_models( | ||
| role_or_model, config, temperature) -> anyhow::Result<TurnModels>`: builds the | ||
| `Provider` internally (existing `create_chat_provider` path), resolves the | ||
| async `effective_context_window`, computes `telemetry_provider_id`, and calls | ||
| the seam `build_turn_models`. All `Provider` naming stays in | ||
| `inference/provider/` + the seam. | ||
| 3. **Lifecycle:** `build_turn_models` is per-`(provider, model)`; the harness | ||
| builds a fresh `TurnModels` per turn today. Keep that — the producer builds | ||
| `TurnModels` at turn-request assembly (channels processor, session turn entry, | ||
| subagent runner) instead of the harness graph doing it. `error_slot` stays | ||
| per-turn (correct — it recovers *this* turn's provider error). | ||
| 4. **Type swap:** `AgentTurnRequest.provider: Arc<dyn Provider>` → carries the | ||
| built `TurnModels` (+ the `model`/`provider_name` it already has); | ||
| `Agent`/`AgentBuilder.provider` likewise. `run_channel_turn_via_graph` / | ||
| session `turn/graph.rs` receive `TurnModels`, read caps via the accessors, and | ||
| pass it straight to `run_turn_via_tinyagents_shared` (which already takes | ||
| `TurnModels`). `IntelligentRoutingProvider` stays a `Provider` impl | ||
| (provider-stack member); the factory wraps it before `build_turn_models`. | ||
|
|
||
| Exit: no `agent/` file names the `Provider` trait; `ProviderModel` built only in | ||
| the seam (`model.rs` / `build_turn_models`), reached only via the factory entry. | ||
| Zero behavior change (same `ProviderModel`s, same registry/fallback wiring). | ||
|
|
||
| ### Motion B — Registered models become crate-native (later; the big LOC win) | ||
| Replace `ProviderModel` wrappers with crate `providers::openai` clients built | ||
| from config (BYOK slugs, Ollama/LM Studio base URLs), registering per-tier | ||
| clients directly so `RouterProvider`'s per-call alias resolution disappears | ||
| (each tier is its own registered client). This is inference-plan **Phase 2** | ||
| (client swap, deletes `compatible*.rs`) + the remainder of Phase 3, and keeps the | ||
| bespoke providers (managed backend, claude-code, codex) as host `ChatModel` | ||
| impls (Phase 4). Out of scope for Motion A; unblocked by it. | ||
|
|
||
| ## 3. Optional crate nicety (not required) | ||
| If we later want capability-*aware* fallback (so a hand-built chain isn't the | ||
| only safety net), the one-line crate change is to re-apply `model_eligible` | ||
| to `FallbackPolicy::next_after` targets in `invoke_model_resolving` | ||
| (`vendor/tinyagents/src/harness/agent_loop/model_call.rs:186-204`). File as a | ||
| separate small upstream PR only if Motion B introduces capability-divergent | ||
| fallback chains. Not needed for Motion A. | ||
|
|
||
| ## 4. First implementation slice (Motion A) | ||
| 1. `TurnModels`: add `provider_id` + `native_tools()/supports_vision()/context_window()` accessors (seam-internal; behavior-neutral). | ||
| 2. `create_turn_models(...)` factory entry (wraps `create_chat_provider` + | ||
| async context-window resolve + `build_turn_models`). | ||
| 3. Cut `run_channel_turn_via_graph` to take `TurnModels` and read caps via | ||
| accessors (delete the 4 raw-provider reads); channel producer | ||
| (`channels/runtime/dispatch/processor.rs`) builds `TurnModels` via the factory | ||
| and puts it on `AgentTurnRequest`. | ||
| 4. Repeat for the session turn path + subagent runner; swap `Agent.provider`. | ||
| 5. Update tests to build `TurnModels`/crate `MockModel` instead of hand-rolled | ||
| `Provider` impls. | ||
| 6. Verify: both Cargo worlds green; `json_rpc_e2e`; streaming/cost/tool-timeline | ||
| parity on a mock-backend turn (the #4460 / $0-turn / tool-timeline hazards). | ||
|
|
||
| ## 5. Verification & parity locks (unchanged from the plan) | ||
| Provider-string grammar, `inference.*` RPC, tier alias set, fallback ordering | ||
| (single same-family alternate; vision primary-only), the once-per-logical-call | ||
| FIFO usage push (charged-USD-over-estimate precedence, graceful degradation), and | ||
| the `FallbackSelected` event must all be preserved. These are already crate-wired | ||
| — Motion A only moves *where the models are built*, not how they route. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the
chatrole resolves to the managed OpenHuman backend,create_chat_modelnow returnsOpenHumanBackendModel, which forwards thisModelRequestdirectly to the crateOpenAiModelinstead of going through the host compatible provider'sMessageContent::from_chat_textmarker parser. In that path thisMessage::user(user)is only a text block containing[IMAGE:data:...], so the screenshot is not sent as an image andlocate_via_visioncannot actually inspect the UI. Build a crate message with an actual image content block here, or keep managed vision calls on the host provider path that expands[IMAGE:]markers.Useful? React with 👍 / 👎.