arcane-os 0.78.0 → 0.79.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.79.0
4
+
5
+ - Add public `createExecutionDeviceCatalog()` and `createExecutionDeviceService()`
6
+ with lazy Windows CPU/GPU/NPU discovery, stable host-local identities,
7
+ complete discovery issues, explicit refresh, cancellation and disposal.
8
+ `localai.devices` and `localai.resolveTarget` use the existing Core transport
9
+ without loading models or changing saved preferences.
10
+ - Share one catalog per local-AI Core lifetime. Native ONNX and Laya accept
11
+ `executionTarget` for a physical CPU or mapped DirectML GPU. Automatic GPU
12
+ selection can use the largest reported dedicated capacity among addressable
13
+ hardware GPUs; integrated GPUs remain eligible. Unsupported or unavailable
14
+ selections are reported without silently choosing another physical device.
15
+ - Preserve caller options and complete execution diagnostics. Distinguish the
16
+ requested, resolved and configured target from observed execution; successful
17
+ configuration is not GPU/NPU execution proof. Laya target replacement cancels
18
+ and drains only its retiring activation before the successor starts.
19
+ - Keep discovery independent from model readiness and preserve existing engine
20
+ behavior when a target is omitted. Browser Wllama, native llama.cpp, image and
21
+ Whisper device routing are unchanged; this release adds no driver, model or
22
+ native runtime dependency.
23
+
3
24
  ## 0.78.0
4
25
 
5
26
  - Add `AI.readPreparedTTS()` and shared `readPreparedSpeech()` for complete
package/README.md CHANGED
@@ -20,12 +20,17 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
20
20
  share the theme, packaging, event, cancellation,
21
21
  and browser run contracts while retaining their selected application layout.
22
22
 
