@finalai/final-agent-provider 0.10.0 → 0.11.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.
Files changed (161) hide show
  1. package/HOSTING.md +334 -245
  2. package/README.md +126 -40
  3. package/WORKSPACE-EGRESS.md +11 -11
  4. package/dist/modules/catalog.d.mts +9 -0
  5. package/dist/modules/catalog.mjs +21 -0
  6. package/dist/modules/catalog.mjs.map +7 -0
  7. package/dist/modules/channels/projection.mjs +15 -8
  8. package/dist/modules/channels/projection.mjs.map +2 -2
  9. package/dist/modules/harnesses/acp-agent.d.mts +4 -0
  10. package/dist/modules/harnesses/acp-agent.mjs +35 -0
  11. package/dist/modules/harnesses/acp-agent.mjs.map +7 -0
  12. package/dist/modules/harnesses/adapters.d.mts +7 -0
  13. package/dist/modules/harnesses/adapters.mjs +74 -0
  14. package/dist/modules/harnesses/adapters.mjs.map +7 -0
  15. package/dist/modules/harnesses/claude-code.d.mts +14 -0
  16. package/dist/modules/harnesses/claude-code.mjs +79 -0
  17. package/dist/modules/harnesses/claude-code.mjs.map +7 -0
  18. package/dist/modules/harnesses/codex.d.mts +12 -0
  19. package/dist/modules/harnesses/codex.mjs +90 -0
  20. package/dist/modules/harnesses/codex.mjs.map +7 -0
  21. package/dist/modules/harnesses/config-fields.d.mts +98 -57
  22. package/dist/modules/harnesses/config-fields.mjs +58 -35
  23. package/dist/modules/harnesses/config-fields.mjs.map +3 -3
  24. package/dist/modules/harnesses/definitions.d.mts +3 -0
  25. package/dist/modules/harnesses/definitions.mjs +21 -0
  26. package/dist/modules/harnesses/definitions.mjs.map +7 -0
  27. package/dist/modules/harnesses/fidelity.d.mts +1 -1
  28. package/dist/modules/harnesses/fidelity.mjs +19 -16
  29. package/dist/modules/harnesses/fidelity.mjs.map +2 -2
  30. package/dist/modules/harnesses/final.d.mts +12 -0
  31. package/dist/modules/harnesses/final.mjs +70 -0
  32. package/dist/modules/harnesses/final.mjs.map +7 -0
  33. package/dist/modules/harnesses/index.d.mts +13 -12
  34. package/dist/modules/harnesses/index.mjs +86 -45
  35. package/dist/modules/harnesses/index.mjs.map +3 -3
  36. package/dist/modules/harnesses/names.d.mts +43 -0
  37. package/dist/modules/harnesses/names.mjs +59 -0
  38. package/dist/modules/harnesses/names.mjs.map +7 -0
  39. package/dist/modules/harnesses/nanocode-claude.d.mts +18 -0
  40. package/dist/modules/harnesses/nanocode-claude.mjs +101 -0
  41. package/dist/modules/harnesses/nanocode-claude.mjs.map +7 -0
  42. package/dist/modules/harnesses/nanocode-codex.d.mts +13 -0
  43. package/dist/modules/harnesses/nanocode-codex.mjs +64 -0
  44. package/dist/modules/harnesses/nanocode-codex.mjs.map +7 -0
  45. package/dist/modules/harnesses/pi.d.mts +12 -0
  46. package/dist/modules/harnesses/pi.mjs +71 -0
  47. package/dist/modules/harnesses/pi.mjs.map +7 -0
  48. package/dist/modules/integrations.d.mts +9 -0
  49. package/dist/modules/integrations.mjs +29 -0
  50. package/dist/modules/integrations.mjs.map +7 -0
  51. package/dist/modules/provider/general.bundle.d.mts +1 -0
  52. package/dist/modules/provider/general.bundle.mjs +5 -0
  53. package/dist/modules/provider/general.bundle.mjs.map +7 -0
  54. package/dist/modules/provider/general.d.mts +11 -0
  55. package/dist/modules/provider/general.mjs +130 -0
  56. package/dist/modules/provider/general.mjs.map +7 -0
  57. package/dist/modules/provider/index.d.mts +3 -0
  58. package/dist/modules/provider/index.mjs +13 -0
  59. package/dist/modules/provider/index.mjs.map +7 -0
  60. package/dist/modules/sandboxes/access.d.mts +34 -0
  61. package/dist/modules/sandboxes/access.mjs +15 -0
  62. package/dist/modules/sandboxes/access.mjs.map +7 -0
  63. package/dist/modules/sandboxes/computer.d.mts +24 -51
  64. package/dist/modules/sandboxes/computer.mjs +523 -888
  65. package/dist/modules/sandboxes/computer.mjs.map +3 -3
  66. package/dist/modules/sandboxes/computers-api.d.mts +3 -2
  67. package/dist/modules/sandboxes/computers-api.mjs +2 -4
  68. package/dist/modules/sandboxes/computers-api.mjs.map +2 -2
  69. package/dist/modules/sandboxes/device-api.d.mts +4 -3
  70. package/dist/modules/sandboxes/device-api.mjs +2 -4
  71. package/dist/modules/sandboxes/device-api.mjs.map +2 -2
  72. package/dist/modules/sandboxes/device.d.mts +6 -6
  73. package/dist/modules/sandboxes/device.mjs +126 -121
  74. package/dist/modules/sandboxes/device.mjs.map +3 -3
  75. package/dist/modules/sandboxes/egress.d.mts +10 -9
  76. package/dist/modules/sandboxes/egress.mjs +73 -56
  77. package/dist/modules/sandboxes/egress.mjs.map +2 -2
  78. package/dist/modules/sandboxes/environment.d.mts +35 -269
  79. package/dist/modules/sandboxes/environment.mjs +27 -104
  80. package/dist/modules/sandboxes/environment.mjs.map +3 -3
  81. package/dist/modules/sandboxes/index.d.mts +9 -69
  82. package/dist/modules/sandboxes/index.mjs +35 -95
  83. package/dist/modules/sandboxes/index.mjs.map +3 -3
  84. package/dist/modules/sandboxes/local.d.mts +28 -0
  85. package/dist/modules/sandboxes/local.mjs +499 -0
  86. package/dist/modules/sandboxes/local.mjs.map +7 -0
  87. package/dist/modules/sandboxes/session.d.mts +0 -3
  88. package/dist/modules/sandboxes/session.mjs +2 -22
  89. package/dist/modules/sandboxes/session.mjs.map +2 -2
  90. package/dist/modules/sandboxes/testing/fake-machine.mjs +0 -28
  91. package/dist/modules/sandboxes/testing/fake-machine.mjs.map +2 -2
  92. package/package.json +76 -104
  93. package/dist/modules/connections.d.mts +0 -11
  94. package/dist/modules/connections.mjs +0 -16
  95. package/dist/modules/connections.mjs.map +0 -7
  96. package/dist/modules/harnesses/acp-describe.d.mts +0 -20
  97. package/dist/modules/harnesses/acp-describe.mjs +0 -110
  98. package/dist/modules/harnesses/acp-describe.mjs.map +0 -7
  99. package/dist/modules/harnesses/local-stdio.d.mts +0 -47
  100. package/dist/modules/harnesses/local-stdio.mjs +0 -41
  101. package/dist/modules/harnesses/local-stdio.mjs.map +0 -7
  102. package/dist/modules/harnesses/remote-acp.d.mts +0 -76
  103. package/dist/modules/harnesses/remote-acp.mjs +0 -83
  104. package/dist/modules/harnesses/remote-acp.mjs.map +0 -7
  105. package/dist/modules/harnesses/remote-claude-code.d.mts +0 -88
  106. package/dist/modules/harnesses/remote-claude-code.mjs +0 -113
  107. package/dist/modules/harnesses/remote-claude-code.mjs.map +0 -7
  108. package/dist/modules/harnesses/remote-client-tls.d.mts +0 -35
  109. package/dist/modules/harnesses/remote-client-tls.mjs +0 -37
  110. package/dist/modules/harnesses/remote-client-tls.mjs.map +0 -7
  111. package/dist/modules/harnesses/remote-codex.d.mts +0 -98
  112. package/dist/modules/harnesses/remote-codex.mjs +0 -128
  113. package/dist/modules/harnesses/remote-codex.mjs.map +0 -7
  114. package/dist/modules/harnesses/remote-pi.d.mts +0 -78
  115. package/dist/modules/harnesses/remote-pi.mjs +0 -104
  116. package/dist/modules/harnesses/remote-pi.mjs.map +0 -7
  117. package/dist/modules/harnesses/remote-url.d.mts +0 -5
  118. package/dist/modules/harnesses/remote-url.mjs +0 -19
  119. package/dist/modules/harnesses/remote-url.mjs.map +0 -7
  120. package/dist/modules/harnesses/serverless-final.d.mts +0 -63
  121. package/dist/modules/harnesses/serverless-final.mjs +0 -88
  122. package/dist/modules/harnesses/serverless-final.mjs.map +0 -7
  123. package/dist/modules/harnesses/serverless-nanocode-claude.d.mts +0 -64
  124. package/dist/modules/harnesses/serverless-nanocode-claude.mjs +0 -105
  125. package/dist/modules/harnesses/serverless-nanocode-claude.mjs.map +0 -7
  126. package/dist/modules/harnesses/serverless-nanocode-codex.d.mts +0 -66
  127. package/dist/modules/harnesses/serverless-nanocode-codex.mjs +0 -113
  128. package/dist/modules/harnesses/serverless-nanocode-codex.mjs.map +0 -7
  129. package/dist/modules/harnesses/serverless-pi.d.mts +0 -64
  130. package/dist/modules/harnesses/serverless-pi.mjs +0 -98
  131. package/dist/modules/harnesses/serverless-pi.mjs.map +0 -7
  132. package/dist/modules/runtime/index.d.mts +0 -7
  133. package/dist/modules/runtime/index.mjs +0 -18
  134. package/dist/modules/runtime/index.mjs.map +0 -7
  135. package/dist/modules/runtime/local-stdio.d.mts +0 -10
  136. package/dist/modules/runtime/local-stdio.mjs +0 -106
  137. package/dist/modules/runtime/local-stdio.mjs.map +0 -7
  138. package/dist/modules/runtime/remote-acp.d.mts +0 -16
  139. package/dist/modules/runtime/remote-acp.mjs +0 -152
  140. package/dist/modules/runtime/remote-acp.mjs.map +0 -7
  141. package/dist/modules/runtime/remote-claude-code.d.mts +0 -5
  142. package/dist/modules/runtime/remote-claude-code.mjs +0 -47
  143. package/dist/modules/runtime/remote-claude-code.mjs.map +0 -7
  144. package/dist/modules/runtime/remote-codex.d.mts +0 -5
  145. package/dist/modules/runtime/remote-codex.mjs +0 -47
  146. package/dist/modules/runtime/remote-codex.mjs.map +0 -7
  147. package/dist/modules/runtime/remote-host.d.mts +0 -127
  148. package/dist/modules/runtime/remote-host.mjs +0 -387
  149. package/dist/modules/runtime/remote-host.mjs.map +0 -7
  150. package/dist/modules/runtime/remote-pi.d.mts +0 -5
  151. package/dist/modules/runtime/remote-pi.mjs +0 -44
  152. package/dist/modules/runtime/remote-pi.mjs.map +0 -7
  153. package/dist/modules/sandboxes/guide.d.mts +0 -35
  154. package/dist/modules/sandboxes/guide.mjs +0 -119
  155. package/dist/modules/sandboxes/guide.mjs.map +0 -7
  156. package/dist/modules/sandboxes/workspace-setup.d.mts +0 -42
  157. package/dist/modules/sandboxes/workspace-setup.mjs +0 -242
  158. package/dist/modules/sandboxes/workspace-setup.mjs.map +0 -7
  159. package/dist/modules/testing/harness.d.mts +0 -9
  160. package/dist/modules/testing/harness.mjs +0 -123
  161. package/dist/modules/testing/harness.mjs.map +0 -7
