@finalai/agents 0.10.0 → 0.12.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/HOSTING.md +334 -245
- package/README.md +214 -29
- package/dist/modules/agent-client/agent-input.d.mts +24 -0
- package/dist/modules/agent-client/agent-input.mjs +46 -0
- package/dist/modules/agent-client/agent-input.mjs.map +7 -0
- package/dist/modules/agent-client/channels.d.mts +52 -3
- package/dist/modules/agent-client/channels.mjs +71 -10
- package/dist/modules/agent-client/channels.mjs.map +2 -2
- package/dist/modules/agent-client/client.d.mts +85 -20
- package/dist/modules/agent-client/client.mjs +74 -21
- package/dist/modules/agent-client/client.mjs.map +2 -2
- package/dist/modules/agent-client/config-options.mjs +1 -2
- package/dist/modules/agent-client/config-options.mjs.map +2 -2
- package/dist/modules/agent-client/conversation-resources.d.mts +85 -0
- package/dist/modules/agent-client/conversation-resources.mjs +150 -0
- package/dist/modules/agent-client/conversation-resources.mjs.map +7 -0
- package/dist/modules/agent-client/conversations.d.mts +116 -38
- package/dist/modules/agent-client/conversations.mjs +379 -71
- package/dist/modules/agent-client/conversations.mjs.map +2 -2
- package/dist/modules/agent-client/defaults.d.mts +8 -0
- package/dist/modules/agent-client/defaults.mjs +77 -0
- package/dist/modules/agent-client/defaults.mjs.map +7 -0
- package/dist/modules/agent-client/errors.d.mts +22 -2
- package/dist/modules/agent-client/errors.mjs +19 -0
- package/dist/modules/agent-client/errors.mjs.map +2 -2
- package/dist/modules/agent-client/events.d.mts +62 -0
- package/dist/modules/agent-client/events.mjs +107 -0
- package/dist/modules/agent-client/events.mjs.map +7 -0
- package/dist/modules/agent-client/harnesses.d.mts +66 -0
- package/dist/modules/agent-client/harnesses.mjs +156 -0
- package/dist/modules/agent-client/harnesses.mjs.map +7 -0
- package/dist/modules/agent-client/http.d.mts +2 -1
- package/dist/modules/agent-client/http.mjs +21 -2
- package/dist/modules/agent-client/http.mjs.map +2 -2
- package/dist/modules/agent-client/index.d.mts +21 -6
- package/dist/modules/agent-client/index.mjs +26 -0
- package/dist/modules/agent-client/index.mjs.map +2 -2
- package/dist/modules/agent-client/integrations.d.mts +40 -0
- package/dist/modules/agent-client/integrations.mjs +92 -0
- package/dist/modules/agent-client/integrations.mjs.map +7 -0
- package/dist/modules/agent-client/local-host-protocol.d.mts +156 -0
- package/dist/modules/agent-client/local-host-protocol.mjs +258 -0
- package/dist/modules/agent-client/local-host-protocol.mjs.map +7 -0
- package/dist/modules/agent-client/local-host.d.mts +56 -0
- package/dist/modules/agent-client/local-host.mjs +200 -0
- package/dist/modules/agent-client/local-host.mjs.map +7 -0
- package/dist/modules/agent-client/oauth-clients.d.mts +36 -0
- package/dist/modules/agent-client/oauth-clients.mjs +67 -0
- package/dist/modules/agent-client/oauth-clients.mjs.map +7 -0
- package/dist/modules/agent-client/project-agents.d.mts +54 -0
- package/dist/modules/agent-client/project-agents.mjs +111 -0
- package/dist/modules/agent-client/project-agents.mjs.map +7 -0
- package/dist/modules/agent-client/project.d.mts +43 -0
- package/dist/modules/agent-client/project.mjs +66 -0
- package/dist/modules/agent-client/project.mjs.map +7 -0
- package/dist/modules/agent-client/projects.d.mts +61 -0
- package/dist/modules/agent-client/projects.mjs +145 -0
- package/dist/modules/agent-client/projects.mjs.map +7 -0
- package/dist/modules/agent-client/provide.d.mts +41 -0
- package/dist/modules/agent-client/provide.mjs +90 -0
- package/dist/modules/agent-client/provide.mjs.map +7 -0
- package/dist/modules/agent-client/publishers.d.mts +57 -0
- package/dist/modules/agent-client/publishers.mjs +109 -0
- package/dist/modules/agent-client/publishers.mjs.map +7 -0
- package/dist/modules/agent-client/requirements.d.mts +60 -0
- package/dist/modules/agent-client/requirements.mjs +83 -0
- package/dist/modules/agent-client/requirements.mjs.map +7 -0
- package/dist/modules/agent-client/sandbox-providers.d.mts +34 -0
- package/dist/modules/agent-client/sandbox-providers.mjs +75 -0
- package/dist/modules/agent-client/sandbox-providers.mjs.map +7 -0
- package/dist/modules/agent-client/sandbox-uri.d.mts +1 -1
- package/dist/modules/agent-client/sandbox-uri.mjs +4 -4
- package/dist/modules/agent-client/sandbox-uri.mjs.map +1 -1
- package/dist/modules/agent-client/sandboxes.d.mts +65 -0
- package/dist/modules/agent-client/sandboxes.mjs +130 -0
- package/dist/modules/agent-client/sandboxes.mjs.map +7 -0
- package/dist/modules/agent-client/schedules.d.mts +24 -0
- package/dist/modules/agent-client/schedules.mjs +56 -0
- package/dist/modules/agent-client/schedules.mjs.map +7 -0
- package/dist/modules/agent-client/sessions.d.mts +46 -8
- package/dist/modules/agent-client/sessions.mjs +91 -29
- package/dist/modules/agent-client/sessions.mjs.map +2 -2
- package/dist/modules/agent-client/start.d.mts +101 -0
- package/dist/modules/agent-client/start.mjs +260 -0
- package/dist/modules/agent-client/start.mjs.map +7 -0
- package/dist/modules/agent-client/test-fakes.d.mts +1 -0
- package/dist/modules/agent-client/test-fakes.mjs +3 -1
- package/dist/modules/agent-client/test-fakes.mjs.map +2 -2
- package/dist/modules/cli/agent-module.d.mts +39 -0
- package/dist/modules/cli/agent-module.mjs +89 -0
- package/dist/modules/cli/agent-module.mjs.map +7 -0
- package/dist/modules/cli/cli.d.mts +6 -6
- package/dist/modules/cli/cli.mjs +94 -120
- package/dist/modules/cli/cli.mjs.map +2 -2
- package/dist/modules/cli/conformance.d.mts +2 -6
- package/dist/modules/cli/conformance.mjs +5 -8
- package/dist/modules/cli/conformance.mjs.map +2 -2
- package/dist/modules/cli/main.mjs.map +1 -1
- package/dist/modules/cli/output.d.mts +0 -9
- package/dist/modules/cli/output.mjs +7 -85
- package/dist/modules/cli/output.mjs.map +2 -2
- package/dist/modules/harnesses.mjs.map +2 -2
- package/dist/modules/index.d.mts +0 -2
- package/dist/modules/index.mjs +0 -4
- package/dist/modules/index.mjs.map +2 -2
- package/dist/modules/integrations.d.mts +24 -0
- package/dist/modules/integrations.mjs +30 -0
- package/dist/modules/integrations.mjs.map +7 -0
- package/dist/modules/public.d.mts +10 -10
- package/dist/modules/public.mjs +29 -78
- package/dist/modules/public.mjs.map +2 -2
- package/dist/modules/sandboxes.d.mts +2 -0
- package/dist/modules/sandboxes.mjs +4 -0
- package/dist/modules/sandboxes.mjs.map +2 -2
- package/dist/modules/verify.d.mts +1 -0
- package/dist/modules/verify.mjs +2 -0
- package/dist/modules/verify.mjs.map +7 -0
- package/dist/modules/webhooks.d.mts +37 -0
- package/dist/modules/webhooks.mjs +100 -0
- package/dist/modules/webhooks.mjs.map +7 -0
- package/examples/release-scout/README.md +18 -10
- package/examples/release-scout/agent.ts +41 -0
- package/examples/release-scout/by-name.ts +43 -0
- package/examples/release-scout/db.ts +6 -0
- package/examples/release-scout/instructions.md +5 -0
- package/examples/release-scout/run-on-a-computer.ts +49 -0
- package/examples/release-scout/run.ts +38 -0
- package/package.json +42 -28
- package/dist/modules/agent-client/build.d.mts +0 -110
- package/dist/modules/agent-client/build.mjs +0 -213
- package/dist/modules/agent-client/build.mjs.map +0 -7
- package/dist/modules/authoring/index.d.mts +0 -1
- package/dist/modules/authoring/index.mjs +0 -2
- package/dist/modules/authoring/index.mjs.map +0 -7
- package/dist/modules/authoring/instructions.d.mts +0 -1
- package/dist/modules/authoring/instructions.mjs +0 -2
- package/dist/modules/authoring/instructions.mjs.map +0 -7
- package/dist/modules/authoring/providers.d.mts +0 -2
- package/dist/modules/authoring/providers.mjs +0 -1
- package/dist/modules/authoring/providers.mjs.map +0 -7
- package/dist/modules/cli/build.d.mts +0 -22
- package/dist/modules/cli/build.mjs +0 -77
- package/dist/modules/cli/build.mjs.map +0 -7
- package/dist/modules/cli/channel-install.d.mts +0 -12
- package/dist/modules/cli/channel-install.mjs +0 -25
- package/dist/modules/cli/channel-install.mjs.map +0 -7
- package/dist/modules/cli/channel-socket.d.mts +0 -9
- package/dist/modules/cli/channel-socket.mjs +0 -114
- package/dist/modules/cli/channel-socket.mjs.map +0 -7
- package/dist/modules/cli/dev.d.mts +0 -26
- package/dist/modules/cli/dev.mjs +0 -311
- package/dist/modules/cli/dev.mjs.map +0 -7
- package/dist/modules/cli/files.d.mts +0 -18
- package/dist/modules/cli/files.mjs +0 -66
- package/dist/modules/cli/files.mjs.map +0 -7
- package/dist/modules/cli/harness-descriptions.d.mts +0 -20
- package/dist/modules/cli/harness-descriptions.mjs +0 -70
- package/dist/modules/cli/harness-descriptions.mjs.map +0 -7
- package/dist/modules/cli/publish.d.mts +0 -15
- package/dist/modules/cli/publish.mjs +0 -92
- package/dist/modules/cli/publish.mjs.map +0 -7
- package/dist/modules/compile.d.mts +0 -3
- package/dist/modules/compile.mjs +0 -58
- package/dist/modules/compile.mjs.map +0 -7
- package/dist/modules/index.browser.d.mts +0 -4
- package/dist/modules/index.browser.mjs +0 -8
- package/dist/modules/index.browser.mjs.map +0 -7
- package/examples/custom-harness/README.md +0 -16
- package/examples/custom-harness/agent.ts +0 -154
- package/examples/custom-sandbox/README.md +0 -12
- package/examples/custom-sandbox/agent.ts +0 -14
- package/examples/custom-sandbox/sandbox.ts +0 -24
- package/examples/release-scout/agent/agent.ts +0 -22
- package/examples/release-scout/agent/channels/github-signature.ts +0 -27
- package/examples/release-scout/agent/channels/github.ts +0 -32
- package/examples/release-scout/agent/connections/linear.ts +0 -9
- package/examples/release-scout/agent/hooks/audit.ts +0 -24
- package/examples/release-scout/agent/instructions/org-context.ts +0 -26
- package/examples/release-scout/agent/instructions.md +0 -2
- package/examples/release-scout/agent/sandbox.ts +0 -20
- package/examples/release-scout/agent/schedules/daily-digest.md +0 -7
- package/examples/release-scout/agent/skills/triage/SKILL.md +0 -8
- package/examples/release-scout/agent/skills/triage/checklist.md +0 -3
- package/examples/release-scout/agent/tools/lookup_release_notes.ts +0 -24
- package/examples/release-scout/client.ts +0 -122
package/HOSTING.md
CHANGED
|
@@ -1,274 +1,363 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hosting agents: requirements, provide documents and local hosts
|
|
2
2
|
|
|
3
|
-
An agent
|
|
4
|
-
|
|
5
|
-
|
|
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://<
|
|
11
|
-
export FINAL_API_KEY=<
|
|
12
|
-
|
|
13
|
-
pnpm exec final-agents
|
|
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
|
-
|
|
17
|
-
`
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`
|
|
54
|
-
|
|
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
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
`
|
|
216
|
-
|
|
217
|
-
`
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
`
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
When
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
1.
|
|
252
|
-
|
|
253
|
-
2.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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.
|