23
- This checkout defines the `0.78.0` SDK contract. Development applications declare
23
+ This checkout defines the `0.79.0` SDK contract. Development applications declare
24
24
  `arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
25
25
  designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
26
26
  to refresh and compare the installed SDK and lockfile with the published channel;
27
27
  registry state is deliberately not baked into application artifacts.
28
28
 
29
+ [Physical execution devices](docs/reference/execution-devices.md) exposes Windows
30
+ CPU/GPU/NPU discovery and native ONNX/Laya target selection through the existing
31
+ Core host, independently of model loading. Device presence, configured provider
32
+ placement and observed model execution remain separate facts.
33
+
29
34
  Standalone browser apps use their repository root and installed npm package directly, without a
30
35
  generated workspace `arcane/` directory or `arcane.lock.json`. Select the
31
36
  [four installed-package routes](docs/reference/protocols.md#installed-package-browser-routes)
@@ -106,9 +106,13 @@ export function createCoreONNXRuntime({client: suppliedClient} = {}) {
106
106
  }
107
107
  }
108
108
 
109
- async function load({id, model, sessionOptions, signal, timeoutMs} = {}) {
109
+ async function load({id, model, sessionOptions, executionPreference, executionTarget, signal, timeoutMs} = {}) {
110
110
  const currentConnection = connection;
111
- const result = await invoke('onnx.load', {id, model, sessionOptions}, {signal, timeoutMs});
111
+ const result = await invoke(
112
+ 'onnx.load',
113
+ {id, model, sessionOptions, executionPreference, executionTarget},
114
+ {signal, timeoutMs}
115
+ );
112
116
  currentConnection.controller.signal.throwIfAborted();
113
117
  return result;
114
118
  }
@@ -32,6 +32,16 @@ export const CORE_METHOD_CONTRACTS={
32
32
  'preferences.set':{input:'{key,value}',output:'{key,value}'},
33
33
  'preferences.setMany':{input:'{entries}',output:'preference-batch-result'},
34
34
  'preferences.delete':{input:'{key}',output:'{key,deleted}'},
35
+ 'localai.devices': {
36
+ input: '{refresh?}',
37
+ output: '{platform,state,devices,issues}',
38
+ meaning: 'Physical host inventory, independent of model startup. Presence does not establish engine support or observed execution.'
39
+ },
40
+ 'localai.resolveTarget': {
41
+ input: '{executionTarget:{deviceId}|null,refresh?}',
42
+ output: '{requestedTarget,resolvedDevice,resolution,reason}',
43
+ meaning: 'Resolve a saved physical identity. Explicit null selects engine-owned automatic resolution; it does not claim a configured or observed device.'
44
+ },
35
45
  'window.setTheme': {
36
46
  input: '{backgroundColor?,textColor?}: null reset or {red,green,blue,alpha}',
37
47
  output: '{platform,supported,applied,unsupported}',
@@ -53,6 +53,22 @@ and reports npm's response. It tolerates npm's publish-time scanning. If
53
53
  scanning or manual review remains pending, the workflow reports that state and
54
54
  a rerun safely resumes without republishing the version.
55
55
 
56
+ Registry verification requires both the selected version/channel metadata and
57
+ an ordinary complete GET of that exact version's published `dist.tarball` URL.
58
+ Metadata visibility alone is not downloadable-package delivery. The verifier
59
+ reads exact-version metadata, follows its tarball URL, and consumes the full
60
+ successful response before reporting completion. Missing metadata, an HTTP
61
+ failure, or an interrupted body remains pending within the existing bounded
62
+ retry window; the remaining window also bounds the metadata/tarball requests.
63
+ The existing npm metadata subprocesses retain their current behavior. If they
64
+ consume the window, including a zero-wait invocation, the verifier reports
65
+ pending without starting a new download observation.
66
+ It records the actual failure and exits unsuccessfully if availability does not
67
+ arrive. It does not save a second artifact, repack, republish, alter registry
68
+ tags, add cache-bypass parameters, or impose a content-size or hash comparison.
69
+ Requester notices follow successful version/channel and download observation;
70
+ consumer installation and application verification remain separately owned.
71
+
56
72
  The public `0.3.2` release is fixed to package-source commit
57
73
  `445bd2d982f12e6ef8dd2b615c70512000cc5224`, selected Check run
58
74
  `33264677687`, and publication/registry run `33264829711`. Its numeric Git tag
@@ -77,9 +93,10 @@ The npm package already has its trusted-publishing relationship. Each later
77
93
  release therefore follows the same direct selected-artifact path: push the
78
94
  intended `main` source, manually run Check for that exact revision, review the
79
95
  resulting package inventory and legal notices, then manually dispatch
80
- publication with that Check run and artifact. Confirm the selected version and
81
- dist-tag after publication. Never rebuild or repack the artifact under
82
- publication authority, and never substitute a different source revision.
96
+ publication with that Check run and artifact. Confirm the selected version,
97
+ dist-tag, and complete published tarball availability after publication. Never
98
+ rebuild or repack the artifact under publication authority, and never substitute
99
+ a different source revision.
83
100
 
84
101
  Generated app CI uses `npm ci --ignore-scripts`, so its lock must exist and its
85
102
  dependency source must be reachable by the runner. External application CI uses
@@ -0,0 +1,157 @@
1
+ # Physical execution devices
2
+
3
+ The local-AI Core host exposes physical device discovery independently of model
4
+ installation, loading and inference. A device being present does not establish
5
+ that a particular engine can use it. The engine resolves that second question
6
+ when the application explicitly loads its selected model.
7
+
8
+ ## List devices without loading a model
9
+
10
+ Use the application's existing Core client:
11
+
12
+ ```js
13
+ const catalog = await client.invoke('localai.devices', {refresh: false}, {signal});
14
+ console.log(catalog.devices);
15
+ console.log(catalog.issues);
16
+ ```
17
+
18
+ The inventory has `{platform, state, devices, issues}`. `platform` uses Node's
19
+ platform name. `state` is `ready`, `partial` or `unavailable`. Incomplete or failed
20
+ discovery is reported in `issues`; it is not evidence that the machine has no
21
+ devices. This initial discovery implementation uses Windows host APIs. Other
22
+ platforms report this capability as unavailable while retaining their existing
23
+ AI runtime behavior.
24
+
25
+ Each device has the following fields:
26
+
27
+ | Field | Meaning |
28
+ | --- | --- |
29
+ | `deviceId` | Stable host-local selection key, or `null` when identity could not be established. |
30
+ | `identitySource` | `windows-pnp-instance`, `host-cpu`, or `null`. |
31
+ | `kind` | `cpu`, `gpu` or `npu`. |
32
+ | `name` | Complete reported display name. Names are not selection identities. |
33
+ | `present` | Whether the queried device is present. |
34
+ | `isHardware`, `isIntegrated` | Reported hardware facts, or `null` when unavailable. |
35
+ | `dedicatedMemoryMiB` | Reported dedicated capacity, or `null`; used only for supported automatic GPU selection. |
36
+ | `addresses` | Observed adapter addresses described below. These are not persistent selection keys. |
37
+ | `processors` | Actual processor records for the aggregate CPU option; an empty array for GPU/NPU records. |
38
+
39
+ GPU and NPU identities are actual, uniquely correlated Windows PnP instance IDs.
40
+ An adapter whose stable identity cannot be established remains in the inventory
41
+ with `deviceId: null` and a corresponding issue. The SDK does not invent a key
42
+ from a name, enumeration order, hardware model number or adapter LUID.
43
+
44
+ The CPU choice uses `deviceId: 'cpu'` and `identitySource: 'host-cpu'`. It selects
45
+ the whole-host CPU execution target, without claiming socket or core affinity.
46
+ Its `processors` entries contain `{deviceId, name, socketDesignation, cores,
47
+ logicalProcessors}`; unavailable optional facts are `null`.
48
+
49
+ `addresses` contains:
50
+
51
+ ```js
52
+ {
53
+ adapterLuid: {highPart, lowPart}, // or null
54
+ pci: {bus, device, function: pciFunction}, // or null
55
+ dxgiAdapterIndex // or null
56
+ }
57
+ ```
58
+
59
+ The DXGI index is the actual `EnumAdapters` index joined to the adapter's LUID.
60
+ It is never inferred from DXCore or PnP enumeration order. It supplies a possible
61
+ DirectML address, not proof of provider availability or successful execution.
62
+ No CUDA ordinal is inferred from these Windows addresses.
63
+
64
+ An issue contains `{source, code, message, deviceId}`. `source` identifies `cpu`,
65
+ `gpu`, `npu`, `identity` or `dxgi`; `deviceId` is `null` when no stable identity
66
+ was established. Keep the complete diagnostic available to developers rather
67
+ than substituting an empty inventory.
68
+
69
+ ## Resolve a saved selection
70
+
71
+ Store the selected physical ID with the application's existing preferences,
72
+ keyed by provider and model. The SDK introduces no new preference store and
73
+ does not change the six-slot model preference tuple.
74
+
75
+ ```js
76
+ const resolution = await client.invoke('localai.resolveTarget', {
77
+ executionTarget: {deviceId: savedDeviceId}
78
+ }, {signal});
79
+ ```
80
+
81
+ The result contains `{requestedTarget, resolvedDevice, resolution, reason}`:
82
+
83
+ - `matched`: the complete device record was found. Engine support is still a
84
+ separate question.
85
+ - `unavailable` with `device-not-present`: complete relevant discovery did not
86
+ find that saved identity. Preserve the saved selection so the application can
87
+ show that it is currently unavailable.
88
+ - `unsupported` with `inventory-unavailable` or `identity-unresolved`: discovery
89
+ cannot establish the requested identity. Do not describe it as an absent device.
90
+ - `automatic` with `engine-default-required`: an explicit `executionTarget: null`
91
+ asks the selected engine to choose at its activation boundary. The physical
92
+ resolver returns `resolvedDevice: null` because it cannot establish the
93
+ engine's routing capabilities.
94
+
95
+ Calling the resolver does not load a model, change preferences or replace an
96
+ existing activation. A model load that omits `executionTarget` retains that
97
+ API's existing constructor, session-option and default behavior. Explicit
98
+ `null` selects automatic resolution for the new activation; it does not erase
99
+ or relabel a running session.
100
+
101
+ ## Engine configuration and execution evidence
102
+
103
+ The physical-target integration described here applies to native ONNX and
104
+ native Laya through Core. Listing host devices does not add physical-device
105
+ selection to browser WebGPU/Wllama or to the separate native llama.cpp, image
106
+ and Whisper owners. Their existing backend options remain unchanged. A browser
107
+ uses the existing Core connection for this inventory; the catalog does not
108
+ create a native connection or replace a browser provider.
109
+
110
+ An engine receives the saved selection as `executionTarget: {deviceId}`. It
111
+ owns provider availability, the physical-to-provider address mapping and the
112
+ actual session configuration. Its public state distinguishes the requested
113
+ target, physical resolution, configured target and observed execution. A
114
+ successful session configuration alone is not evidence that every operation ran
115
+ on a GPU or NPU.
116
+
117
+ Automatic selection can prefer the hardware GPU with the highest reported
118
+ dedicated capacity among supported targets with established routing. An
119
+ integrated GPU remains eligible when it meets those same conditions. An unavailable saved
120
+ target is not silently replaced by another device or a cloud route. See
121
+ [native ONNX](local-ai.md#native-onnx-sessions) and [native decisions](native-decisions.md)
122
+ for the selected engine's load, replacement, cancellation and status contracts.
123
+
124
+ ## Catalog and Core ownership
125
+
126
+ SDK development local-AI composition and generated native composition with
127
+ `localAI` selected register one lazy catalog and its independent
128
+ `execution-devices` service. An empty runtime selection still permits device
129
+ discovery. Hosts without local-AI composition retain their existing service
130
+ selection. A manually composed Core can register the service explicitly:
131
+
132
+ ```js
133
+ import {createExecutionDeviceCatalog} from 'arcane-os/local-ai/execution-devices';
134
+ import {createExecutionDeviceService} from 'arcane-os/core/execution-devices';
135
+ import {createLocalAIService} from 'arcane-os/core/local-ai';
136
+
137
+ const executionDevices = createExecutionDeviceCatalog({signal});
138
+ const services = [
139
+ createExecutionDeviceService({catalog: executionDevices}),
140
+ createLocalAIService(localAI, {appRoot, runtimes, signal, executionDevices})
141
+ ];
142
+ ```
143
+
144
+ Pass these definitions to the existing Core host. The independent service owns
145
+ catalog disposal. The local-AI engine borrows the same catalog; it does not
146
+ dispose a supplied owner or create another discovery process per model.
147
+
148
+ For direct Node use, `createExecutionDeviceCatalog({signal})` returns
149
+ `devices({refresh, signal})`, `resolveTarget({executionTarget, refresh, signal})`
150
+ and `dispose()`. The direct caller owns disposal. Construction performs no
151
+ discovery I/O. Calls reuse/coalesce the host's discovery; `refresh: true` requests
152
+ an explicit new observation. There is no background polling. Closing the owner
153
+ releases its discovery work and native query resources.
154
+
155
+ The catalog queries installed operating-system facilities. It does not install
156
+ drivers, download models, alter system policy or introduce a hardware admission
157
+ rule. A host capability failure remains an honest unavailable/partial result.
@@ -1,30 +1,58 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sdkVersion": "0.77.0",
3
+ "sdkVersion": "0.78.0",
4
4
  "environment": {
5
5
  "runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
7
7
  "moduleSystem": "ESM"
8
8
  },
9
- "memberCount": 299,
9
+ "memberCount": 301,
10
10
  "runtimeSubpathPatterns": {
11
11
  "arcane-os/modules/*": "./runtime/arcane/modules/*",
12
12
  "arcane-os/entities/*": "./runtime/arcane/entities/*"
13
13
  },
14
14
  "members": [
15
+ {
16
+ "id": "local-ai-execution-devices:createExecutionDeviceCatalog",
17
+ "name": "createExecutionDeviceCatalog",
18
+ "displayName": "createExecutionDeviceCatalog()",
19
+ "kind": "function",
20
+ "signature": "createExecutionDeviceCatalog({signal}={})",
21
+ "entrypoints": ["arcane-os/local-ai/execution-devices"],
22
+ "primaryImport": "arcane-os/local-ai/execution-devices",
23
+ "group": "Physical execution devices",
24
+ "summary": "Owns lazy physical-device discovery and saved device-ID resolution independently of models and inference.",
25
+ "availability": "Node; Windows discovery is implemented, while other platforms return an unavailable inventory without changing existing AI behavior",
26
+ "protocol": "Physical inventory {platform,state,devices,issues}; target resolution {requestedTarget,resolvedDevice,resolution,reason}",
27
+ "normalization": "Returns async devices({refresh=false,signal}={}), async resolveTarget({executionTarget=null,refresh=false,signal}={}) and dispose(). Construction performs no discovery I/O; calls share cached/coalesced discovery and refresh explicitly requests a new observation. Complete records and issues distinguish ready, partial and unavailable discovery. A saved deviceId resolves as matched, unavailable or unsupported; null returns automatic/engine-default-required without discovery, and a non-null target without a nonempty string deviceId throws TypeError. Physical presence never proves engine support or actual execution. Request cancellation rejects AbortError/ARCANE_CANCELLED and the last departing caller stops the query; retained dispose() joins owned discovery shutdown, and constructor-signal abort disposes the catalog. The direct caller owns disposal; no polling, model loading, preference writes or silent target substitution. See execution-devices.md for all record fields and reasons."
28
+ },
29
+ {
30
+ "id": "core-execution-devices:createExecutionDeviceService",
31
+ "name": "createExecutionDeviceService",
32
+ "displayName": "createExecutionDeviceService()",
33
+ "kind": "function",
34
+ "signature": "createExecutionDeviceService({catalog=createExecutionDeviceCatalog()}={})",
35
+ "entrypoints": ["arcane-os/core/execution-devices"],
36
+ "primaryImport": "arcane-os/core/execution-devices",
37
+ "group": "Physical execution devices",
38
+ "summary": "Registers physical-device discovery as an independent Core service with catalog lifetime ownership.",
39
+ "availability": "Node Core; available independently of selected or loaded model runtimes, with discovery support reported by the catalog",
40
+ "protocol": "arcane/1 localai.devices and localai.resolveTarget",
41
+ "normalization": "Returns the execution-devices service with localai.devices({refresh=false}) and localai.resolveTarget({executionTarget=null,refresh=false}); both forward the Core request signal and return the catalog's complete result. Registration performs no discovery or model work. dispose() owns disposal of the supplied or default catalog; other services may borrow that same catalog without owning it."
42
+ },
15
43
  {
16
44
  "id": "local-ai-decisions:createNativeDecisionModel",
17
45
  "name": "createNativeDecisionModel",
18
46
  "displayName": "createNativeDecisionModel()",
19
47
  "kind": "function",
20
- "signature": "createNativeDecisionModel({onnx,modelAssets,workingDirectory,paths,model='onnx-community/laya-typed-decisions-ONNX',revision='main',dtype='fp32',sessionOptions,executionPreference='gpu',signal}={})",
48
+ "signature": "createNativeDecisionModel({onnx,modelAssets,workingDirectory,paths,model='onnx-community/laya-typed-decisions-ONNX',revision='main',dtype='fp32',sessionOptions,executionPreference='gpu',executionTarget,signal}={})",
21
49
  "entrypoints": ["arcane-os/local-ai/decisions"],
22
50
  "primaryImport": "arcane-os/local-ai/decisions",
23
51
  "group": "Native typed decisions",
24
52
  "summary": "Explicit native Laya FP32 option scoring through an existing ONNX owner and a tokenizer Worker.",
25
53
  "availability": "Node on Windows/Linux/macOS with selected ONNX Runtime and model files; Android needs host adaptation",
26
54
  "protocol": "Shared browser/native decision encoding with native named tensor results",
27
- "normalization": "Returns load/evaluate/classify/current/status/subscribe/unload/dispose; preserves complete separate state/question/options/type and original rows, raw scores and every graph output. Explicit load prepares selected upstream files or uses caller-owned paths; GPU preference attempts advertised providers then CPU. Cancellation retires the shared activation; files remain retained through actual ONNX and tokenizer Worker exit. No chat history or duplicate inference engine."
55
+ "normalization": "Returns load/evaluate/classify/current/status/subscribe/unload/dispose; preserves complete separate state/question/options/type and original rows, raw scores and every graph output. Explicit load prepares selected upstream files or uses caller-owned paths. load({executionTarget,signal}) retains the latest selected target when omitted or undefined, initially the factory selection; null requests automatic resolution, while {deviceId} forwards the saved physical selection to the existing ONNX owner. With no target selection, GPU preference still attempts advertised providers then CPU and explicit session providers retain precedence. Same-target loads share activation; a changed target cancels the earlier request and joins its actual ONNX/tokenizer cleanup before replacement. pendingActivation reports the requested replacement separately; current().execution remains the complete ONNX load record, with requested/resolved/configured/observed target diagnostics in current().execution.execution. Configuration alone does not prove physical execution. Cancellation retires the shared activation; files remain retained through actual ONNX and tokenizer Worker exit. No preference writes, chat history or duplicate inference engine."
28
56
  },
29
57
  {
30
58
  "id": "core-decisions:createNativeDecisionService",
@@ -38,7 +66,7 @@
38
66
  "summary": "Renderer-independent native Laya service reusing the registered local-AI and model-assets owners.",
39
67
  "availability": "Node Core with selected native ONNX and model files",
40
68
  "protocol": "arcane/1 decisions.status/load/evaluate/unload and decisions.state",
41
- "normalization": "Registration starts no model or tokenizer. Explicit load obtains dependency-ready same-process owners and reacquires getONNXRuntime after recovery. Native load/evaluate/classify/current/subscribe/unload/dispose preserve complete results; RPC uses the existing tensor codec. Unload has accepted service lifetime; host shutdown cancels request-lifetime load/evaluation."
69
+ "normalization": "Registration starts no model or tokenizer. Explicit load obtains dependency-ready same-process owners and reacquires getONNXRuntime after recovery. Factory configuration accepts executionTarget; direct load({executionTarget,signal}) and Core decisions.load({executionTarget}) preserve omitted/undefined selection and explicit null automatic selection. Same-target loads coalesce; changed targets cancel pending work and join the prior model's disposal before replacement. pendingActivation remains separate from the full activation execution record and its nested execution diagnostics. Native load/evaluate/classify/current/subscribe/unload/dispose preserve complete results; RPC uses the existing tensor codec. Unload has accepted service lifetime; host shutdown cancels request-lifetime load/evaluation."
42
70
  },
43
71
  {
44
72
  "id": "core-decisions:default",
@@ -353,14 +381,14 @@
353
381
  "name": "createONNXRuntime",
354
382
  "displayName": "createONNXRuntime()",
355
383
  "kind": "function",
356
- "signature": "createONNXRuntime({modulePath,signal,onEvent}={})",
384
+ "signature": "createONNXRuntime({modulePath,onEvent,signal,executionDevices}={})",
357
385
  "entrypoints": ["arcane-os/local-ai/onnx"],
358
386
  "primaryImport": "arcane-os/local-ai/onnx",
359
387
  "group": "Core local AI",
360
388
  "summary": "Owns explicitly loaded ONNX graphs and complete named tensor inference in retained native workers.",
361
389
  "availability": "Node on Windows/Linux/macOS x64/arm64 with prepared onnxruntime-node; Android needs host adaptation",
362
390
  "protocol": "Per-session worker lifecycle with structured-clone tensors and replaying state",
363
- "normalization": "Returns load/run/unload/current/subscribe/close; load accepts executionPreference gpu for advertised provider creation attempts then CPU, while explicit sessionOptions.executionProviders takes precedence. Complete execution diagnostics distinguish configured providers from physical acceleration. stopping revokes new runs while exited records actual Worker exit; recoverable run errors preserve the session. Cleanup joins exit and output delivery even when termination fails."
391
+ "normalization": "Returns load/run/unload/current/subscribe/close. load({id,model,sessionOptions,executionPreference,executionTarget,signal}) preserves existing behavior when executionTarget is omitted: gpu preference tries advertised providers then CPU, while explicit sessionOptions.executionProviders takes precedence. Explicit null requests automatic selection; with gpu preference and no explicit providers it prefers the highest dedicatedMemoryMiB among mapped hardware GPUs, including integrated hardware, then uses existing provider selection when no eligible mapping exists. A saved {deviceId} selects whole-host CPU or an established DirectML adapter mapping; unavailable, unsupported or conflicting targets report LOCAL_AI_EXECUTION_TARGET_UNAVAILABLE, LOCAL_AI_EXECUTION_TARGET_UNSUPPORTED or LOCAL_AI_EXECUTION_TARGET_CONFLICT without silently replacing the selection. Every loaded session reports requestedTarget, resolvedDevice, resolution/reason and configuredTarget {source,executionProviders}; observedTarget remains null because configuration does not prove physical execution. SDK-selected GPU configurations retain CPU operator support. A supplied executionDevices catalog is borrowed; otherwise the runtime owns its lazy catalog. stopping revokes new runs while exited records actual Worker exit; recoverable run errors preserve the session. Cleanup cancels target discovery and joins preparation, exit and output delivery even when termination fails."
364
392
  },
365
393
  {
366
394
  "id": "core-onnx:createCoreONNXRuntime",
@@ -374,7 +402,7 @@
374
402
  "summary": "Accesses local system ONNX through an existing Core client with complete tensor transport.",
375
403
  "availability": "Browser with available Core ONNX service; construction installs no runtime or model",
376
404
  "protocol": "arcane/1 onnx.status/load/run/unload and localai.state",
377
- "normalization": "Returns load/run/unload/inspect/current/subscribe/close; omitted client follows late installation/replacement/retirement and reads current status, while explicit client including null stays fixed. Retirement clears readiness and cancels owned work; late old-client results are suppressed. Preserves typed and string tensors; close removes subscriptions and cancels requests without closing shared Core or implicitly unloading shared models"
405
+ "normalization": "Returns load/run/unload/inspect/current/subscribe/close; load({id,model,sessionOptions,executionPreference,executionTarget,signal,timeoutMs}) forwards provider preference and physical selection while preserving omitted-versus-null target semantics. Complete native execution diagnostics distinguish requested, resolved and configured targets from unobserved physical execution. Omitted client follows late installation/replacement/retirement and reads current status, while explicit client including null stays fixed. Retirement clears readiness and cancels owned work; late old-client results are suppressed. Preserves typed and string tensors; close removes subscriptions and cancels requests without closing shared Core or implicitly unloading shared models"
378
406
  },
379
407
  {
380
408
  "id": "root:createGitTextSnapshot",
@@ -465,14 +493,14 @@
465
493
  "name": "createLocalAIService",
466
494
  "displayName": "createLocalAIService()",
467
495
  "kind": "function",
468
- "signature": "createLocalAIService(configuration,{appRoot,runtimes=[],signal,onEvent,prepare}={})",
496
+ "signature": "createLocalAIService(configuration,{appRoot,runtimes=[],signal,onEvent,prepare,executionDevices}={})",
469
497
  "entrypoints": ["arcane-os/core/local-ai"],
470
498
  "primaryImport": "arcane-os/core/local-ai",
471
499
  "group": "Core local AI",
472
500
  "summary": "Composes selected native llama.cpp, Ollama and ONNX engines as a Core service with retained process/worker ownership and model lifecycle.",
473
501
  "availability": "Node with selected upstream runtime distributions or available configured system services",
474
502
  "protocol": "arcane/1 methods and events; llama.cpp OpenAI HTTP/SSE, Ollama native HTTP/NDJSON and ONNX tensor transport",
475
- "normalization": "Application-owned runtime and model requirements; optional prepare supplies installed records during this service startup. getONNXRuntime() returns the actual prepared ONNX owner for native composition after context.getService('local-ai'); callers reacquire after recovery and retain the acquired handle for cleanup. llama.status/models observe without starting or restarting an engine. Independent services, complete responses and exact owned-process cleanup remain unchanged."
503
+ "normalization": "Application-owned runtime and model requirements; optional prepare supplies installed records during this service startup. Optional executionDevices supplies a borrowed catalog to ONNX; its supplying owner retains catalog disposal responsibility. onnx.load forwards executionPreference and executionTarget with existing model-path resolution, cancellation and omitted-target behavior. getONNXRuntime() returns the actual prepared ONNX owner for native composition after context.getService('local-ai'); callers reacquire after recovery and retain the acquired handle for cleanup. llama.status/models observe without starting or restarting an engine. Independent services, complete responses and exact owned-process cleanup remain unchanged."
476
504
  },
477
505
  {
478
506
  "id": "core-local-ai:default",
@@ -4192,7 +4220,7 @@
4192
4220
  "summary": "Describes existing Core method input, output and semantic shapes.",
4193
4221
  "availability": "Node and browser/bundler",
4194
4222
  "protocol": "arcane/1 method descriptions",
4195
- "normalization": "Descriptive data only; the active host and its registered services remain the authority for method availability"
4223
+ "normalization": "Descriptive data only, including independent localai.devices inventory and localai.resolveTarget physical-selection records; presence does not establish engine support or observed execution. The active host and its registered services remain the authority for method availability"
4196
4224
  },
4197
4225
  {
4198
4226
  "id": "core-contracts:CoreError",
@@ -169,6 +169,10 @@ process between requests, and joins cleanup during disposal.
169
169
  Optional `prepare({signal,onEvent})` returns resolved runtime records before
170
170
  this service starts its selected engines. The managed development owner uses
171
171
  that callback without delaying the HTTP listener or independent services.
172
+ Optional `executionDevices` supplies the shared execution-device catalog to
173
+ the ONNX owner. Development and generated native composition share one catalog
174
+ per Core lifetime with the independent `execution-devices` service. The ONNX
175
+ owner does not dispose an injected catalog; the composing owner retains it.
172
176
  This factory accepts chat/ONNX requirements; the separate
173
177
  [`createLocalImageService()`](local-image-generation.md#core-service-and-transport)
174
178
  owns image requirements. The development and generated native composition
@@ -384,8 +388,8 @@ try {
384
388
  }
385
389
  ```