package/HOSTING.md CHANGED
@@ -1,274 +1,363 @@
1
- # Portable agent definitions and hosted bindings
1
+ # Hosting agents: requirements, provide documents and local hosts
2
2
 
3
- An agent declares behavior and logical resources. Hosting supplies endpoints,
4
- resource identifiers, and credential references. The same compiled source can
5
- resolve against dev and production configuration without source edits.
3
+ An agent is a document (data) that states what it requires: a harness within a
4
+ permission ceiling, compute by capability, and named integration slots. Whoever
5
+ starts work provides implementations in a plain `provide` object. This guide is
6
+ for hosts that implement the agent platform API these packages call: what the
7
+ SDK sends, what it expects back, and the connection a client dials to lend its
8
+ own process to an agent.
6
9
 
7
10
  ## Selecting a deployment
8
11
 
9
12
  ```sh
10
- export FINAL_API_HOST=https://<dev-agents-api-host>/api
11
- export FINAL_API_KEY=<dev-api-key>
12
- pnpm exec final-agents build ./agent
13
- pnpm exec final-agents publish ./agent --agent my-agent
13
+ export FINAL_API_HOST=https://<agents-api-host>
14
+ export FINAL_API_KEY=<api-key>
15
+ export FINAL_PROJECT_ID=<project-id>
16
+ pnpm exec final-agents print ./agent.ts # the document, as JSON; calls nothing
17
+ pnpm exec final-agents publish ./agent.ts --name release-scout
14
18
  ```
