Add the self_hosted decision provider (contract 2.9) - #69
UmutAlihan wants to merge 7 commits into
Conversation
A self-hosted open decision model — an operator-run Jev-compatible decisions endpoint — can now drive the Jev loops. JevProvider::SelfHosted (wire self_hosted, alias selfhosted) has no approved route and no default model: JevConfig::endpoint_url and JevConfig::model are both required, the declared endpoint is trusted because the operator named it, and Client::new remains the syntactic gate (absolute HTTP(S), no embedded credentials, no query or fragment, plain HTTP only on a literal loopback). The client is built exactly like first-party TypeSafe Jev at the declared endpoint, so a self-hosted model satisfies the first-party response check by echoing the requested model id.
Tiny Sweeper reviewAdds the self_hosted decision provider (contract 2.9), allowing operators to declare their own Jev-compatible endpoint. Contract version bumped to 2.9. Includes minor code refactors and documentation updates. All earlier concerns about the provider loop and compatibility test have been resolved; no active findings remain. Many files could not be reviewed due to retrieval failures, but the reviewed lanes confirm the change is sound. State: Incomplete Review snapshot
Completeness: Incomplete What changedImplements the self_hosted decision provider with required endpoint_url and model. Adds SelfHosted variant to JevProvider enum, updates runtime configuration and endpoint validation, bumps contract version, adds tests, and updates documentation. Includes minor refactors in accessibility modules and a test assertion improvement. Features
Tests
FindingsNo active actionable findings. Resolved this pass
Could not review: MODULE.md, crates/tinycomputer-accessibility/src/focus.rs, crates/tinycomputer-accessibility/src/paste.rs, crates/tinycomputer-accessibility/src/permissions.rs, crates/tinycomputer-bus/src/agentic/agentic_tests.rs, crates/tinycomputer-bus/src/agentic/types/config.rs, crates/tinycomputer-bus/src/version/mod.rs, crates/tinycomputer-bus/src/version/version_tests.rs, crates/tinycomputer-cursor/src/screen/screen_tests.rs, crates/tinycomputer-engine/src/agentic/agentic_tests/resolve_tests.rs, crates/tinycomputer-engine/src/agentic/runtime.rs, crates/tinycomputer/src/tinybus_module/tinybus_module_tests/config_tests.rs, crates/tinycomputer/tests/public_api_tests.rs, docs/crates/tinycomputer-bus/goal-loop.md, docs/crates/tinycomputer-engine/jev-runtime.md, docs/crates/tinycomputer/configuration.md, docs/technical/architecture.md, docs/technical/specs/desktop-module-contract.md Before merge
How this fits togetherflowchart LR
n0["focused_text_context_verbose<br/>changed"]:::changed
n1["restore_focus_to_app<br/>changed"]:::changed
n2["JevConfig<br/>changed"]:::changed
n3["JevProvider<br/>changed"]:::changed
n4["configure"]:::impacted
n5["command_output_with_timeout"]:::impacted
n6["Err"]:::impacted
n7["JevConfiguration"]:::impacted
n8["focused_text_via_osascript"]:::impacted
n0 -->|calls| n6
n0 -->|calls| n8
n1 -->|calls| n6
n2 -->|uses| n3
n4 -->|uses| n2
n4 -->|calls| n6
n4 -->|uses| n7
n5 -->|calls| n6
n5 -->|tests| n6
n7 -->|uses| n3
n8 -->|calls| n5
n8 -->|calls| n6
classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Agent review detailscritique
security
tests
commits
description
e2e
Evidence and run details
|
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID:
Comment |
There was a problem hiding this comment.
tinysweeper found nothing blocking, but could not review everything, so this is not an approval: MODULE.md, crates/tinycomputer-bus/src/agentic/agentic_tests.rs, crates/tinycomputer-bus/src/agentic/types/config.rs, crates/tinycomputer-bus/src/version/mod.rs, crates/tinycomputer-bus/src/version/version_tests.rs, crates/tinycomputer-engine/src/agentic/agentic_tests/resolve_tests.rs, crates/tinycomputer-engine/src/agentic/runtime.rs, crates/tinycomputer/src/tinybus_module/tinybus_module_tests/config_tests.rs and 8 more.
$0.0000 · 0 in / 0 out · 969 embedded · ladder/vectors
The stable toolchain roll (1.96.1 -> 1.98.1) turned four lints in tinycomputer-accessibility into CI failures for every open PR, including this one: - focus.rs: push_str with a format! allocation -> inline format args - paste.rs: positional format args -> inline format args - permissions.rs: add #[must_use] to the macOS/Windows detect_microphone_permission, and a single-pattern match -> if let Behavior is unchanged; cargo clippy --all-targets --all-features -D warnings is green across the workspace under both 1.96.1 and 1.98.1, and tinycomputer-accessibility tests pass.
|
Pushed Root cause: not this PR's diff — the stable toolchain roll (1.96.1 → 1.98.1) turned four pre-existing lints in The fix commit is mechanical and behavior-preserving:
Verified locally with CI's exact toolchain and flags ( |
There was a problem hiding this comment.
tinysweeper found nothing blocking, but could not review everything, so this is not an approval: crates/tinycomputer-accessibility/src/focus.rs, crates/tinycomputer-accessibility/src/paste.rs, crates/tinycomputer-accessibility/src/permissions.rs, tinysweeper/description, tinysweeper/tests.
$0.0000 · 0 in / 0 out · 1,109 embedded · ladder/vectors
Stable 1.99.0 (2026-09-28) adds clippy::assert-is-empty, denying the assert!(x.is_empty()) idiom across the workspace's test code — every open PR fails CI on it. Convert each flagged site to assert_eq! / assert_ne! so the compared value prints on failure: - Vec fields: assert_eq!(x, []) (with the type annotation where inference needs it), Vec::new() for non-inferable element types - Strings: assert_eq!(x, "") - MutexGuard<Vec>: deref the guard - the TERMINAL_NAMES const sanity check: assert_ne!(len, 0) - HashMap sites are not matched by the lint and are unchanged Applied with cargo clippy --fix where the suggestion was machine-applicable, by hand where it was not. Behavior-preserving; cargo clippy --all-targets --all-features -D warnings is green workspace-wide under 1.96.1, 1.98.1, and 1.99.0, and the full test suite passes.
|
Follow-up: Pushed Verified: |
There was a problem hiding this comment.
tinysweeper found nothing blocking, but could not review everything, so this is not an approval: crates/tinycomputer-accessibility/src/terminal_tests.rs, crates/tinycomputer-browser/src/outputs/outputs_tests.rs, crates/tinycomputer-browser/src/sessions/sessions_tests/artifacts_tests.rs, crates/tinycomputer-browser/src/sessions/sessions_tests/lifecycle_tests.rs, crates/tinycomputer-browser/src/surface/surface_tests/cursor_tests.rs, crates/tinycomputer-browser/src/surface/surface_tests/tree_tests.rs, crates/tinycomputer-bus/src/agent/agent_tests.rs, crates/tinycomputer-bus/src/agentic/agentic_tests.rs and 32 more.
$0.0000 · 0 in / 0 out · 1,192 embedded · ladder/vectors
globe_tests.rs's non_macos_listener_entry_points_report_unsupported is #[cfg(not(target_os = "macos"))], so a macOS clippy run never compiles it and the previous commit missed its assert!(polled.events.is_empty()). Convert it like the rest. This was the only remaining single-form is_empty assert inside a non-macOS-gated block; verified by sweeping every cfg(not(macos))/linux/windows-gated file for the pattern.
|
Hi @senamakel — gentle nudge for a review when you have a moment 🙂 All CI checks are green on the latest head ( Summary of the change: adds a No rush at all — just let me know if anything should change. Thanks! |
senamakel
left a comment
There was a problem hiding this comment.
Thanks for this. The self_hosted design is sound: the endpoint and model are both required, Client::new stays the syntactic gate, the api key is still redacted in Debug, and the provider is only reachable through private module config, so an agent cannot point the key at an arbitrary host. I am not merging yet because of one correctness regression.
Blocking: crates/tinycomputer-accessibility/src/focus.rs loses its interpolation.
// before
helper_error.push_str(&format!("; osascript fallback failed: {fallback_err}"));
// after
helper_error.push_str("; osascript fallback failed: {fallback_err}");The format! was dropped but the argument stayed inside a plain string literal. The error text now contains a literal {fallback_err} and the real error is lost. This is a macOS-only path, so Linux CI cannot catch it. Please restore the formatting, for example use std::fmt::Write; let _ = write!(helper_error, "; osascript fallback failed: {fallback_err}");.
Heads-up: overlap with main. Main CI has been red since the Rust 1.99 toolchain landed (clippy::assert_is_empty under -D warnings). Several of the test-file edits here are the same fix. I am fixing main's lint failures in a separate sync PR, so you will need to merge main into this branch afterwards. Conflicts should be limited to those test-only edits, and you can take main's version of them.
Minor, non-blocking: with self_hosted the Describe capabilities echo endpoint_url. Client::new rejects embedded credentials, so it cannot carry a secret, but docs could say operators should not put tokens in the path or query.
Resolves the expected overlap with tinyhumansai#70: both branches fixed the same clippy::assert_is_empty sites with slightly different spellings; main's versions are taken, as agreed in review.
… endpoint URLs
- focus.rs (blocking review finding): the 1.98 lint fix dropped format!
but left {fallback_err} inside a plain string literal, so the macOS
osascript-fallback error text contained a literal placeholder and the
real error was lost. Restore interpolation with fmt::Write::write!.
- configuration.md, jev-runtime.md (non-blocking review suggestion):
state that self_hosted authentication belongs in api_key alone — keep
tokens out of the endpoint_url path or query, since Describe echoes
the configured endpoint_url in its capabilities.
|
Both review points are addressed on the new head ( Blocking — use std::fmt::Write as _;
let _ = write!(helper_error, "; osascript fallback failed: {fallback_err}");Since Linux CI cannot compile this path, I verified it compiles natively on a macOS host (stable 1.99 Docs — no tokens in Merged main. Your sync PR #70 landed, so I merged it into this branch as suggested. All five conflicts were the same benign overlap (both branches fixed the same Local verification on the new head, matching CI: One pre-existing note, unrelated to this PR: |
There was a problem hiding this comment.
Requesting changes: 1 lane(s) blocking, worst finding is high.
Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.
$0.0062 · 61,806 in / 14,787 out · 28,160 cached (46%) · deepseek/deepseek-v4-flash
tests: $0.0036 · 33,751 in / 9,681 out · 15,872 cached (47%) · deepseek/deepseek-v4-flash
description: $0.0016 · 15,593 in / 1,027 out · 0 cached (0%) · deepseek/deepseek-v4-flash
| JevProvider::TinyHumansOpenRouter, | ||
| JevProvider::OpenJev, | ||
| JevProvider::Sage, | ||
| JevProvider::SelfHosted, |
There was a problem hiding this comment.
Exclude SelfHosted from the provider loop that expects a hosted route
Adding JevProvider::SelfHosted to the providers list in an_endpoint_cannot_be_reused_across_providers causes the test to panic because approved_endpoint(SelfHosted) returns None and the code immediately calls .expect("a hosted route") on it. This will cause every test run to fail. Either skip SelfHosted in this loop (since it has no approved endpoint) or handle the None case before unwrapping.
[RULE] failing-test ·
|
@tinysweeper please re-review the current head (63b349c). The previous tests lane ran with code retrieval unavailable (ladder embeddings 502, rate limited) and saw the diff alone; its one finding appears to be a false positive — |
There was a problem hiding this comment.
tinysweeper found nothing blocking, but could not review everything, so this is not an approval: MODULE.md, crates/tinycomputer-accessibility/src/focus.rs, crates/tinycomputer-accessibility/src/paste.rs, crates/tinycomputer-accessibility/src/permissions.rs, crates/tinycomputer-bus/src/agentic/agentic_tests.rs, crates/tinycomputer-bus/src/agentic/types/config.rs, crates/tinycomputer-bus/src/version/mod.rs, crates/tinycomputer-bus/src/version/version_tests.rs and 10 more.
$0.0050 · 51,982 in / 6,811 out · 12,544 cached (24%) · deepseek/deepseek-v4-flash
tests: $0.0017 · 12,963 in / 3,186 out · 0 cached (0%) · deepseek/deepseek-v4-flash
description: $0.0006 · 12,609 in / 2,149 out · 12,544 cached (99%) · deepseek/deepseek-v4-flash
|
@senamakel All three points from your review are addressed on the new head (
CI is green on the new head (Rust, Docs, MSRV, Supply chain, tinysweeper/review). The one |
Takes main's version of detect_microphone_permission: the cpal 0.18 bump (tinyhumansai#72) changed the device-name API (description().name()), which overlaps the branch's 1.98 if-let lint refactor.
There was a problem hiding this comment.
Requesting changes: 1 lane(s) blocking, worst finding is high.
Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.
$0.0078 · 71,761 in / 8,916 out · 3,072 cached (4%) · deepseek/deepseek-v4-flash
tests: $0.0020 · 16,259 in / 3,728 out · 1,280 cached (8%) · deepseek/deepseek-v4-flash
description: $0.0019 · 15,905 in / 2,926 out · 1,280 cached (8%) · deepseek/deepseek-v4-flash
| assert!(is_compatible((2, 97))); | ||
| // 2.9 accepts the `self_hosted` decision provider: a 2.9 host may | ||
| // configure it, which a 2.8 module refuses. | ||
| assert!(!is_compatible((2, 8))); |
There was a problem hiding this comment.
Reconcile the compatibility test with the unchanged implementation
is_compatible implements module.0 == CONTRACT_VERSION.0 && module.1 <= CONTRACT_VERSION.1. With CONTRACT_VERSION now (2,9), it returns true for a module reporting (2,8) because 8 <= 9. The test a_newer_minor_on_the_module_side_binds (and the doc example in mod.rs) now assert !is_compatible((2,8)), which will fail. Either change is_compatible to enforce the new policy (e.g., strict equality) or correct the test expectation to true and add a separate check that prevents SelfHosted config from being sent to a 2.8 module.
[RULE] failing-test ·
|
@tinysweeper please re-review head f748115. The tests lane again ran with code retrieval unavailable (ladder embeddings 502) and saw the diff alone; its finding inverts the comparison — |
|
@senamakel Heads-up while waiting on re-review: main moved again (cpal 0.18 bump #72 + v0.9.1), which re-conflicted this branch. I merged it and resolved the One thing you may want for a sync PR: current
(Your CI run for #72 may have used an older clippy; on 1.99.0 these fire.) The new head is |
There was a problem hiding this comment.
tinysweeper found nothing blocking, but could not review everything, so this is not an approval: MODULE.md, crates/tinycomputer-accessibility/src/focus.rs, crates/tinycomputer-accessibility/src/paste.rs, crates/tinycomputer-accessibility/src/permissions.rs, crates/tinycomputer-bus/src/agentic/agentic_tests.rs, crates/tinycomputer-bus/src/agentic/types/config.rs, crates/tinycomputer-bus/src/version/mod.rs, crates/tinycomputer-bus/src/version/version_tests.rs and 10 more.
$0.0047 · 88,663 in / 17,394 out · 28,708 cached (32%) · deepseek/deepseek-v4-flash
tests: $0.0005 · 16,246 in / 108 out · 0 cached (0%) · deepseek/deepseek-v4-flash
description: $0.0005 · 32,042 in / 16,221 out · 28,196 cached (88%) · deepseek/deepseek-v4-flash
What
Adds
JevProvider::SelfHosted(wireself_hosted, aliasselfhosted) to the bus contract and engine, bumpingCONTRACT_VERSIONto 2.9 (additive).An operator-declared Jev-compatible decisions endpoint — e.g. a self-hosted open decision model serving the Surogate decisions-v1 protocol — can now drive the Jev loops:
JevConfig::endpoint_urlandJevConfig::modelare required (JEV_INVALID_CONFIGotherwise).Client::newremains the syntactic gate (absolute HTTP(S), no embedded credentials, no query or fragment, plain HTTP only on a literal loopback address).Why
Hosts that run their own decision models (on-prem inference, no third-party Jev route) currently have no way to point the Jev loops at them: every provider has exactly one hardcoded approved endpoint. The allowlist's job — keeping provider credentials off unapproved routes — does not apply to a self-hosted endpoint, where the key and the endpoint are one operator-declared unit.
Verification
cargo testgreen fortinycomputer-bus,tinycomputer-engine,tinycomputer(bus 192+34 lib/doctest, engine 353, module 33+10),cargo clippyclean (pedantic),cargo fmtapplied.desktop-module-contract.mdversion history,configuration.md,jev-runtime.md,architecture.md,MODULE.md,goal-loop.md.