386
390
 
387
- The factory accepts `{modulePath,signal?,onEvent?}` and starts no worker until
388
- `load({id,model,sessionOptions?,executionPreference?,signal?})`. The model path is absolute for this
391
+ The factory accepts `{modulePath,signal?,onEvent?,executionDevices?}` and starts no worker until
392
+ `load({id,model,sessionOptions?,executionPreference?,executionTarget?,signal?})`. The model path is absolute for this
389
393
  direct Node API. Session creation defaults to the CPU execution provider;
390
394
  explicit native session options remain caller-owned. The returned session
391
395
  record contains `id`, `model`, `state`, `loaded`, `error`, input/output names and
@@ -400,7 +404,7 @@ A recoverable run error keeps `stopping:false` and the loaded session usable.
400
404
  rejected load or inference. Unload joins worker exit and output delivery even
401
405
  when requesting termination fails; complete cleanup errors remain observable.
402
406
 
403
- Select `executionPreference: 'gpu'` to try the installed runtime's advertised
407
+ With `executionTarget` omitted, select `executionPreference: 'gpu'` to try the installed runtime's advertised
404
408
  CUDA, TensorRT, DirectML, CoreML and WebGPU providers in that order, followed by
405
409
  CPU when no accelerator session can be created. Discovery runs once per load;