15
19
 
16
- Choose production by changing the host and API key. `--host` overrides
17
- `FINAL_API_HOST`; without either, the existing production default remains. Build
18
- compiles locally and optionally checks installed-harness compatibility. Publish
19
- compiles again and sends source/release requests to the selected deployment.
20
- Neither rewrites authored URLs. A GitHub-backed definition uses remote source;
21
- push edits before explicitly publishing a ref. Caller API keys never become agent
22
- credentials merely because they were used to publish.
23
-
24
- ## Hosted model resolution (SDK 0.2.0)
25
-
26
- Hosted Final, Pi, Codex and Claude configurations accept string model references.
27
- `serverless*ConfigSchema.parse(...).model` can be undefined: the host must choose
28
- its catalog default, resolve aliases and validate availability before dispatch.
29
- The host owns current candidates, runtime capabilities and dated prices, and
30
- advertises picker choices through its harness descriptions. Compilation remains
31
- offline and checks reference syntax.
32
-
33
- SDK 0.2.0 removes `@finalai/agents/models` and
34
- `@finalai/final-agent-provider/harnesses/models`, including their model arrays,
35
- defaults, model-ID unions and pricing helpers. Migrate callers to string model
36
- references and host-owned discovery, metadata and pricing.
37
-
38
- Publish the SDK release before upgrading host dependencies. Ship the host's
39
- catalog/default implementation together with its SDK dependency upgrade; a host
40
- that only changes the dependency will lose the old default and catalog APIs.
41
-
42
- ## Hosted connections
43
-
44
- ```ts
45
- import { defineHostedConnection } from '@finalai/agents';
46
-
47
- export default defineHostedConnection({
48
- binding: 'issue-tracker',
49
- tools: { allow: ['read_issue'], deny: ['delete_issue'] },
50
- });
51
- ```
52
-
53
- `issue-tracker` is an example configuration name, not a built-in service.
54
- Hosting configures it with non-secret JSON:
20
+ `--host` overrides `FINAL_API_HOST`; `--project` overrides `FINAL_PROJECT_ID`.
21
+ `createClient({ host })` selects a deployment the same way. Nothing in an agent
22
+ names a deployment, a server URL or a credential, so the same document runs
23
+ against any host.
24
+
25
+ ## Agents are documents
26
+
27
+ An agent value (`defineAgent`) becomes its document with `documentOf(agent)`:
28
+ identity, instructions, skills, `require`, workspace seed files, each tool's
29
+ name and schema, hooks, tool policy, secret names and schedules. The agent's
30
+ identity is the content hash of the canonical document with defaults applied
31
+ (`agentDocumentHash` from `@finalai/agent-provider/document`, `sha256:<hex>`).
32
+ Hosts must hash with that function; any other canonicalization yields other
33
+ hashes.
34
+
35
+ Each tool and hook says where it runs:
36
+
37
+ - **Serverless** code carries one self-contained ES module (`source`, `export`).
38
+ It runs on the host's serverless runtime from the document alone, whether or
39
+ not any client is connected. Hosts re-check that the source imports nothing.
40
+ - **In-process** code carries only its interface. Its body never leaves the
41
+ process that created the session; the host calls it over that process's
42
+ local host connection (below). With no such connection the tool reports
43
+ itself unavailable and the session carries on.
44
+
45
+ `@finalai/agent-provider/code-host` routes each call by placement
46
+ (`createCodeHost`); the serverless runtime contract is `ServerlessRuntimeIface`.
47
+ A serverless tool gives the model a projection apart from its structured output
48
+ by returning `{ [Symbol.for(SERVERLESS_TOOL_RESULT)]: true, output, modelOutput }`;
49
+ the runtime reads every tool return with `serverlessToolResultOf` and sends
50
+ `modelOutput` on the result line. The model sees `modelOutput` when present,
51
+ and `output` stays the structured result.
52
+
53
+ Routes, under `/api/v1/projects/:projectId/agents`:
54
+
55
+ | Route | Body / query | Response |
56
+ | ---------------- | ----------------------------------- | -------------------------------------- |
57
+ | `PUT /documents` | `{ document, hash? }` | `{ hash }`; refuse a hash that differs |
58
+ | `POST /publish` | `{ name, hash }` | `{ id, name, hash, publishedAt }` |
59
+ | `GET /lookup` | exactly one of `name`, `id`, `hash` | `agentInfoSchema` |
60
+ | `GET /history` | `name`, `cursor?`, `limit?` | `agentHistorySchema` |
61
+
62
+ An agent is always passed as an object, `{ hash, document? }`, never a name. A
63
+ request whose document is absent names a hash the host already stores.
64
+
65
+ ## Provide documents
55
66
 
