Repository navigation
model: one-shot model calls for contained apps (model.complete) - #95
Merged
Merged
Conversation
ADR 0002 decision C: an app's AI keeps its main path through its own
octos agent and the toolbox's templates; contained script apps also get a
narrow, direct, one-shot model call. The ADR gains section 14, "Direct
one-shot model calls (model.complete)".
The `model` host service (octosense-llm-service, src/complete), registered
by crates/ai-host with the llm service:
- host.request("model.complete", {task, input, schema, class?, allow_urls?})
answers {output, meta: {class, requested, attempts, usage, budget}};
model.budget answers the caller's budget.
- The app names a class (fast or strong, octos's catalog types); the host
picks the provider from the person's profile in their order, that class
first, and falls back past a failing provider. The app never sees the
provider, model id or key; meta carries the class and token usage.
- One shot: no tools, memory or history. The JSON Schema (Octoscript's
bounded subset, 8 KiB) is required; a reply that is not JSON, fails the
schema, carries a URL (unless allow_urls) or is over 16 KiB is retried
once, then refused.
- No output-token cap is sent (Anthropic gets the model's own catalog
maximum, which its API requires).
- A per-app ledger in <host dir>/model/ledger.json: 6 calls a minute,
100 calls and 100,000 tokens a UTC day by default, with per-app
overrides for a Settings page (follow-up).
- Refusals are "<code>: <sentence>": capability, no_provider, rate, budget,
bad_request, invalid_output, too_large, provider.
- The service checks the app's own manifest for `model` behind the Card
runner's isolate gate. ModelHost::complete is the Rust entry the
toolbox's ModelClient (#82) can share, so budgets live in one place.
Tests with a fake provider (tests/complete.rs) and a live example against
DeepSeek (examples/model_complete_live.rs). No pins move: the `model`
capability is in App Hub (#24) and reaches the shells with the App Hub pin
move in #70.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main now pins App Hub e8601b80, whose KNOWN_CAPABILITIES has `model`, so the capability is real end to end. The new test checks Makepad's isolate gate (splash_policy, fed the app's resolved policy) admits model.complete and model.budget only with `model` (not `llm` or a near name, and `model` grants nothing else), then that the service's default manifest grant serves the granted app and refuses the other. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ymote
marked this pull request as ready for review
September 28, 2026 09:27
ymote
added a commit
that referenced
this pull request
Sep 29, 2026
… [skip ci]
- approvals/contacts.rs: ContactsSource (is_known for email addresses and
Matrix IDs), ContactList/NoContacts test doubles, MailContacts reading
Mail's host service data, and ContactsGate: off by default, on only by
the person's Settings tap ("Use my contacts in approval rules"), kept
owner-only in approvals/contacts.json. The router evaluates "recipients
in contacts" through the gate.
- mail host service: contacts::known_addresses (own accounts, recorded
send recipients, To/Cc of sent folders; never the secret store); the
decoder keeps To/Cc; a removed account forgets its recipients.
- docs/ai-services(.zh-CN).md refreshed against main (#106, #113, #117,
#118, #120, #124, #95, #86), linking architecture.md; architecture's
contacts lines updated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tyreseluo
pushed a commit
to tyreseluo/OctoSense
that referenced
this pull request
Oct 4, 2026
…s, CEF profile under MAKEPAD_HOME) Moves the runtime from makepad 39a9de88 to makepad main c155f61d, through Octoscript-Makepad 2cc5ef37 (OctoSense-org#71). New in makepad: - OctoSense-org#94 Files, Task and Browser: an empty library beside each binary - OctoSense-org#95 Calculator, Clock, Notes, Reminders, Weather: each module opens its own agent's link and answers its tools there - OctoSense-org#96 hosted Notes and Reminders start empty - OctoSense-org#97 Files' chat panel only reads - OctoSense-org#98 CEF keeps its stable profile under MAKEPAD_HOME when that is set Root Cargo.toml (every makepad revision, octoscript-makepad and octoscript-node), the Sheets and Terminal revisions in native-apps.json, both native-runtime.lock.json files, and runtime-patches.lock.json: base c155f61d, the five patches reapply unchanged (none touches a changed file), new tree 3eadf628. Cargo.lock does not change. The phone docs that name the pinned hashes move with it; rom/docs/home-build.md gets its own follow-up, as with OctoSense-org#289. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
ADR 0002 decision C: an app's AI keeps its main path through its own octos agent and the toolbox's templates. In addition, contained script apps get a narrow, direct, one-shot model call,
model.complete. This PR adds the ADR addendum (section 14, "Direct one-shot model calls (model.complete)") and themodelhost service.The API
host.request("model.complete", {task, input, schema, class?, allow_urls?}, fn(r){ … })taskinputschematype,properties,required,additionalProperties,items,min/maxItems,min/maxLength,minimum,maximum,enum; ≤ 8 KiB). Any other keyword is refusedclass"fast"(default) or"strong": octos catalog types, never a provider or a model idallow_urlsfalseReply:
{output, meta: {class, requested, attempts, usage: {input_tokens, output_tokens, estimated}, budget: {calls_today, calls_per_day, tokens_today, tokens_per_day, tokens_left, per_minute, resets_at}}}.model.budgetreturns the caller's budget alone.Errors are
"<code>: <sentence>", wherecodeis one ofcapability,no_provider,rate,budget,bad_request,invalid_output,too_largeorprovider. The sentence never names a provider or a model.Rules
meta.classis the class that answered. The model id is deliberately not shown.http://,https://orwww.refuses the reply. This is the toolbox'svalidate_digestrule, because replies become L0/L1 card data.allow_urls: trueopts out.max_tokens, so it gets the model's own catalog maximum. The host reads at most 1 MiB of a provider's answer and accepts at most 16 KiB of reply.<apps root>/.host/model/ledger.json. Defaults are 6 calls a minute, 100 calls and 100,000 tokens per app per UTC day. A call is admitted only if its estimated input fits. Both attempts of a retry are charged. Per-app overrides live in the same file.ModelHost::usage()andModelHost::set_limits()are ready for a Settings page (follow-up).splash_policy::service_allowed). Behind it, the service checks the app's own verified manifest formodel(manifest_grants). There is noos.*restriction, unlikellm.complete::host()→ModelHost::complete(app, Request). The toolbox'sModelClient(feat(toolbox): the system toolbox's workflow template library #82) maps{system, user, output_schema}onto aRequestwith the host-onlysystemfield set, whichhost.requestcan never set. The adapter is a few lines and shares the per-app ledger. feat(toolbox): the system toolbox's workflow template library #82 is not touched here.octoscript-schemaitself. That crate enables serde_json'sarbitrary_precision, which would change number handling across the whole shell build.Where
apps/ai-providers/host-service/src/complete/{mod,schema,ledger,wire}.rs, plusregister_model()insrc/lib.rscrates/ai-host/src/lib.rs: registered with thellmservice (featurellm)docs/adr/0002-event-driven-app-agents.md§14. The ADR's title and status are unchanged, so the ADR indexes are not touched.AGENTS.mdtable rowsVerification
cargo test --locked -p octosense-llm-service --test complete: 18 passed, using a fake provider. Covered: schema pass; schema fail after one retry; invalid JSON then a pass on the retry; a URL refused, and allowed withallow_urls; the size cap (reply, input, schema); nomax_tokens/max_completion_tokens/max_output_tokenssent (and Anthropic's catalog maximum); token budget; daily call budget; rate limit; no provider; capability missing; the manifest grant; fallback order with class preference; no provider, model or key in replies or errors; the profile as provider source; a host caller sharing the ledger; and, new after the merge,the_card_runner_gate_and_the_service_agree_on_the_model_capability: Makepad's isolate gate (splash_policy, which App Hub feeds each app's resolved policy) admitsmodel.completeandmodel.budgetonly withmodel(notllmormodels),modelgrants nothing else, and the service's default manifest grant then serves the granted app and refuses the other. A scratch crate onoctosense-app-policyate8601b80also confirmed a manifest declaringmodelresolves withallows("model")and nothing more. Plus 7 unit tests (schema, ledger, wire).cargo test --locked -p octosense-mail-service -p octosense-llm-service -p octosense-news-service: pass.cargo test --locked -p octosense-ai-host --features octos-core,llm: 12 passed.cargo clippy --locked -p octosense-llm-service --all-targets --no-deps -- -D warningsand the same for-p octosense-ai-host --features octos-core,llm: clean.cargo check --locked -p octosense, with and without--features mobile-apps: pass.python3 tools/setup.py --check --cargo: pass (.sourcesare hub worktrees at main's pins, makepadb0cbc9bc).bash tools/check-shell-graph.sh -p octosensepasses, and fromphone/,cargo check --locked -p octosense-home --features mobile-appsandbash ../tools/check-shell-graph.sh -p octosense-homepass.from
phone/:cargo test --locked -p octosense-app-policy --no-run: pass, not ambiguous.After the merge:
cargo test --locked -p octosense-llm-serviceandcargo test --locked -p octosense-ai-host --features octos-core,llm: pass. The live DeepSeek check below was run before the merge.Live check against DeepSeek (
examples/model_complete_live.rs), with one call per class and 1,054 tokens in total (about US$0.0004):{"title":"Boiler leaking; call plumber","tags":["boiler","plumbing","urgent"],"urgent":true}, 241 in / 185 out;{"title":"Leaking boiler in kitchen","tags":["home","plumbing","urgent"],"urgent":true}, 294 in / 334 out.Both replies validated on the first attempt, and no output cap was sent.
Not run in a shell or on a device: no app declares
modelyet.Follow-ups
ModelClientadapter ontoModelHost(after feat(toolbox): the system toolbox's workflow template library #82).modeldeclares it in its manifest.🤖 Generated with Claude Code