406
410
  each advertised candidate receives one actual session-creation attempt with CPU
@@ -413,18 +417,106 @@ DirectML attempts default `enableMemPattern` to `false` and `executionMode` to
413
417
  `'sequential'` as required by that provider; explicit caller values remain
414
418
  unchanged, including on the final CPU attempt.
415
419
 
416
- Automatic GPU selection adds an `execution` record to the loaded session and
417
- its ordinary state events: `preference`, complete `supportedBackends`,
420
+ Every loaded session includes an `execution` record in its ordinary state
421
+ events: `preference`, `requestedTarget`, `resolvedDevice`, `resolution`,
422
+ `reason`, `configuredTarget`, `observedTarget`, complete `supportedBackends`,
418
423
  `selectedProviders`, ordered `attempts` with their full errors, `fallback`, and
419
- `discoveryError`. Each attempt records `executionProviders`, `status`
420
- (`configured` or `failed`), and `error`. `fallback: true` means the SDK created
424
+ `discoveryError`. Each attempt records provider names in `executionProviders`,
425
+ its requested `configuredTarget`, `status` (`configured` or `failed`), and
426
+ `error`. The accepted top-level `configuredTarget` is
427
+ `{source,executionProviders}`; it retains the complete provider options,
428
+ including native numeric `deviceId` values. Its `source` is `cpu-default`,
429
+ `session-options`, `gpu-preference`, `execution-target`, or
430
+ `automatic-physical-target`. It remains `null` until session creation succeeds.
431
+ `fallback: true` means the SDK attempted
421
432
  the final CPU session after GPU preference, including when no GPU candidate was