56
67
  ```json
57
68
  {
58
- "url": "https://issues.dev.example/mcp",
59
- "auth": { "secretKey": "ISSUE_TRACKER_TOKEN", "headerName": null },
60
- "headers": { "X-Workspace": "development" }
69
+ "harness": {
70
+ "harness": "pi",
71
+ "configuration": {
72
+ "model": "gpt-6.1-sol",
73
+ "inference": [{ "source": "final", "billTo": "project" }]
74
+ }
75
+ },
76
+ "compute": { "files": "asbx_01J…", "shell": "asbx_01J…" },
77
+ "integrations": { "issues": "intg_01H…" }
61
78
  }
62
79
  ```
63
80
 
64
- Production supplies another value under the same name. The helper uses an ordinary
65
- `defineDynamic` connection at `session.started`, then the usual HTTP MCP lowering,
66
- validation, permissions, and credential broker. Hosting supplies transport settings,
67
- not tool policies. URL, authentication references, transport headers belong to hosting; the hosted helper does
68
- not accept authored overrides for these fields. Referenced secrets still need the agent's secret allowlist and normal
69
- authorization. Actual tokens must not be returned in configuration headers.
81
+ `provideDocumentSchema` (`@finalai/agent-protocol/agent-platform`) is the one
82
+ shape on every call that can start work: `sessions.create`, `conversations.create`
83
+ (per participant, plus a shared document for the conversation), direct and Home
84
+ conversations, `addParticipant`, `PUT …/participants/:pid/provide`, channel
85
+ installs (`agents.provide`) and schedules. It names only things that exist: a
86
+ sandbox or integration by id, harness settings inline. Hosts store it as given
87
+ and resolve participant, then conversation, then the install or schedule that
88
+ created the conversation, taking each top-level key whole from the nearest scope
89
+ that sets it. A conversation returns its shared document as `provide`, so a
90
+ client can run the same check before adding a participant. Nothing has a
91
+ default on the host.
92
+
93
+ `files` and `shell` must name the same sandbox. A sandbox or integration id
94
+ must belong to the session's project.
95
+
96
+ ## The requirements check
97
+
98
+ Every call that can start work runs `unmetRequirements` from
99
+ `@finalai/agent-provider/requirements-check` with what the host knows (each
100
+ sandbox's capabilities, which integrations exist, the composed harness via
101
+ `harnessCheckInputOf`, whether a client holding the in-process code is
102
+ connected). If anything is unmet, nothing is created and the response is:
70
103
 
