@zackbart/connecta 0.10.5 → 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.
- package/AGENTS.md +8 -6
- package/CHANGELOG.md +150 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +0 -7
- package/dist/activity.d.ts +11 -1
- package/dist/activity.d.ts.map +1 -1
- package/dist/activity.js +44 -3
- package/dist/activity.js.map +1 -1
- package/dist/catalog-service.d.ts +24 -0
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +68 -9
- package/dist/catalog-service.js.map +1 -1
- package/dist/connectors/api.d.ts +2 -2
- package/dist/connectors/remote-mcp.d.ts +1 -1
- package/dist/errors.d.ts +49 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +68 -1
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +73 -3
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +161 -29
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +28 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -37
- package/dist/index.js.map +1 -1
- package/dist/invocation.d.ts +9 -2
- package/dist/invocation.d.ts.map +1 -1
- package/dist/invocation.js +61 -31
- package/dist/invocation.js.map +1 -1
- package/dist/meta-tools.d.ts +24 -59
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +107 -359
- package/dist/meta-tools.js.map +1 -1
- package/dist/operator-ui/generated.d.ts +1 -1
- package/dist/operator-ui/generated.d.ts.map +1 -1
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/generated.js.map +1 -1
- package/dist/registry.d.ts +12 -10
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +8 -17
- package/dist/registry.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +19 -21
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/shared.d.ts +9 -11
- package/dist/routes/shared.d.ts.map +1 -1
- package/dist/routes/shared.js.map +1 -1
- package/dist/server.js +5 -4
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +8 -18
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +13 -60
- package/dist/skills.js.map +1 -1
- package/dist/types.d.ts +6 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/code-first-exploration.md +16 -16
- package/documentation/code-mode.md +137 -63
- package/documentation/connectors.md +1 -1
- package/documentation/meta-tools.md +96 -33
- package/documentation/rich-output-design.md +212 -0
- package/ethos.md +17 -19
- package/examples/node/README.md +1 -2
- package/examples/node/src/index.ts +1 -3
- package/examples/worker/README.md +19 -16
- package/examples/worker/src/d1-activity-row.ts +40 -0
- package/examples/worker/src/d1-activity.ts +3 -2
- package/examples/worker/src/index.ts +6 -14
- package/examples/worker/wrangler.jsonc +3 -6
- package/package.json +1 -1
- package/src/activity.ts +69 -3
- package/src/catalog-service.ts +113 -20
- package/src/connectors/api.ts +2 -2
- package/src/connectors/remote-mcp.ts +1 -1
- package/src/errors.ts +104 -3
- package/src/execute.ts +237 -37
- package/src/index.ts +60 -67
- package/src/invocation.ts +61 -19
- package/src/meta-tools.ts +136 -482
- package/src/operator-ui/browser.ts +10 -2
- package/src/operator-ui/generated.ts +1 -1
- package/src/registry.ts +7 -35
- package/src/routes/mcp.ts +19 -21
- package/src/routes/shared.ts +8 -11
- package/src/server.ts +7 -7
- package/src/skills.ts +11 -74
- package/src/types.ts +6 -21
- package/src/version.ts +1 -1
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +1 -1
|
@@ -38,7 +38,8 @@ The recommendation is to evolve Connecta toward:
|
|
|
38
38
|
and result retrieval;
|
|
39
39
|
- policy, credentials, egress, and auditing enforced below generated code; and
|
|
40
40
|
- the current tool surface retained as a compatibility path and evaluation
|
|
41
|
-
control while the new path matures
|
|
41
|
+
control while the new path matures — a retention that ended with #273, which
|
|
42
|
+
made the executor mandatory and deleted the classic surface outright.
|
|
42
43
|
|
|
43
44
|
This was an exploratory spike, not a production implementation. The next agent
|
|
44
45
|
should use these findings as a design brief rather than porting the prototype
|
|
@@ -195,7 +196,10 @@ It did not establish:
|
|
|
195
196
|
complexity.
|
|
196
197
|
|
|
197
198
|
The existing surface should therefore remain available during the transition
|
|
198
|
-
as a compatibility mode, rollback path, and experimental control.
|
|
199
|
+
as a compatibility mode, rollback path, and experimental control. It was, and
|
|
200
|
+
then it was not: #224 made code-first the default and #273 removed the second
|
|
201
|
+
shape entirely. The open questions above outlived the control arm rather than
|
|
202
|
+
being settled by it.
|
|
199
203
|
|
|
200
204
|
## Recommended product shape
|
|
201
205
|
|
|
@@ -222,11 +226,10 @@ Turn the exploratory scenarios into a repeatable evaluation suite before
|
|
|
222
226
|
changing the default surface. Run at least 20 independent samples per task and
|
|
223
227
|
model, with prompt variation.
|
|
224
228
|
|
|
225
|
-
That suite
|
|
226
|
-
[`eval/code-first-gate`](../eval/code-first-gate/README.md)
|
|
227
|
-
as
|
|
228
|
-
the
|
|
229
|
-
flipping anything.
|
|
229
|
+
That suite was built under
|
|
230
|
+
[`eval/code-first-gate`](../eval/code-first-gate/README.md). Its recorded results
|
|
231
|
+
remain as measurement history; the runnable comparison was retired when #273
|
|
232
|
+
removed the alternate deployment shapes.
|
|
230
233
|
|
|
231
234
|
Capture:
|
|
232
235
|
|
|
@@ -254,16 +257,13 @@ catalogs, centralized invocation, admission control, and lazy connector
|
|
|
254
257
|
capabilities. Build the new surface on those rather than recreating parallel
|
|
255
258
|
logic in the executor.
|
|
256
259
|
|
|
257
|
-
### Phase 3:
|
|
260
|
+
### Phase 3: consolidate the surface
|
|
258
261
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
code-first the executor-backed default. Avoid adding new top-level read tools
|
|
265
|
-
unless a measured case cannot be expressed safely or clearly through the
|
|
266
|
-
programmable surface.
|
|
262
|
+
This phase is complete: code-first became the default in #224, then #273 made
|
|
263
|
+
the executor mandatory and removed the deployment choice. Consequential writes
|
|
264
|
+
remain outside the sandbox. New top-level read tools still require a measured
|
|
265
|
+
case that cannot be expressed safely or clearly through the programmable
|
|
266
|
+
surface.
|
|
267
267
|
|
|
268
268
|
### Phase 4: stabilize successful workflows
|
|
269
269
|
|
|
@@ -20,19 +20,11 @@ are stable and cited by the tests in [Verification](#verification).
|
|
|
20
20
|
|
|
21
21
|
## Deploy-time capability
|
|
22
22
|
|
|
23
|
-
The `executor` passed to `createConnecta()` is
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
it is the nine base meta-tools, whose `list_connectors`, `describe_tools`, and
|
|
29
|
-
`batch_call` are what seven folds into `connecta.search`, `connecta.describe`, and
|
|
30
|
-
`connecta.batch`.
|
|
31
|
-
|
|
32
|
-
No feature flag, and no code tool advertised before it can be honored.
|
|
33
|
-
`surface: "classic"` beside an executor is the one override (ten tools, the eval
|
|
34
|
-
gate's incremental arm); `surface: "code-first"` without one throws at
|
|
35
|
-
construction rather than advertise an absent program surface.
|
|
23
|
+
The `executor` passed to `createConnecta()` is required. `tools/list` is exactly
|
|
24
|
+
seven — `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`,
|
|
25
|
+
`authorize_connector`, `get_result`, and `skills`. Construction fails when the
|
|
26
|
+
executor is missing, and the removed `surface` option is rejected rather than
|
|
27
|
+
ignored ([#273](https://github.com/zackbart/connecta/issues/273)).
|
|
36
28
|
|
|
37
29
|
On Node, install the optional `quickjs-emscripten` peer and use the package's
|
|
38
30
|
QuickJS subpath:
|
|
@@ -51,25 +43,20 @@ const connecta = createConnecta({
|
|
|
51
43
|
CPU, wall-time, memory, stack, queue, result, log, and IPC bounds are configured
|
|
52
44
|
on the executor. Server bundlers must keep the `@zackbart/connecta/quickjs`
|
|
53
45
|
package files external so the child entry stays on disk. The
|
|
54
|
-
[Node example](../examples/node/README.md)
|
|
55
|
-
for the nine-tool compatibility deployment.
|
|
46
|
+
[Node example](../examples/node/README.md) carries the complete setup.
|
|
56
47
|
|
|
57
|
-
On Cloudflare Workers, the Worker Loader binding
|
|
58
|
-
the configuration switch:
|
|
48
|
+
On Cloudflare Workers, the Worker Loader binding provides the required sandbox:
|
|
59
49
|
|
|
60
50
|
```ts
|
|
61
51
|
createConnecta({
|
|
62
|
-
|
|
63
|
-
? { executor: new DynamicWorkerExecutor({ loader: env.LOADER }) }
|
|
64
|
-
: {}),
|
|
52
|
+
executor: new DynamicWorkerExecutor({ loader: env.LOADER }),
|
|
65
53
|
// connectors, auth, storage…
|
|
66
54
|
});
|
|
67
55
|
```
|
|
68
56
|
|
|
69
|
-
|
|
70
|
-
represented as optional in the deployment's `Env` type. The
|
|
57
|
+
Dynamic Workers require the Workers Paid plan. The
|
|
71
58
|
[Worker example](../examples/worker/README.md#code-mode) carries the complete
|
|
72
|
-
binding and package setup.
|
|
59
|
+
required binding and package setup.
|
|
73
60
|
|
|
74
61
|
## What an executor must implement
|
|
75
62
|
|
|
@@ -98,7 +85,8 @@ Connecta passes exactly one provider, named `connecta`. An executor must:
|
|
|
98
85
|
|
|
99
86
|
1. **Expose each provider as a guest global** whose properties are its `fns`,
|
|
100
87
|
called with the program's arguments and awaited. Connecta's provider carries
|
|
101
|
-
`search`, `describe`, `call`, `batch`, and `__callNamespace` — see
|
|
88
|
+
`search`, `describe`, `call`, `batch`, `emit`, and `__callNamespace` — see
|
|
89
|
+
point 3.
|
|
102
90
|
2. **Evaluate `prelude` after the provider globals exist and before the
|
|
103
91
|
program**, in a scope where those globals are reachable. It is host-authored
|
|
104
92
|
trusted code, never model input, and skipping it is not an option: connecta's
|
|
@@ -127,6 +115,10 @@ Optionally implement `AdmittingExecutor` (`acquire()` returning a lease whose
|
|
|
127
115
|
`execute` runs once) for bounded admission (`L7`) and `close()` for shutdown;
|
|
128
116
|
connecta wraps a plain `Executor` with `withExecutorAdmission` otherwise.
|
|
129
117
|
|
|
118
|
+
Note what is *not* on this list: [emitted output](#emitted-output) asks
|
|
119
|
+
nothing of an executor — `connecta.emit` is just another provider function
|
|
120
|
+
(`M8`).
|
|
121
|
+
|
|
130
122
|
## The program
|
|
131
123
|
|
|
132
124
|
**P1.** A program is one JavaScript `async` arrow-function expression. It is
|
|
@@ -300,13 +292,22 @@ const outcomes = await connecta.batch([
|
|
|
300
292
|
order. A success is `{ address, ok: true, data }`. A failure is
|
|
301
293
|
`{ address, ok: false, error, errorDetails }`, where `error` is the message and
|
|
302
294
|
`errorDetails` is the typed object described in [Errors](#errors) — the same two
|
|
303
|
-
field names
|
|
304
|
-
than ten calls throws.
|
|
295
|
+
field names the host's internal batch path uses. One failing call never rejects
|
|
296
|
+
the batch, and more than ten calls throws.
|
|
305
297
|
|
|
306
298
|
**S8.** `connecta.batch` is the classification channel: because a thrown host
|
|
307
299
|
error crosses the bridge as a bare message (`E1`), a batch of one is the supported
|
|
308
300
|
way for a program to *decide* something about a failure rather than report it.
|
|
309
301
|
|
|
302
|
+
### connecta.emit
|
|
303
|
+
|
|
304
|
+
```js
|
|
305
|
+
await connecta.emit({ type: "image", data: shot.data, mimeType: "image/png" });
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
The rich-output channel, delivered after the JSON envelope on success. Its
|
|
309
|
+
clauses are [Emitted output](#emitted-output) (`M1`–`M10`).
|
|
310
|
+
|
|
310
311
|
## Errors
|
|
311
312
|
|
|
312
313
|
**E1.** There are four error channels, and only two of them are typed.
|
|
@@ -354,11 +355,12 @@ program may do about it.
|
|
|
354
355
|
sentence. A program cannot recover credentials — only an operator can — so the
|
|
355
356
|
right move is to stop and let the failure reach the model.
|
|
356
357
|
|
|
357
|
-
**E4.**
|
|
358
|
-
|
|
359
|
-
`
|
|
360
|
-
|
|
361
|
-
|
|
358
|
+
**E4.** An unannotated, write-capable, or destructive tool stays refused with
|
|
359
|
+
`destructive_tool_requires_approval`; `nextAction` carries its canonical address
|
|
360
|
+
to `call_destructive_tool`, plus the original arguments when they fit the
|
|
361
|
+
512-byte echo budget — whole or not at all, since a clipped copy is a different
|
|
362
|
+
call. The model's short `reason` for the human reviewer grants no authority,
|
|
363
|
+
never goes downstream, and generated code cannot mint the capability.
|
|
362
364
|
|
|
363
365
|
**E5.** Failures of the *execution*, not of a call, never appear inside the
|
|
364
366
|
guest: admission rejection (`executor_overloaded`, retryable, with
|
|
@@ -377,11 +379,9 @@ exactly first, by containment second — so a program that *wraps* a failure's
|
|
|
377
379
|
message in its own text still reports the underlying typed failure. Keeping the
|
|
378
380
|
type beats keeping the prose.
|
|
379
381
|
|
|
380
|
-
**E7.** `retryable` for `unknown_address`, `unknown_tool`,
|
|
381
|
-
`ambiguous_tool_alias`, and `destructive_tool_requires_approval` is pinned false,
|
|
382
|
-
never inferred from an address containing `503`, `429`, or `temporar`.
|
|
382
|
+
**E7.** `retryable` for `unknown_address`, `unknown_tool`, `ambiguous_tool_alias`, and `destructive_tool_requires_approval` is pinned false, never inferred from an address containing `503`, `429`, or `temporar`. The first two carry `nextAction: { function: "connecta.search", arguments: { query, connector?, includeSchemas: "compact" } }` — the same scoped discovery the top-level record names, keyed to the surface the caller actually has. A program cannot call `search_tools`, so it is never told to. Both the message and the derived `query` clamp the address to 512 UTF-8 bytes with a `…` marker: the address is caller-authored and lands in the message, the query, the text content, and `structuredContent`, so an invented 50 KB one would otherwise produce a refusal orders of magnitude past the deployment's result cap. A clipped address still identifies the mistake; a short one — the common case — is exact and untagged.
|
|
383
383
|
|
|
384
|
-
**E8.** A remote MCP tool whose advertised schema rejects the call fails before provider dispatch with `invalid_args`, carrying bounded, value-free `{ path, code, expected }` findings and scoped search recovery. Unsupported schemas pass through; unrecognized provider prose remains `connector_call_failed`.
|
|
384
|
+
**E8.** A remote MCP tool whose advertised schema rejects the call fails before provider dispatch with `invalid_args`, carrying bounded, value-free `{ path, code, expected }` findings and scoped search recovery keyed `function: "connecta.search"` like every other in-program miss. Unsupported schemas pass through; unrecognized provider prose remains `connector_call_failed`.
|
|
385
385
|
|
|
386
386
|
## Results and projection
|
|
387
387
|
|
|
@@ -423,12 +423,70 @@ on data nobody asked for.
|
|
|
423
423
|
|
|
424
424
|
**R5.** `console.log`, `console.warn`, and `console.error` are captured in call order and returned as a single `logs` string, capped at 4,000 characters with a truncation marker. Logs survive failure — they ride along with the error result, which is what makes them worth writing. How a non-string argument renders is not contract (`X4`).
|
|
425
425
|
|
|
426
|
-
**R6.** Nothing else is added to a normal program result. Passing `diagnostics: true` adds one request-local, payload-free `diagnostics` block;
|
|
426
|
+
**R6.** Nothing else is added to a normal program result. Passing `diagnostics: true` adds one request-local, payload-free `diagnostics` block; a program that emitted adds `emitted: N` and its blocks (`M2`). Omitted, `false`, and emit-free are byte-for-byte the ordinary response path.
|
|
427
427
|
|
|
428
428
|
**R7.** Timing separates admission, provider setup, total executor wall time, catalog work, and connector work. Catalog and connector values are cumulative, so parallel work can exceed executor wall time. Each used operation kind (`search`, `describe`, `call`, `batch`) gets one aggregate with count, failures, duration, returned serialized bytes, and catalog/connector time; batch adds only its total child count.
|
|
429
429
|
|
|
430
430
|
**R8.** Diagnostics contain measurements and fixed operation names only: no addresses, arguments, results, code, credentials, logs, or raw errors. Result sizes are numbers, never previews. The collector exists only for the opted-in request; it is not activity, a session, or a stream.
|
|
431
431
|
|
|
432
|
+
## Emitted output
|
|
433
|
+
|
|
434
|
+
MCP-native output a return value cannot carry: base64 is not projectable, so a
|
|
435
|
+
block that survives intake uncapped (`S5`) must not die at the `R2` exit
|
|
436
|
+
guard. The argument and the refused alternatives live in the
|
|
437
|
+
[design record](./rich-output-design.md) and `ethos.md`
|
|
438
|
+
([#267](https://github.com/zackbart/connecta/issues/267),
|
|
439
|
+
[#270](https://github.com/zackbart/connecta/issues/270)).
|
|
440
|
+
|
|
441
|
+
**M1.** `connecta.emit(block)` accepts exactly one block: `{ type: "text",
|
|
442
|
+
text }` or `{ type: "image" | "audio", data /* base64 */, mimeType }`, every
|
|
443
|
+
field a string, no extra fields, no `annotations`, no `_meta`, no sugar forms.
|
|
444
|
+
An invalid block throws catchably and nothing is accepted — rejected, not
|
|
445
|
+
stripped. The refused types are pointers: a guest-minted `resource_link` URI
|
|
446
|
+
is a lure a client may dereference.
|
|
447
|
+
|
|
448
|
+
**M2.** Blocks collect on the host in emission order and are delivered only
|
|
449
|
+
with a successful result, appended to `content` after the JSON envelope, which
|
|
450
|
+
gains `emitted: N`. A program that never emits produces the byte-for-byte
|
|
451
|
+
ordinary response (`R6`). `structuredContent` stays the envelope alone —
|
|
452
|
+
emission is presentation, not a second data channel.
|
|
453
|
+
|
|
454
|
+
**M3.** Return value and emission are independent: `R2` never measures emitted
|
|
455
|
+
bytes, a truncated return does not suppress delivered blocks, and blocks do
|
|
456
|
+
not shrink the return budget.
|
|
457
|
+
|
|
458
|
+
**M4.** A failed program delivers no blocks. The error result reports
|
|
459
|
+
`emittedDiscarded: N` when N > 0 — a field on the structured envelope, a
|
|
460
|
+
trailing line on the plain-text paths — never silently.
|
|
461
|
+
|
|
462
|
+
**M5.** Two budgets (`ConnectaConfig.execute.maxEmittedBytes` /
|
|
463
|
+
`.maxEmittedBlocks`, defaults 4,000,000 serialized bytes and 32 blocks) fail
|
|
464
|
+
loudly at the `emit` call, naming the budget and the room remaining; nothing
|
|
465
|
+
is partially accepted and prior blocks stand. No `get_result` stash: the
|
|
466
|
+
program learns while it can still choose differently. The byte default is a
|
|
467
|
+
transport bound, not a context bound — emitted media reaches the model as
|
|
468
|
+
media, not base64 text.
|
|
469
|
+
|
|
470
|
+
**M6.** No provenance is claimed: every emitted block is program output,
|
|
471
|
+
trusted exactly as much as the return value. Preservation is re-emission of
|
|
472
|
+
the raw downstream block, so `S5`'s uncapped fallthrough is contract.
|
|
473
|
+
|
|
474
|
+
**M7.** `emit` spends no host-call budget (`L4`); `M5`'s bounds are its only
|
|
475
|
+
bounds.
|
|
476
|
+
|
|
477
|
+
**M8.** Emission asks nothing of an executor: `emit` is a provider function,
|
|
478
|
+
blocks cross the guest boundary once as an argument, and `ExecuteResult` is
|
|
479
|
+
unchanged — `Executor` stays assignable from `@cloudflare/codemode`'s
|
|
480
|
+
`DynamicWorkerExecutor`, and any executor that bridges provider calls gets
|
|
481
|
+
emission for free.
|
|
482
|
+
|
|
483
|
+
**M9.** Request-local and unstreamed: blocks exist only in the finished
|
|
484
|
+
response, and `emit` resolving means "accepted," never "delivered."
|
|
485
|
+
|
|
486
|
+
**M10.** Activity stays payload-free. `diagnostics: true` adds one `emitted`
|
|
487
|
+
aggregate — count and serialized bytes, numbers only (`R8`), present only
|
|
488
|
+
when something was emitted.
|
|
489
|
+
|
|
432
490
|
## Retry semantics
|
|
433
491
|
|
|
434
492
|
**Y1.** Connecta retries nothing beneath a program. `call_tool` accepts an
|
|
@@ -519,27 +577,34 @@ itself (`R2`, `R5`).
|
|
|
519
577
|
|
|
520
578
|
## Activity
|
|
521
579
|
|
|
522
|
-
**V1.** One payload-free activity event per call
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
a
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
which is what makes moving work into the sandbox an optimization, not a
|
|
529
|
-
blindfold.
|
|
580
|
+
**V1.** One payload-free activity event per attempted call, with
|
|
581
|
+
`source: "execute_code"` — every dispatched call plus every local refusal: a
|
|
582
|
+
read-only refusal, an unknown tool, an ambiguous shortcut, an unloadable
|
|
583
|
+
catalog, a missing credential, an exhausted host-call budget, an address no
|
|
584
|
+
connector owns. Ten tools called is ten events, as legible as ten `call_tool`
|
|
585
|
+
calls — which makes moving work into the sandbox an optimization, not a blindfold.
|
|
530
586
|
|
|
531
587
|
**V2.** Each event carries `connectorId`, `toolName`, `address`, `source`,
|
|
532
588
|
`outcome` (`success`, `error`, `timeout`, `cancelled`), `durationMs`,
|
|
533
|
-
`attempts`, and `errorCode` when
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
589
|
+
`attempts`, and `errorCode` when the call *failed* — plus request, actor, and
|
|
590
|
+
server identity. Typed codes derive an optional `friction`: `tool_not_found`,
|
|
591
|
+
`schema_retry`, `destructive_reroute`, or `auth_required`. The fifth class,
|
|
592
|
+
`result_too_large`, cannot reach an `execute_code` event: it belongs to a
|
|
593
|
+
`call_tool` result too large to return inline, and a program's own
|
|
594
|
+
return is refused paging by design rather than truncated into friction. There is
|
|
595
|
+
nowhere to put arguments, results, program source, or
|
|
596
|
+
raw error text; a caught failure is still recorded. `address` is
|
|
597
|
+
canonical (`A1`) where a tool resolved, otherwise the name the program used —
|
|
598
|
+
for a shortcut its sanitized alias, the honest record of what was attempted.
|
|
599
|
+
|
|
600
|
+
**V3.** A call whose connector does not exist is recorded at the address as
|
|
601
|
+
written, *provided* it split into the two fields activity keeps — one with no
|
|
602
|
+
interior dot records nothing. An invented id is the address mistake an operator
|
|
603
|
+
most needs to see. But recording it as written puts caller-authored text in
|
|
604
|
+
fields that are otherwise operator- and connector-authored, so `connectorId`
|
|
605
|
+
and `toolName` clamp at 128 UTF-8 bytes (`address` at 257) with a `…` marker:
|
|
606
|
+
payload-free *by construction* means the event has nowhere to put a payload,
|
|
607
|
+
not merely that connecta declines to.
|
|
543
608
|
|
|
544
609
|
**V4.** The execution itself emits no event. It has no address, and its one
|
|
545
610
|
distinctive artifact is the program source, which is exactly what a payload-free
|
|
@@ -614,8 +679,8 @@ entry. Programs that ran before still run.
|
|
|
614
679
|
|
|
615
680
|
- **`connecta.batch` failures gained `errorDetails`** (`S7`). They carried only a
|
|
616
681
|
message, which left a program unable to tell a policy refusal from a transient
|
|
617
|
-
failure. Additive, and it reuses
|
|
618
|
-
|
|
682
|
+
failure. Additive, and it reuses the host's internal batch field names, so a
|
|
683
|
+
program and the host describe a failed call the same way.
|
|
619
684
|
- **A policy refusal can no longer look retryable** (`E7`). Pinned in code rather
|
|
620
685
|
than read out of message text, so a connector named `svc-503` stops flipping a
|
|
621
686
|
permanent refusal to `retryable: true`. This reaches the call tools too.
|
|
@@ -632,6 +697,10 @@ The middle three were places where the contract described behavior the code did
|
|
|
632
697
|
not quite have. The code moved, because the described behavior is the one worth
|
|
633
698
|
having.
|
|
634
699
|
|
|
700
|
+
One surface was added since: [emitted output](#emitted-output) (`M1`–`M10`,
|
|
701
|
+
[#270](https://github.com/zackbart/connecta/issues/270)) — additive by
|
|
702
|
+
construction, with the byte-for-byte no-emit promise pinned by test.
|
|
703
|
+
|
|
635
704
|
## Verification
|
|
636
705
|
|
|
637
706
|
Every clause has a test. `test/guest-contract-cases.ts` holds the case table,
|
|
@@ -681,19 +750,24 @@ the upstream `Executor` shape assignable.
|
|
|
681
750
|
| `L5`, `X2` | `test/quickjs-executor.test.ts` (CPU, heap) |
|
|
682
751
|
| `L6`, `X10` | `test/quickjs-executor.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message) |
|
|
683
752
|
| `L7` | `test/execute.test.ts`, `test/executor-admission.test.ts` |
|
|
684
|
-
| `V1
|
|
685
|
-
| `
|
|
753
|
+
| `V1`–`V4` | `test/guest-api-contract.test.ts` (dispatched calls, every refusal class including an address no connector owns, the friction each derives, no event for the execution itself), `test/activity.test.ts` (the shared code → friction table, and the identity clamp) |
|
|
754
|
+
| `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (every rejected shape) |
|
|
755
|
+
| `M2`, `M3` | `test/guest-api-contract.test.ts` (delivery order, truncated return plus delivered blocks), `test/execute-emit.test.ts` (envelope, `structuredContent`, byte-for-byte no-emit path) |
|
|
756
|
+
| `M4` | `test/guest-api-contract.test.ts` (discard is visible), `test/execute-emit.test.ts` (structured and plain paths) |
|
|
757
|
+
| `M5`, `M7` | `test/execute-emit.test.ts` (both budgets fail the crossing block; host-call budget untouched) |
|
|
758
|
+
| `M6`, `M9` | verdicts; `M1`'s strict typing and `M2`'s collect-then-deliver are their enforcement |
|
|
759
|
+
| `M8` | two arms passing one case table, `test/codemode-compat.test.ts` |
|
|
760
|
+
| `M10` | `test/execute-emit.test.ts` (aggregate present, numbers only, absent when nothing emitted) |
|
|
686
761
|
| `X3` | `test/quickjs-executor.test.ts` (cancels a running child) |
|
|
687
762
|
| `X4` | `test/guest-api-contract.test.ts` (string logs only) |
|
|
688
763
|
| `X6` | `test/quickjs-executor.test.ts` (never-settling await) |
|
|
689
764
|
| `X7` | `P3`'s tests; the Workers superset is deliberately unused |
|
|
690
765
|
|
|
691
|
-
The surface itself is checked by `test/server.test.ts` (the exact seven
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
766
|
+
The surface itself is checked by `test/server.test.ts` (the exact seven-tool
|
|
767
|
+
list) and `test/code-first-surface.test.ts` (the fold's construction rules, the
|
|
768
|
+
required executor, the refusals a removed top-level tool now gets, copy, and
|
|
769
|
+
measured size). There is one shape left to audit, so there is one audit:
|
|
695
770
|
|
|
696
771
|
```sh
|
|
697
772
|
npm --prefix eval/current-version run audit
|
|
698
|
-
npm --prefix eval/current-version run audit -- --executor disabled
|
|
699
773
|
```
|
|
@@ -33,7 +33,7 @@ Connecta deliberately sits between protocol generations
|
|
|
33
33
|
[#206](https://github.com/zackbart/connecta/issues/206).
|
|
34
34
|
- **Multi-round-trip results:** a downstream `input_required` result becomes a
|
|
35
35
|
non-retryable `input_required_unsupported` failure. `call_tool`, the
|
|
36
|
-
`execute_code` host bridge
|
|
36
|
+
`execute_code` host bridge and internal batch path both preserve the
|
|
37
37
|
structured code. Relaying the
|
|
38
38
|
opaque `requestState` is architecturally possible but gated until real hosts
|
|
39
39
|
and downstreams adopt it.
|
|
@@ -4,26 +4,20 @@ Connecta keeps one small tool surface in model context and resolves downstream
|
|
|
4
4
|
tools behind it. `search_tools` finds addresses, the call tools enforce safety
|
|
5
5
|
annotations, and `get_result` pages bounded results.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## The deployment surface
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
| **executor configured** | seven: `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`, `authorize_connector`, `get_result`, `skills` | `connecta.search`, `connecta.describe`, `connecta.batch` inside a program |
|
|
15
|
-
| **no executor** | nine: the above minus `execute_code`, plus `list_connectors`, `describe_tools`, `batch_call` | those three top-level tools |
|
|
9
|
+
Every deployment requires an executor and `tools/list` is exactly seven:
|
|
10
|
+
`execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`,
|
|
11
|
+
`authorize_connector`, `get_result`, and `skills`. Discovery breadth and
|
|
12
|
+
batching live in `connecta.search`, `connecta.describe`, and `connecta.batch`
|
|
13
|
+
inside a program ([#273](https://github.com/zackbart/connecta/issues/273)).
|
|
16
14
|
|
|
17
15
|
Code-first is what a model sees. Four overlapping ways to reach one connector
|
|
18
16
|
became two: `search_tools` then `call_tool` for a single cold read — measurably
|
|
19
17
|
cheaper direct than through a program — and `execute_code` for everything wider.
|
|
20
|
-
The
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
work. Note which baseline that is: the executor-free nine serialize to 7,207B,
|
|
24
|
-
so the seven-tool surface is *larger* than the row below it in that table. It
|
|
25
|
-
buys the program with those bytes. The [guest API contract](./code-mode.md) is
|
|
26
|
-
what a program is promised.
|
|
18
|
+
The consolidation removed overlapping routing choices while preserving the
|
|
19
|
+
cheaper direct path for one cold call. The [guest API contract](./code-mode.md)
|
|
20
|
+
is what a program is promised.
|
|
27
21
|
|
|
28
22
|
`execute_code` accepts optional `diagnostics: true` when a caller is measuring
|
|
29
23
|
a workflow. It adds only compact request-local timing and serialized-size
|
|
@@ -31,20 +25,11 @@ aggregates; normal calls carry no diagnostics block or response-context cost.
|
|
|
31
25
|
The measurements never contain program source, arguments, values, addresses,
|
|
32
26
|
credentials, logs, or raw error text.
|
|
33
27
|
|
|
34
|
-
Classic is the compatibility surface: what an executor-free deployment
|
|
35
|
-
necessarily serves, since the program surface the fold depends on is not there.
|
|
36
|
-
It is supported and tested, not an equal citizen in the docs. `surface:
|
|
37
|
-
"classic"` beside an executor is the only override; it produces the ten-tool
|
|
38
|
-
shape the [eval gate](../eval/code-first-gate/README.md)'s *incremental* arm
|
|
39
|
-
measures. That gate's control arm is executor-free classic and needs no
|
|
40
|
-
override.
|
|
41
|
-
|
|
42
28
|
Nothing became unreachable. `connecta.describe` takes the same addresses and
|
|
43
|
-
formats as
|
|
44
|
-
read-only calls
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
of it, and that is an operator concern: the operator pages and `/health` own it.
|
|
29
|
+
formats as the internal catalog service, `connecta.batch` runs 1–10 parallel
|
|
30
|
+
read-only calls and returns typed outcomes, and an unfiltered
|
|
31
|
+
`connecta.search({})` browses every catalog a program can reach. Live connector
|
|
32
|
+
probing is an operator concern: the operator pages and `/health` own it.
|
|
48
33
|
|
|
49
34
|
## Discovery context
|
|
50
35
|
|
|
@@ -61,8 +46,8 @@ shape. Bounded plain-object schemas also expose `inputKeys`,
|
|
|
61
46
|
list rather than repeating a large partial inventory. Matches carry declared
|
|
62
47
|
behavior annotations. When
|
|
63
48
|
that shape is sufficient, call the returned address directly. Reserve schema
|
|
64
|
-
expansion
|
|
65
|
-
|
|
49
|
+
expansion through `connecta.describe` for a search without schemas, an
|
|
50
|
+
ambiguous compact shape, or exact
|
|
66
51
|
constraints that require `format: "json"`.
|
|
67
52
|
|
|
68
53
|
Compact search is deliberately a routing view, not a second copy of connector
|
|
@@ -89,6 +74,14 @@ mode; they are not a duplicated Connecta object result. Newly stashed JSON and
|
|
|
89
74
|
downstream content envelopes use compact serialization, so `get_result` byte
|
|
90
75
|
offsets and totals refer to that exact compact text.
|
|
91
76
|
|
|
77
|
+
A `call_tool` truncation notice carries both the historical `resultId` and an
|
|
78
|
+
exact `nextAction: { tool: "get_result", arguments: { id, offset: 0 } }`. The
|
|
79
|
+
handle is therefore
|
|
80
|
+
directly actionable without copying an identifier out of prose; re-calling with
|
|
81
|
+
`fields` remains the smaller alternative when projection is possible. Program results and oversized discovery responses carry no such
|
|
82
|
+
route — paging a program's return value is a refused shape, because a program
|
|
83
|
+
can shrink anything before it returns.
|
|
84
|
+
|
|
92
85
|
`fields` keeps its historical flat `{ "<path>": value }` result when every
|
|
93
86
|
requested dot-path resolves. Dot notation traverses objects; append `[]` to an
|
|
94
87
|
array field before continuing, as in `results[].id`. An exact downstream
|
|
@@ -172,6 +165,74 @@ only through the same-origin, Clerk-operator credential route. After OAuth
|
|
|
172
165
|
consent or an operator update, retry the original operation; a static update is
|
|
173
166
|
read from the vault on the next call and needs no redeploy.
|
|
174
167
|
|
|
168
|
+
## Routing recovery
|
|
169
|
+
|
|
170
|
+
Predictable local refusals carry structured recovery on both result modes.
|
|
171
|
+
An unknown connector suggests an unscoped discovery query derived from the
|
|
172
|
+
attempted tool name; an unknown tool scopes the same query to the connector that
|
|
173
|
+
answered. The suggested route follows the route the caller took: `tool:
|
|
174
|
+
"search_tools"` for a top-level call, `function: "connecta.search"` with the same
|
|
175
|
+
arguments when the miss happened inside `execute_code`, which has no way to call
|
|
176
|
+
a tool. A read path that reaches an unannotated, write-capable, or destructive
|
|
177
|
+
tool returns `nextAction` for `call_destructive_tool` with the canonical
|
|
178
|
+
address. Nothing is executed by these records.
|
|
179
|
+
|
|
180
|
+
That route echoes the caller's own arguments back only while they fit a
|
|
181
|
+
512-byte budget, and then whole — never clipped. An error envelope is not
|
|
182
|
+
size-guarded the way a result is, so an unbounded echo would let a large
|
|
183
|
+
argument object produce a refusal many times the deployment's result cap, on
|
|
184
|
+
both `call_tool` and calls a program routes through `connecta.call` or
|
|
185
|
+
`connecta.batch`. Over budget, `args` is absent and the `purpose` says to
|
|
186
|
+
re-send what was just sent: the agent already holds its own arguments, and half
|
|
187
|
+
of them would describe a call nobody made.
|
|
188
|
+
|
|
189
|
+
The address gets the same budget and the opposite rule: 512 bytes, clamped
|
|
190
|
+
with a trailing `…` rather than dropped. It is caller-authored too — an
|
|
191
|
+
invented one can be any length — and it reaches the error message *and* the
|
|
192
|
+
recovery query, each of which lands in both the text content and
|
|
193
|
+
`structuredContent`; unbounded, a 50 KB typo produced a 200 KB refusal under a
|
|
194
|
+
1 KB result cap. Dropping it is not an option the way dropping arguments is:
|
|
195
|
+
the address is the thing being corrected, a clipped one still identifies the
|
|
196
|
+
mistake, and a short one — every real one — comes back exact and untagged.
|
|
197
|
+
|
|
198
|
+
Shortcut ambiguity inside `execute_code` returns every colliding canonical
|
|
199
|
+
address and points at `connecta.call`; the program or model must still choose
|
|
200
|
+
which one matches the user's intent. `call_destructive_tool` accepts an optional
|
|
201
|
+
`reason` of at most 500 characters for the host's human approval view. It is
|
|
202
|
+
outer-call context only: Connecta neither treats it as authority nor passes it
|
|
203
|
+
to the downstream connector, and an empty or whitespace-only one is read as no
|
|
204
|
+
reason rather than as a reason to refuse the call.
|
|
205
|
+
|
|
206
|
+
Activity carries an optional coarse `friction` class: `tool_not_found`,
|
|
207
|
+
`schema_retry`, `destructive_reroute`, `auth_required`, or `result_too_large`.
|
|
208
|
+
It is derived from the typed error code, except on the one call that has no
|
|
209
|
+
error code to derive from: a result too large to return inline is friction for
|
|
210
|
+
the agent while remaining `outcome: "success"`. That applies to a `call_tool`
|
|
211
|
+
result, the only source of `result_too_large` friction. (Activity stored by
|
|
212
|
+
older releases may still carry the retired `batch_call` source; nothing writes
|
|
213
|
+
it today.)
|
|
214
|
+
An oversized *discovery* response and an oversized program return are shaped
|
|
215
|
+
differently and produce none, and an `errorCode` is written only when the call
|
|
216
|
+
actually failed. The category adds no arguments, results, search text,
|
|
217
|
+
generated code, credentials, or raw errors.
|
|
218
|
+
|
|
219
|
+
An address whose connector does not exist is recorded too, as written, provided
|
|
220
|
+
it has the `<connectorId>.<toolName>` shape at all — a string that never split
|
|
221
|
+
into the two fields activity keeps still records nothing. A hallucinated
|
|
222
|
+
connector id is the most common address mistake, and an operator reading
|
|
223
|
+
activity should see it; addresses are already a first-class activity field, so
|
|
224
|
+
nothing new is retained.
|
|
225
|
+
|
|
226
|
+
What *is* new is that those fields now hold caller-authored text, so the
|
|
227
|
+
recording seam clamps them: `connectorId` and `toolName` at 128 UTF-8 bytes
|
|
228
|
+
each, `address` at 257, with a `…` marker. Far past any real id or tool name,
|
|
229
|
+
and far short of a 40 KB invented one. The clamp is structural rather than a
|
|
230
|
+
policy the writer applies, because "payload-free by construction" has to mean
|
|
231
|
+
the event type has nowhere to put a payload — a 40 KB connector id is a payload
|
|
232
|
+
wearing an id's clothing. Clamped rather than skipped: the invented id is
|
|
233
|
+
exactly what an operator needs to see, and its first 128 bytes say as much
|
|
234
|
+
about the mistake as all 40,000 would.
|
|
235
|
+
|
|
175
236
|
## Argument recovery
|
|
176
237
|
|
|
177
238
|
A remote MCP tool's advertised `inputSchema` is checked in the shared
|
|
@@ -183,9 +244,11 @@ JSON Pointer `path`, schema-keyword `code`, and expected shape. Submitted
|
|
|
183
244
|
values are never copied into those findings.
|
|
184
245
|
|
|
185
246
|
At most three findings are returned; `validation.truncated` says when more
|
|
186
|
-
exist. `nextAction` points to
|
|
187
|
-
|
|
188
|
-
|
|
247
|
+
exist. `nextAction` points to discovery scoped to the same connector and tool
|
|
248
|
+
name when the compact schema is needed — routed like any other miss, so a
|
|
249
|
+
program is sent to `connecta.search` and a top-level call to `search_tools` —
|
|
250
|
+
while `retry` says to correct the listed arguments and reissue the original
|
|
251
|
+
operation. A schema the local
|
|
189
252
|
validator cannot evaluate passes through to the provider. Provider error prose
|
|
190
253
|
is not parsed or guessed, so an unknown format remains
|
|
191
254
|
`connector_call_failed`.
|