422
- advertised. Successful creation establishes an accepted provider configuration;
433
+ advertised; `loaded` establishes whether creation succeeded. Successful creation establishes an accepted provider configuration;
423
434
  it does not establish that a GPU executed any graph nodes. If every attempt
424
435
  fails, the load error retains the complete attempt errors and `execution`
425
436
  record. Inference failures retain their existing behavior and do not replay
426
437
  inference on another provider.
427
438
 
439
+ ### Physical execution targets
440
+
441
+ `executionTarget: {deviceId}` selects a physical identifier returned by the
442
+ [execution-device catalog](execution-devices.md). The identifier is separate
443
+ from an ONNX provider's numeric device ordinal. `executionTarget: null` requests
444
+ automatic selection for this new session. Omission keeps the previous provider
445
+ selection and exact explicit `sessionOptions` behavior described above.
446
+ Generic ONNX sessions still require unloading a live ID before replacement;
447
+ passing another target cannot silently replace that running session.
448
+
449
+ The current physical routes are the catalog's whole-host CPU (`deviceId: 'cpu'`)
450
+ and GPUs with an actual `addresses.dxgiAdapterIndex` when the installed ONNX
451
+ binding advertises DirectML. DirectML receives
452
+ `{name: 'dml', deviceId: dxgiAdapterIndex}` and retains CPU participation for
453
+ unsupported graph operators. The index comes from DXGI enumeration joined to
454
+ the physical device; a display order, DXCore order or CUDA ordinal is not
455
+ substituted. No CPU socket/core affinity, physical CUDA mapping or NPU provider
456
+ support is inferred from inventory.
457
+
458
+ An explicit physical target gets one configuration attempt. Missing hardware,
459
+ unsupported provider mapping and contradictory explicit session options report
460
+ `LOCAL_AI_EXECUTION_TARGET_UNAVAILABLE`,
461
+ `LOCAL_AI_EXECUTION_TARGET_UNSUPPORTED`, or
462
+ `LOCAL_AI_EXECUTION_TARGET_CONFLICT`, respectively. An explicit provider list
463
+ must already select the mapped target; it is not overwritten. An automatic
464
+ target conflicts with a fixed provider `deviceId` in explicit session options.
465
+ Matching DirectML options retain caller-authored values and receive only the
466
+ omitted `enableMemPattern: false` and `executionMode: 'sequential'` defaults.
467
+ Native session-creation failures preserve their original errors. The SDK does
468
+ not retry a physical target on another adapter or provider.
469
+
470
+ For `executionTarget: null` with `executionPreference: 'gpu'` and no explicit
471
+ provider list, automatic selection chooses the largest known positive
472
+ `dedicatedMemoryMiB` among present hardware GPUs with a stable ID and actual
473
+ DirectML mapping, when that provider is advertised. An integrated GPU remains
474
+ eligible when it meets those same conditions. Once selected, that adapter
475
+ gets one attempt. If no such candidate is established, the existing ordered
476
+ provider attempts remain available and report their unresolved physical
477
+ identity honestly. Omitted targets keep their existing selection behavior.
478
+
479
+ ```js
480
+ await onnx.load({
481
+ id: 'cheese-radar',
482
+ model: absoluteModelPath,
483
+ executionPreference: 'gpu',
484
+ executionTarget: null
485
+ });
486
+ ```
487
+
488
+ `requestedTarget` preserves the requested physical record, or `null` for no
489
+ explicit physical choice. `resolvedDevice` is the matched catalog record or
490
+ `null`; `resolution` describes physical resolution, not engine compatibility.
491
+ During initial physical discovery it is `null`; the resolver then supplies
492
+ `matched`, `automatic`, `unavailable` or `unsupported`. `reason` explains the
493
+ selection or inability. `observedTarget` remains `null`: ONNX Runtime Node
494
+ exposes no actual physical placement observation here, including after a
495
+ successful run. Accepted GPU configuration does not establish that every node
496
+ ran on that GPU.
497
+
498
+ The runtime creates a lazy catalog only when `executionDevices` is omitted and
499
+ disposes only that owned catalog at close. Catalog preparation belongs to the
500
+ loading session, follows its cancellation and is joined during release. It
501
+ does not serialize independent sessions or delay browser rendering.
502
+
503
+ ### What Settings can know before loading
504
+
505
+ `localai.devices` and `localai.resolveTarget` belong to the independent
506
+ `execution-devices` service and do not load a model or wait for model-runtime
507
+ preparation. They establish detected hardware and physical identity only.
508
+ Settings can show those records and retain an application-owned preference
509
+ keyed by provider/model. A matched physical ID is not a confirmed selectable
510
+ target for every engine.
511
+
512
+ The current ONNX binding's compiled-provider discovery runs in the session
513
+ worker at an explicit load. There is no model-free ONNX capability query in
514
+ this interface. Before that observation, engine compatibility is unknown;
515
+ do not label every enumerated GPU or NPU supported. An advertised provider
516
+ still does not establish usable provider libraries, model compatibility or
517
+ actual execution. Consume the explicit load's configuration or complete
518
+ failure, and keep observed placement unknown when it is not supplied.
519
+
428
520
  `run({id,feeds,fetches?,runOptions?,signal?})` returns the complete output-name