71
- Final uses the same generic helper:
72
-
73
- ```ts
74
- import { defineFinalConnection } from '@finalai/agents';
75
-
76
- export default defineFinalConnection({
77
- tools: { deny: ['create_agent_session'] },
78
- });
104
+ ```json
105
+ {
106
+ "error": {
107
+ "code": "requirements_unmet",
108
+ "message": "…",
109
+ "details": {
110
+ "agent": "sha256:…",
111
+ "requirements": [
112
+ {
113
+ "kind": "integration",
114
+ "name": "issues",
115
+ "description": "Issue tracker",
116
+ "problem": "not_provided",
117
+ "detail": null
118
+ }
119
+ ]
120
+ }
121
+ }
122
+ }
79
123
  ```
80
124
 
81
- This resolves the `final` binding. Hosting supplies its URL and
82
- `auth: { kind: 'final-session' }`, just as it supplies authentication for any
83
- other hosted connection. `binding`
84
- selects another name. Existing explicit `url` calls remain static and compatible;
85
- they always point at that URL. There is no Final-specific compiler rewrite.
86
-
87
- ## Connection tool policy
125
+ `harness_unsuitable` has the same body for a harness change refused on its own.
126
+ The SDK runs the same function before sending, so a caller sees the same list
127
+ either way; the SDK error carries `code` and `requirements`
128
+ (`isRequirementsUnmetError`).
129
+
130
+ A running agent therefore always has what it requires: the host adds nothing to
131
+ its context about requirements. An optional requirement that was not provided is
132
+ simply absent.
133
+
134
+ ## Harnesses, ACP adapters and environments
135
+
136
+ Three independent pieces reach a model loop, and the host speaks only ACP v2:
137
+
138
+ - a **harness** definition: the agent program (it need not speak ACP);
139
+ - an **ACP adapter** definition: maps one harness interface to ACP v2, reusable
140
+ across harnesses; a harness that speaks ACP v2 needs none;
141
+ - an **environment**: a sandbox that runs them, or an ACP v2 endpoint already
142
+ running at a URL.
143
+
144
+ Harness settings name them:
145
+ `{ harness, adapter?, configuration, environment? }`. `adapter` defaults to the
146
+ harness definition's. `environment` is a sandbox id (the pair is started
147
+ there), `{ url, hop? }` (nothing is started), or absent: a harness that uses
148
+ `files` or `shell` natively then runs in the sandbox provided for `shell`; any
149
+ other harness is refused.
150
+
151
+ Definitions are data (`harnessDefinitionSchema`, `acpAdapterDefinitionSchema`
152
+ in `@finalai/agent-provider/harness-definition`), published by name and stored
153
+ by content hash. A harness definition carries its configuration's JSON Schema,
154
+ supported features, `lowestPermission`, accepted inference sources, what it uses
155
+ natively from where it runs (`native.files`, `native.shell`), its interface and
156
+ default adapter, ACP behaviour (`promptWhileRunning`, `sessionSurvivesDisconnect`,
157
+ `supports`, `mcp`, `consumes`), and `run` entries per runtime kind:
158
+
159
+ - `process`: a command, with arguments and environment derived from the
160
+ configuration by templates (`{{configuration.model}}`, groups included
161
+ `when` a value is set). An adapter's process entry wraps the harness's
162
+ (`{{harness.command}}`, `{ spread: 'harness.args' }`).
163
+ - `worker`: a program built for a worker runtime, by name.
164
+
165
+ An environment runs what its sandbox provider declares
166
+ (`SandboxEnvironmentImplementationIface.runtimes`: `process`, `worker`; absent
167
+ means `process` when it offers `shell`). A worker runtime is not a machine: it
168
+ offers the model no files or shell, so a harness running there reaches the
169
+ sandboxes provided for them over the handle RPC. `composeHarness` picks a
170
+ runtime kind the environment, the harness and the adapter all have, and
171
+ `harnessCheckInputOf` turns the result into the requirements check's input: a
172
+ harness's native capabilities are its environment's, so only that sandbox may
173
+ be provided for them. `harnessStartFor` renders what to start.
174
+
175
+ A harness provider (`HarnessProviderIface`, `@finalai/agent-provider/harness-provider`)
176
+ reaches a harness: `open` and `resume` return an ACP endpoint (`ws`, `http`, or
177
+ `sandbox`: a process or worker program the session service starts in the
178
+ environment sandbox over the handle RPC, ACP on its stdio or stream, with no
179
+ port exposed), and `park`, `close` and `health` follow its lifecycle. The
180
+ general provider starts adapter(harness) from the definitions; a definition
181
+ whose harness is reached some other way carries its own provider code
182
+ (`defineHarnessProvider`), one self-contained module run on the serverless
183
+ runtime.
184
+
185
+ Routes, under `/api/v1/projects/:projectId`:
186
+
187
+ | Route | Body or query | Response |
188
+ | ------------------------------------------ | ------------------------------------ | --------------------------------- |
189
+ | `POST /harnesses/check` | `{ agent: hash, harness, compute? }` | `harnessCheckResponseSchema` |
190
+ | `POST /harnesses/publish` | `{ name, definition, hash? }` | `definitionPublicationSchema` |
191
+ | `GET /harnesses/lookup` | `name` or `hash` | `harnessDefinitionInfoSchema` |
192
+ | `GET /harnesses` | | `harnessDefinitionListSchema` |
193
+ | `POST /acp-adapters/publish` | `{ name, definition, hash? }` | `definitionPublicationSchema` |
194
+ | `GET /acp-adapters/lookup` | `name` or `hash` | `acpAdapterDefinitionInfoSchema` |
195
+ | `GET /acp-adapters` | | `acpAdapterDefinitionListSchema` |
196
+ | `GET /subscription-allowances` | | `subscriptionAllowanceListSchema` |
197
+ | `PUT /subscription-allowances/:harness` | `{}` | `subscriptionAllowanceSchema` |
198
+ | `DELETE /subscription-allowances/:harness` | | `{ ok }` |
199
+
200
+ The check answers `{ suitable, problems, compute, environment, adapter,
201
+ runtime }`, where `compute[capability].status` is `harness` (its environment
202
+ serves it natively), `sandbox` (a provided sandbox must) or `unavailable`. A
203
+ project can name its own definitions, its ancestors' and the public catalog's.
204
+ A subscription inference source reaches a harness only when the project allows
205
+ that harness, or the public catalog does for its own definitions. The model is
206
+ a string the harness validates; the host never substitutes a default model.
207
+
208
+ ## Sandboxes and setups
209
+
210
+ Under `/api/v1/projects/:projectId`: `POST /sandboxes` (`{ provider, environment,
211
+ setup?, ownedBy?, idempotencyKey? }`; creating boots nothing), `GET` and
212
+ `DELETE /sandboxes/:id`, `GET /sandboxes/:id/description`, and
213
+ `POST /sandboxes/:id/run` (`{ command, cwd?, env?, timeoutMs? }` →
214
+ `{ exitCode, stdout, stderr }`, booting the sandbox if needed). A sandbox owned
215
+ by a conversation or session is deleted with its owner. A retried create with
216
+ the same `idempotencyKey` returns the first sandbox. Sandbox setups are
217
+ published with `POST /sandbox-setups` (`{ name, setup }`); `latest` resolves once,
218
+ when a sandbox is created.
219
+
220
+ ## Integrations and sign-in
221
+
222
+ Integrations are project resources: an MCP server and how calls to it
223
+ authenticate (`oauth` personal or shared, `secret` by project secret name,
224
+ `platform-identity`, `none`). A person's own sign-in is runtime state: on the
225
+ first call that needs it, the host raises a `sign_in` decision for that person,
226
+ holds the call, and continues it when `POST /api/v1/sign-ins/complete` succeeds.
227
+ `platform-identity` sends a short-lived signed assertion of the calling session
228
+ that any server can verify against the host's published keys; Final's gateway is
229
+ an ordinary integration with this auth.
230
+
231
+ ### Integration tool policy
232
+
233
+ A slot's `tools` (`allow`, `deny`, `load`, `allowAnnotations`,
234
+ `denyAnnotations`) and `approvals` are a ceiling over whatever integration fills
235
+ it. Keys are tool names or `*` patterns: `*` matches any run of characters,
236
+ `deny` wins over `allow`, and the most specific key governs (an exact name, else
237
+ the pattern with the most literal characters, ties toward `ask`). Hosts enforce
238
+ this with `isToolExposed`, `isToolLoaded`, `toolApprovalOf` and
239
+ `resolveToolApprovalKey` from `@finalai/agent-provider`.
240
+
241
+ An annotation selector matches when every hint it sets equals the tool's hint,
242
+ reading omitted hints as the MCP spec's defaults (not read-only, destructive, not
243
+ idempotent, open world). Annotations come from the integration's server, so
244
+ select on them only for servers trusted to classify their own tools.
245
+
246
+ With `load`, the host lists only the loaded tools and keeps every other exposed
247
+ tool reachable through a search over the integration's remaining tools and a
248
+ call that runs a found tool by its real name, with the same exposure and
249
+ approval checks as a direct call. Without `load`, the host lists every exposed
250
+ tool except tools a server labels with a connector in `_meta`
251
+ (`{ "ai.final/connector": { "namespace": "…", "name": "…" } }`) and tools past
252
+ the host's listing limit, which it searches instead. A harness that declares
253
+ `supports.deferredToolSearch` searches tools itself, so the host lists every
254
+ exposed tool to it.
255
+
256
+ ## Local hosts
257
+
258
+ A client that starts an agent can lend its own process to it: it serves the
259
+ agent's `files` and `shell` (any it was not given a sandbox for, unless created
260
+ with `localCompute: false`) and answers calls to the agent's in-process tools,
261
+ hooks and approval predicates. It does so over one WebSocket per agent it
262
+ started:
88
263
 
89
- `tools.allow`, `tools.deny` and `tools.approval` keys are tool names or `*`
90
- patterns, so a definition can scope a connection whose tool list it does not know:
91
-
92
- ```ts
93
- export default defineFinalConnection({
94
- tools: {
95
- allow: ['github.*', 'list_goals'],
96
- deny: ['github.delete_repo'],
97
- approval: { 'github.delete_*': always() },
98
- },
99
- });
264
+ ```
265
+ GET /api/v1/projects/:projectId/local-hosts (WebSocket upgrade)
266
+ Sec-WebSocket-Protocol: final.local-host.v1
267
+ Authorization: Bearer <the client's credential>
100
268
  ```
101
269
 
102
- `*` matches any run of characters; everything else matches literally. `deny` wins
103
- over `allow`. The most specific key governs a tool: an exact name, else the matching
104
- pattern with the most literal characters, with ties resolved toward `ask`. Hosts
105
- enforce this through `isToolExposed` and `toolApprovalOf` from
106
- `@finalai/agent-provider`, and look up a tool's input-approval predicate under the
107
- key `resolveToolApprovalKey` returns.
108
-
109
- ## Loaded and searchable tools
270
+ The socket carries two protocols. Binary frames are always the sandbox handle
271
+ RPC's stream chunks. A text frame belongs to the **host protocol** when it is a
272
+ JSON-RPC request or notification whose method starts with `host/` or `code/`, or
273
+ a response (or `$/cancel_request`) whose id is a string starting with `host:`.
274
+ Every other text frame is the **sandbox handle RPC**
275
+ (`@finalai/agent-provider/sandbox/wire`), in which the host is the RPC client and
276
+ the client process serves the sandbox: the host sends `initialize`,
277
+ `sandbox/readFile`, `sandbox/run` and the rest, with numeric ids, gated by a grant
278
+ of the access the agent requires on the capabilities it serves.
279
+ `splitLocalHostChannel` and `createHostPeer` from `@finalai/agents/client/local-host`
280
+ implement both sides of the split.
110
281
 
111
- A connection that can expose many tools, such as Final's with every service the
112
- person has connected, can name the ones the harness should list up front:
282
+ **`host/hello`** (client → host, first): the client's id is `host:1`.
113
283
 