429
521
  map of `{type,dims,data}` tensors. Input records use that same shape with native
430
522
  typed data or string arrays. Optional `fetches` selects output names or supplies
@@ -456,7 +548,7 @@ Other sessions retain their own lifetimes.
456
548
  | Core method | Parameters | Result |
457
549
  | --- | --- | --- |
458
550
  | `onnx.status` | `{}` | Runtime availability and current `models` session records |
459
- | `onnx.load` | `{id,model,sessionOptions?}` | Loaded session; relative model paths resolve from `appRoot` |
551
+ | `onnx.load` | `{id,model,sessionOptions?,executionPreference?,executionTarget?}` | Loaded session; relative model paths resolve from `appRoot` |
460
552
  | `onnx.run` | `{id,feeds,fetches?,runOptions?}` | Complete encoded tensor output map |
461
553
  | `onnx.unload` | `{id}` | Released session record |
462
554
 
@@ -477,7 +569,9 @@ onnx.close();
477
569
 
478
570
  The accessor exposes `load`, `run`, `unload`, `inspect`, `current`, `subscribe`
479
571
  and `close`. Request methods accept `signal` and optional `timeoutMs` (default
480
- `0`, no elapsed-time cutoff). It preserves full typed tensor values, including
572
+ `0`, no elapsed-time cutoff). Load forwards `executionPreference` and preserves
573
+ the native load's distinction between omitted and null `executionTarget`.
574
+ Tensor transport preserves full typed tensor values, including
481
575
  64-bit integers, floating-point special values and strings, through the
482
576
  transport codec. `current()` adds `busy` and `closed` to its latest Core state.
483
577
  Closing the accessor cancels its requests and subscriptions; model unloading