114
- ```ts
115
- export default defineFinalConnection({
116
- tools: { load: ['list_goals', 'get_goal', 'draft_goal', 'notify_user'] },
117
- });
284
+ ```json
285
+ {
286
+ "jsonrpc": "2.0",
287
+ "id": "host:1",
288
+ "method": "host/hello",
289
+ "params": {
290
+ "protocolVersion": 1,
291
+ "owner": { "kind": "session", "id": "ases_…" },
292
+ "agent": "sha256:…",
293
+ "capabilities": ["files", "shell"],
294
+ "tools": ["find_order"],
295
+ "hooks": [],
296
+ "approvals": [],
297
+ "resume": "lhost_…"
298
+ }
299
+ }
118
300
  ```
119
301
 
120
- With `load`, the host lists only the loaded tools and must keep every other exposed
121
- tool reachable: it serves a search over the connection's remaining exposed tools
122
- (name, description, input schema) and a call that runs a found tool by its real
123
- name, with the same exposure and approval checks as a direct call. A host may
124
- instead add a found tool to the list and send `notifications/tools/list_changed`;
125
- harnesses that ignore that notification still need the call path. `load: []`
126
- leaves every tool to search. Entries are tool names or `*` patterns.
127
- `isToolLoaded` from `@finalai/agent-provider` applies the rule after exposure.
128
-
129
- Without `load`, the harness lists the connection's exposed tools except:
130
-
131
- - **Connector tools.** A server can label a tool with the connector it belongs to
132
- in the tool's `_meta`:
133
- `{ "ai.final/connector": { "namespace": "salesforce", "name": "Salesforce" } }`.
134
- The namespace identifies the connector (unique within the server's tools); the
135
- name is for display, and two connectors may share one. Final's server labels
136
- every connector tool with the namespace and name of the install it comes from.
137
- The host never lists labelled tools up front. Its search tool names each
138
- connector and how many tools it has, so the agent knows what is connected
139
- without any of those definitions in its context, and searches for the tools it
140
- needs. A `load` entry that names a labelled tool still lists it.
141
- - **Tools past a size limit.** When a connection exposes more unlabelled tools
142
- than the host lists directly (100 in Final's host), those are searched too.
143
-
144
- A harness that declares `supports.deferredToolSearch` defers and searches tools
145
- itself, so the host lists every exposed tool to it and serves no search.
146
-
147
- A host's search should name the connectors it covers and accept a connector's
148
- namespace to narrow to. Once a host has listed its search and call tools, it keeps answering them for
149
- that harness even if the connection stops hiding tools, unless the connection
150
- then lists its own tool by one of those names.
151
-
152
- ## Selecting tools by annotation
153
-
154
- `tools.allow` and `tools.deny` entries can be MCP annotation selectors as well as
155
- tool names, so a definition can scope tools it cannot name in advance:
156
-
157
- ```ts
158
- export default defineFinalConnection({
159
- tools: {
160
- allow: ['list_goals', 'draft_goal', { readOnlyHint: true }],
161
- deny: [{ destructiveHint: true, readOnlyHint: false }],
162
- },
163
- });
164
- ```
302
+ `owner.kind` is `session` or `conversation`; `owner.id` is absent when the
303
+ caller chose no id, and the host reserves one the client then creates with.
304
+ `resume` is a host id from an earlier connection. The result:
165
305
 
166
- A selector matches when every hint it sets equals the tool's hint, reading hints the
167
- tool omits as the MCP spec's defaults (not read-only, destructive, not idempotent,
168
- open world). The annotations come from the connection's server, so select on them
169
- only for servers trusted to classify their own tools.
170
-
171
- Selectors lower to `allowAnnotations` (each with the connection default approval and
172
- input predicate) and `denyAnnotations`. A host exposes a tool when nothing in `deny`
173
- or `denyAnnotations` matches it and either an `approvals` key (name or pattern)
174
- governs it, `approvals` is null, or `toolAnnotationRuleFor` finds a rule; a tool an
175
- `approvals` key governs keeps that approval, otherwise it runs under the rule's. `tools.approval` overrides apply only to
176
- named tools.
177
-
178
- ## Resources and provider packages
179
-
180
- `HostedBindingsIface` is the shared `@finalai/agent-provider` contract. Hosting
181
- implements `get(name)` to return scoped JSON or null. `requireHostedBinding` rejects
182
- missing hosting, absent names, and invalid JSON without an environment fallback.
183
- Names are configuration keys, not global permissions or inferred URLs.
184
-
185
- Tools, hooks, dynamic resolvers, sandbox selectors, and harness/sandbox/channel
186
- provider contexts expose an optional `bindings` reader. A third-party resolver can
187
- use the same reader for model or resource configuration:
188
-
189
- ```ts
190
- import { defineDynamic, requireHostedBinding } from '@finalai/agents';
191
-
192
- export const model = defineDynamic({
193
- requiredBindings: [{ name: 'primary-model', schema: { type: 'string', minLength: 1 } }],
194
- description: 'The deployment model',
195
- preview: 'Resolved from primary-model',
196
- events: {
197
- 'session.started': async (_event, ctx) =>
198
- requireHostedBinding({ bindings: ctx.bindings, name: 'primary-model' }),
199
- },
200
- });
306
+ ```json
307
+ {
308
+ "jsonrpc": "2.0",
309
+ "id": "host:1",
310
+ "result": {
311
+ "hostId": "lhost_…",
312
+ "owner": { "kind": "session", "id": "ases_…" },
313
+ "sandboxId": "asbx_…"
314
+ }
315
+ }
201
316
  ```
202
317
 
203
- Use that value in a harness configuration supporting dynamic models. A resource
204
- binding can similarly contain a repository, guide slug, device ID, or non-secret
205
- SDK base URL; the consumer validates the resource-specific schema. Literal IDs,
206
- custom URLs, and external credential choices are never silently rewritten.
207
-
208
- Installed providers already accept factory configuration injected by hosting.
209
- Keep supplying deployment service URLs there. Computer/WebSocket clients retain
210
- explicit configuration; a hosted provider can obtain it through this shared reader.
211
- Package versions stay pinned. Packages do not read a global DEV/PROD flag.
212
-
213
- ## Declared requirements and user experience
214
-
215
- `defineHostedConnection` declares its binding name and JSON configuration schema
216
- in the compiled dynamic slot automatically. Other dynamic consumers declare
217
- `requiredBindings` as in the model example. Hosts can inspect these requirements
218
- without running authored resolvers. Repeated names must satisfy every declared
219
- schema; names are looked up once per preflight.
220
-
221
- Before accepting a release, hosting calls
222
- `validateHostedBindings({ manifest, bindings })` with a reader scoped to the
223
- selected deployment, organization, and agent. Hosts compiling source can instead
224
- pass `hostedBindings` to `compile`; failed preflight returns error diagnostics and
225
- no manifest or bundle. An offline build can still compile and expose requirements
226
- without accessing deployment configuration. The CLI does not yet fetch binding
227
- configuration itself: server-side publication must enable this preflight.
228
-
229
- When dev is selected, lookups use only dev configuration. A missing dev binding
230
- fails even if production has the same name; never retry against production or
231
- substitute a production default. Changing the CLI host selects where to publish;
232
- the runtime reader comes from that deployment, not from the author's environment.
233
- Explicit authored URLs retain their literal destination.
234
-
235
- Authors keep one source definition. Operators configure its named connections
236
- and resources once in each deployment. End users start the agent as usual; an
237
- unconfigured required resource produces a configuration failure instead of a
238
- session silently missing tools. Dynamic handlers validate declared requirements
239
- before running. Shared manifest resolution rejects null results for required slots,
240
- even if a host catches a resolver exception and returns null. Hosts must propagate
241
- that failure to startup. Session-started values stay pinned across turns and resume.
242
-
243
- Hosts can use the compiled requirements to show
244
- which names need configuration and return an actionable deployment-specific error.
318
+ `sandboxId` names this connection's compute in `provide.compute` (one sandbox for
319
+ both capabilities, as the requirements check demands); it is null when the
320
+ client serves no capability. The sandbox belongs to the owner and is deleted
321
+ with it.
322
+
323
+ **`code/run`** (host → client): params are one code invocation
324
+ (`codeInvocationSchema` from `@finalai/agent-provider/code-host`: `kind` tool,
325
+ hook or approval, the `name` in the document, `input` or `event` and `payload`,
326
+ and a `context` with `sessionId`, `callId`, `actingUserId` and the session's
327
+ compute endpoints for `ctx.sandbox(capability)`). A hook's payload is
328
+ `{ data, meta: { id, at, seq } }`. The result is the final line:
329
+ `{ "type": "result", "output": …, "modelOutput"?: … }` or
330
+ `{ "type": "error", "message": "…" }`; an approval answers `output: true` to
331
+ ask first. While it runs the client sends **`code/progress`** notifications
332
+ `{ "requestId": "host:7", "data": …, "label"?: "…" }`. The host cancels with
333
+ `$/cancel_request { "requestId": "host:7" }`.
334
+
335
+ **`code/secret`** (client → host): in-process code reading `ctx.secrets.get(name)`
336
+ asks with `{ "requestId": "host:7", "name": "BILLING_TOKEN" }`, where
337
+ `requestId` is the `code/run` it runs for. The host answers
338
+ `{ "value": "…" | null }` (null when the secret is not set). It refuses a name
339
+ the agent's document does not list in `secrets`, a `requestId` that is not a
340
+ `code/run` in flight on this connection, and resolves the name in the project
341
+ of the session or conversation the host serves. The client refuses unlisted
342
+ names itself before asking.
343
+
344
+ When the socket closes the session carries on. Until a connection resumes it,
345
+ the host reports this process's in-process tools as unavailable
346
+ (`CodeErrorCode.Unavailable`) and its sandbox as unreachable. The SDK's socket
347
+ does not keep a Node process alive, so a script that is done exits and its tools
348
+ become unavailable.
245
349
 
246
350
  ## Host integration
247
351
 
248
- These packages implement contracts and authored runtime behavior, not a deployment
249
- configuration store or RPC service. Existing hosting needs to:
250
-
251
- 1. Resolve names within the authorized deployment, organization, and agent; never
252
- expose the process environment or an unrestricted cross-tenant registry.
253
- 2. Inject `bindings` into lifecycle resolution and the tool/hook/selector/provider
254
- contexts using it. Dynamic approval evaluation and tool reconstruction after
255
- isolate eviction need the same reader. Readers and tokens are not persisted in
256
- boundary snapshots.
257
- 3. Configure `final` with that deployment's approved MCP endpoint. The ordinary
258
- broker continues restricting `final-session` grants to that endpoint.
259
- 4. Return non-secret configuration and credential references, with normal provider,
260
- secret, resource, and organization authorization.
261
- 5. Run the release preflight above and validate again before starting a new
262
- session. Treat failed required-binding resolution as a startup/configuration error, not
263
- an omitted connection or a fallback to production. Preserve existing runtime
264
- revisions when publication fails.
265
- 6. Retain session-started resolved values across turns/resume; resolve new sessions
266
- against their own deployment.
267
-
268
- Optional readers preserve source compatibility with existing hosts. Definitions
269
- requesting bindings fail clearly if hosting does not provide them. Installing these
270
- packages alone does not change Final's deployed service wiring or provision secrets.
271
-
272
- New compiles emit manifest version 4. Hosts read versions 2 through 4; older
273
- channel entries default to no credentials and an empty surface. Upgrade hosting
274
- before publishing newly compiled agents.
352
+ These packages define contracts and client behaviour, not a platform. A host
353
+ implementing them must:
354
+
355
+ 1. Store documents by `agentDocumentHash`, re-check self-contained serverless
356
+ source, and point names at hashes only when someone publishes.
357
+ 2. Run `unmetRequirements` on every call that starts work and at every session
358
+ open, and create nothing when it fails.
359
+ 3. Store each `provide` as given; never fill a requirement on a caller's behalf.
360
+ 4. Route each tool and hook by its placement: serverless code to its runtime with
361
+ the source from the document, in-process code to the local host that holds it.
362
+ 5. Resolve integration auth per call for the acting person, raising sign-in as
363
+ that integration's auth step.