@agentium/harness 4.0.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 (54) hide show
  1. package/README.md +488 -0
  2. package/dist/definition.d.ts +39 -0
  3. package/dist/definition.d.ts.map +1 -0
  4. package/dist/driver-BvzvqvpB.js +2049 -0
  5. package/dist/driver-Cr2BGG4h.cjs +2138 -0
  6. package/dist/drivers.d.ts +14 -0
  7. package/dist/drivers.d.ts.map +1 -0
  8. package/dist/index.cjs +1531 -0
  9. package/dist/index.d.ts +16 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +1500 -0
  12. package/dist/mcp-resources.d.ts +42 -0
  13. package/dist/mcp-resources.d.ts.map +1 -0
  14. package/dist/policies.d.ts +29 -0
  15. package/dist/policies.d.ts.map +1 -0
  16. package/dist/presets.d.ts +41 -0
  17. package/dist/presets.d.ts.map +1 -0
  18. package/dist/runtime/context-policy.d.ts +20 -0
  19. package/dist/runtime/context-policy.d.ts.map +1 -0
  20. package/dist/runtime/context.d.ts +15 -0
  21. package/dist/runtime/context.d.ts.map +1 -0
  22. package/dist/runtime/controller.d.ts +51 -0
  23. package/dist/runtime/controller.d.ts.map +1 -0
  24. package/dist/runtime/driver.d.ts +146 -0
  25. package/dist/runtime/driver.d.ts.map +1 -0
  26. package/dist/runtime/events.d.ts +83 -0
  27. package/dist/runtime/events.d.ts.map +1 -0
  28. package/dist/runtime/index.d.ts +9 -0
  29. package/dist/runtime/index.d.ts.map +1 -0
  30. package/dist/runtime/middleware.d.ts +10 -0
  31. package/dist/runtime/middleware.d.ts.map +1 -0
  32. package/dist/runtime/resolve.d.ts +20 -0
  33. package/dist/runtime/resolve.d.ts.map +1 -0
  34. package/dist/runtime/runtime-registry.d.ts +35 -0
  35. package/dist/runtime/runtime-registry.d.ts.map +1 -0
  36. package/dist/runtime/session-bindings.d.ts +60 -0
  37. package/dist/runtime/session-bindings.d.ts.map +1 -0
  38. package/dist/runtime/types.d.ts +176 -0
  39. package/dist/runtime/types.d.ts.map +1 -0
  40. package/dist/testing.cjs +67 -0
  41. package/dist/testing.d.ts +7 -0
  42. package/dist/testing.d.ts.map +1 -0
  43. package/dist/testing.js +66 -0
  44. package/dist/watch/definition.d.ts +11 -0
  45. package/dist/watch/definition.d.ts.map +1 -0
  46. package/dist/watch/gmail.d.ts +104 -0
  47. package/dist/watch/gmail.d.ts.map +1 -0
  48. package/dist/watch/quiet-hours.d.ts +10 -0
  49. package/dist/watch/quiet-hours.d.ts.map +1 -0
  50. package/dist/watch/runtime.d.ts +42 -0
  51. package/dist/watch/runtime.d.ts.map +1 -0
  52. package/dist/watch/types.d.ts +147 -0
  53. package/dist/watch/types.d.ts.map +1 -0
  54. package/package.json +68 -0
package/README.md ADDED
@@ -0,0 +1,488 @@
1
+ # @agentium/harness
2
+
3
+ A developer kit for building agent execution environments from explicit abilities, policies and drivers. `HarnessRuntime` owns composition, scoped resources, canonical conversation state, aggregate budgets, events and cleanup. Core `Agent` owns ordinary model/tool execution and consumes a neutral `ExecutionServices` port; it does not construct a harness or depend on this package.
4
+
5
+ Definitions can contribute tools, source context, prompt fragments and ordered middleware. Define, extend and inspect are synchronous and perform no filesystem scans, provider calls or client initialization. Binding and execution begin when the runtime starts a run.
6
+
7
+ ## Build and use
8
+
9
+ Install matching versions of `@agentium/core` and `@agentium/harness`, plus your chosen provider SDK. This package has no required browser, queue or provider SDK dependency. ESM and CommonJS exports expose the same API. In this workspace:
10
+
11
+ ```sh
12
+ npm run build:core
13
+ npm run build:harness
14
+ ```
15
+
16
+ Use a definition with a driver and explicit host grants:
17
+
18
+ ```ts
19
+ import { agentDriver, HarnessRuntime, research } from "@agentium/harness";
20
+
21
+ // model is the application's existing ModelProvider.
22
+ const definition = research({
23
+ text: {
24
+ id: "project-notes",
25
+ entries: [{ id: "scope", text: "The project investigates shipping delays.", uri: "notes:project" }],
26
+ },
27
+ });
28
+ const runtime = new HarnessRuntime({
29
+ definition,
30
+ driver: agentDriver({
31
+ name: "research-assistant", model,
32
+ instructions: "Answer questions using the supplied sources and cite them.",
33
+ }),
34
+ grants: { toolIds: [], modelRoles: ["main"] },
35
+ budgets: { maxModelCalls: 4, maxToolCalls: 0, maxTokens: 8000 },
36
+ });
37
+ const result = await runtime.run("What is this project investigating?", {
38
+ identity: { tenantId: verifiedTenant, userId: verifiedActor },
39
+ sessionId: "research-42",
40
+ });
41
+ ```
42
+
43
+ `research` performs no implicit web search. Text and files are labelled source data. Search and business tools come from the host, and their names must be granted before a model can use them. Identity comes from trusted application authentication, never model input.
44
+
45
+ ## Agent ownership and configuration
46
+
47
+ `agentDriver(config)` creates one run-owned Agent, applies definition defaults outside core, and reuses that instance across follow-ups and completion revisions within that run. A different run gets a different Agent. The runtime closes the owned Agent once after owned work settles; cleanup failures are diagnostic and do not replace a primary failure. Host-supplied backing stores remain borrowed and are not closed by this driver.
48
+
49
+ `agentDriver(existingAgent)` borrows an already configured Agent. The host closes it. A definition with Agent defaults or limits is rejected for a borrowed Agent: choose one configuration owner. Abilities, runtime grants, budgets and policies remain available with a borrowed Agent when its definition has no Agent defaults or limits.
50
+
51
+ Explicit Agent configuration overrides definition defaults, including `false` to disable a feature. Definition limits cap configured Agent tool roundtrips and child depth; runtime `budgets` independently bound aggregate model/tool/token use across delegates and revisions. `projectRoot` is captured by the runtime and resolves relative workspace and skill paths. Portable manifest paths remain relative for hashing.
52
+
53
+ Canonical conversations belong to the runtime's session store. The Agent adapter supplies history through the neutral execution-services port and uses ephemeral Agent session semantics, avoiding duplicate session persistence or automatic memory extraction. Explicit memory tools and standing notes are separate capabilities. Tenant/user identity on a run does not automatically namespace an arbitrary host storage client: provide tenant/actor-scoped stores and clients, and keep their shutdown with the host.
54
+
55
+ ## Explicit migration from Agent.deep
56
+
57
+ `Agent.deep()`, `AgentConfig.harness`, `harnessOptions`, Agent `replaceTools`, and the `legacyDeep` preset are removed. There is no hidden replacement preset. The old choices can be written as a normal definition, then adjusted or deleted individually:
58
+
59
+ ```ts
60
+ import { InMemoryStorage } from "@agentium/core";
61
+ import { agentDriver, defineHarness, HarnessRuntime } from "@agentium/harness";
62
+
63
+ // One approved tenant/actor's backing storage. InMemoryStorage is not durable.
64
+ // For persistence, supply your host-owned storage scoped to that tenant/actor.
65
+ const tenantStorage = new InMemoryStorage();
66
+ const definition = defineHarness({
67
+ id: "project-agent",
68
+ defaults: {
69
+ workspace: { path: ".", mode: "write" }, // Explicit host-disk write access.
70
+ skillDirs: ["skills"],
71
+ contextFiles: true,
72
+ filesystem: true, // Virtual files, not the host disk.
73
+ subagents: true,
74
+ fileMemory: true,
75
+ searchPastSessions: true,
76
+ },
77
+ limits: { toolRoundtrips: 10, maxChildDepth: 2 },
78
+ });
79
+ const runtime = new HarnessRuntime({
80
+ definition,
81
+ projectRoot: "/srv/approved-project",
82
+ driver: agentDriver({
83
+ name: "project-agent", model,
84
+ // Standing notes and virtual files reuse this explicit backing store.
85
+ // Automatic Agent session storage/extraction is disabled during controlled runs.
86
+ memory: { storage: tenantStorage, summaries: false },
87
+ }),
88
+ grants: {
89
+ toolIds: [
90
+ "fs_read_file", "fs_list_directory", "fs_file_info", "fs_write_file",
91
+ "list_skills", "get_skill_instructions", "get_skill_reference",
92
+ "agent_fs_write", "agent_fs_read", "agent_fs_list", "agent_fs_search",
93
+ "memory", "task", "search_past_sessions",
94
+ ],
95
+ modelRoles: ["main"],
96
+ },
97
+ budgets: { maxModelCalls: 12, maxToolCalls: 20, maxTokens: 16000 },
98
+ executionPolicy: hostPolicy,
99
+ });
100
+ await runtime.run("Inspect the project", {
101
+ identity: { tenantId: verifiedTenant, userId: verifiedActor },
102
+ sessionId: "project-session",
103
+ });
104
+ // Close tenantStorage only when its host scope has finished using it.
105
+ ```
106
+
107
+ Here `model`, `hostPolicy` and verified identity are application dependencies. The definition reproduces the former feature choices, not implicit permissions: remove `fs_write_file` from grants or use `mode: "read"` when writes are unnecessary. Core rejects string workspaces. Host-file confinement checks static path escapes; use OS isolation against hostile concurrent filesystem mutation.
108
+
109
+ Without explicitly shared storage, the configured Agent's virtual filesystem and file memory are fresh per run. With the scoped storage above, standing notes and virtual files survive subsequent run-owned Agent instances. `search_past_sessions` searches sessions in that Agent's configured backing storage; it does not search the harness session store. Harness canonical history remains independent. The default harness store and `InMemoryStorage` are process-local and non-durable.
110
+
111
+ Children receive the same controlled execution boundary, policy, identity and cancellation with fresh child conversation/state. They do not rebind the definition or implicitly acquire all parent Agent defaults. Delegation remains subject to the shared aggregate budgets. [deep-migration.test.ts](src/__tests__/deep-migration.test.ts) exercises the explicit choices with real Agent execution and a fake provider, including rooted skills/project context, write denial, delegation budgets, note persistence and success/failure cleanup.
112
+
113
+ ## Define custom abilities
114
+
115
+ ```ts
116
+ import { agentDriver, defineAbility, defineHarness, HarnessRuntime } from "@agentium/harness";
117
+
118
+ interface NotesService { read(query: string, userId?: string): Promise<string> }
119
+ const notes = defineAbility({
120
+ type: "example/notes",
121
+ validate: (options: { service: NotesService }) => ({ service: options.service }),
122
+ describe: () => ({ toolNames: [], requirements: ["notes:read"], runtimeDependent: true }),
123
+ bind: async ({ service }) => ({
124
+ tools: [],
125
+ contextSources: [{
126
+ id: "notes",
127
+ fetch: async (query, run, budget) => {
128
+ run.signal?.throwIfAborted();
129
+ const text = await service.read(query, run.userId);
130
+ return [{ id: "result", text, trust: "source", byteLength: Buffer.byteLength(text) }];
131
+ },
132
+ }],
133
+ middleware: [{
134
+ id: "notes-observer",
135
+ afterModel: async (_response, run) => { run.signal?.throwIfAborted(); },
136
+ }],
137
+ // Dispose resources created by this binding only; service is host-owned.
138
+ }),
139
+ });
140
+ const runtime = new HarnessRuntime({
141
+ definition: defineHarness({ id: "support", abilities: [notes({ service: notesService })] }),
142
+ driver: agentDriver({ name: "support", model }),
143
+ requirements: ["notes:read"],
144
+ grants: { toolIds: [], modelRoles: ["main"] },
145
+ });
146
+ ```
147
+
148
+ Options remain statically typed, including callbacks and services. `validate` must be pure. Plain objects/arrays are snapshotted as immutable configuration; fresh containers are supplied to bind/describe. Functions and class instances remain caller-owned references. Represent identity-sensitive SDK clients as class instances. Trusted factories are not sandboxed.
149
+
150
+ Bindings initialize sequentially once per runtime run and dispose once in reverse order, including partial initialization failure. Reusing a definition creates separate run bindings; child execution within that run shares the supplied services. Do not close caller-owned clients in `dispose`.
151
+
152
+ Source fetches receive entry/byte/token/deadline bounds and a cancellation signal; runtime retrieval applies central limits across sources. Source estimates are not authority, and retrieval data cannot become host instructions. Middleware IDs must be unique; stable `before`/`after` ordering rejects unknown references and cycles. Model transforms preserve host instructions and provider/tool continuation groups. Observers receive isolated structured-cloneable responses/results. Middleware cannot replace mandatory tool enforcement.
153
+
154
+ ## Compose, inspect and replace
155
+
156
+ ```ts
157
+ import { defineHarness, extendHarness, describeHarness, textContext } from "@agentium/harness";
158
+
159
+ const original = defineHarness({
160
+ id: "product",
161
+ abilities: [textContext({ id: "notes", entries: [] }, { instanceId: "notes" })],
162
+ });
163
+ const extended = extendHarness(original, {
164
+ id: "product-specialized", disable: ["notes"],
165
+ abilities: [customAbility({ service })],
166
+ });
167
+ console.log(describeHarness(extended));
168
+ ```
169
+
170
+ Inspection reports declarations and diagnostics without binding, discovering credentials or serializing executable options. Use explicit ability instance IDs for reusable definitions.
171
+
172
+ - Omitted defaults inherit; `false` disables. Skill-directory defaults union stably; `false` clears them.
173
+ - `disable` removes an entire ability's tools, context and middleware. `replaceAbilities` explicitly replaces an existing instance ID.
174
+ - Duplicate tool/context/middleware/prompt IDs fail. Replace the ability or host tool collection before execution; Agent has no composition replacement option.
175
+ - Requirements are explicit runtime host grants, not dependency installation. Runtime grants/policy/identity cannot be widened by definitions or model output.
176
+
177
+ ## Text and file context
178
+
179
+ `textContext({ id, entries }, { instanceId })` snapshots UTF-8 source entries and has an approved portable factory. `fileContext({ id, root, files }, { instanceId })` reads only fixed host-selected paths beneath an explicit absolute root. Reads occur during fetch; canonical path, UTF-8 and size checks apply. It grants no workspace-wide tools.
180
+
181
+ ```ts
182
+ const runtime = new HarnessRuntime({
183
+ definition: research({ files: { id: "project-files", root: "/srv/project", files: ["README.md"] } }),
184
+ driver: agentDriver({ name: "research", model }),
185
+ projectRoot: "/srv/project",
186
+ requirements: ["filesystem:read"],
187
+ grants: { toolIds: [], modelRoles: ["main"] },
188
+ });
189
+ ```
190
+
191
+ `research` disables automatic workspace tools, context-file discovery, file memory and subagents. `suppliedTools({ tools, requirements? })` wraps trusted ToolDefs without executing or freezing them. `base({ id?, abilities?, defaults? })` supplies a composition shell.
192
+
193
+ ## Portable manifests
194
+
195
+ ```ts
196
+ import { defineHarness, exportManifest, hashManifest, loadManifest, textContext } from "@agentium/harness";
197
+
198
+ const definition = defineHarness({
199
+ id: "portable-notes",
200
+ abilities: [textContext({ id: "notes", entries: [{ id: "one", text: "Source text" }] }, { instanceId: "notes" })],
201
+ });
202
+ const manifest = exportManifest(definition);
203
+ const hash = hashManifest(manifest);
204
+ const loaded = loadManifest(JSON.parse(JSON.stringify(manifest)), [textContext.factory!]);
205
+ ```
206
+
207
+ Loading requires an approved factory registry; JSON never contains executable factories. Unknown keys, incompatible versions, missing factories and non-JSON options fail. Export requires explicit harness/portable ability IDs and mappings. Local callbacks, clients, file capabilities and tools stay local unless their author supplies an approved equivalent mapping. Credentials never belong in manifests.
208
+
209
+ Custom `defineAbility` mappings use `portable: { validateOptions, toOptions, toJSON }` and expose `factory`. Hashes cover canonical validated manifest JSON, not function text, live clients or resolved absolute paths. Export Agent configuration and the harness manifest separately; core Agent serialization contains no harness runtime definitions.
210
+
211
+ ## Verification and limits
212
+
213
+ ```sh
214
+ npm run build:core
215
+ npm run build:harness
216
+ npx vitest run packages/harness/src
217
+ AGENTIUM_TEST_PACKAGES=1 npx vitest run packages/harness/src/__tests__/package-consumption.test.ts
218
+ ```
219
+
220
+ The opt-in package test installs built local tarballs into an isolated offline consumer and verifies ESM/CJS and public TypeScript usage without optional provider/browser/queue SDKs. Build first so stale output cannot stand in for source verification.
221
+
222
+ The local runtime does not provide durable recovery, distributed ownership, remote policy interception, skill sandboxing, browser/media transport or a visual editor. Those require their explicit integration or recovery contracts. Host capabilities are trusted code, not a JavaScript security boundary.
223
+
224
+ ## Execution drivers and run handles (H2)
225
+
226
+ `HarnessRuntime` owns a scoped session lease, aggregate call budgets, mandatory
227
+ execution policy, event history, completion checks, and cleanup. A driver controls
228
+ progress through `start(request, services)`. The supplied services perform model
229
+ calls and dispatch effects through the same approval and execution boundary used
230
+ by `Agent`. Driver implementations and host bindings are trusted application code;
231
+ this interface does not sandbox JavaScript or intercept arbitrary network calls.
232
+
233
+ ```ts
234
+ import { Agent, defineTool } from "@agentium/core";
235
+ import { HarnessRuntime, agentDriver } from "@agentium/harness";
236
+ import { z } from "zod";
237
+
238
+ // model is the application's existing ModelProvider.
239
+ const lookup = defineTool({
240
+ name: "lookup", description: "Read an approved record",
241
+ parameters: z.object({ id: z.string() }),
242
+ execute: async ({ id }) => records.read(id), // host-owned record service
243
+ });
244
+ const agent = new Agent({ name: "assistant", model, tools: [lookup] });
245
+ const runtime = new HarnessRuntime({
246
+ driver: agentDriver(agent, { stream: true }),
247
+ grants: { toolIds: ["lookup"], modelRoles: ["main"] },
248
+ budgets: { maxModelCalls: 8, maxToolCalls: 6, maxTokens: 8000 },
249
+ executionPolicy: {
250
+ decide: () => ({ action: "allow" }),
251
+ resolveEffect: () => "read",
252
+ },
253
+ });
254
+ const handle = runtime.start("Read record 42", {
255
+ identity: { tenantId: verifiedTenant, userId: verifiedActor },
256
+ sessionId: "conversation-42",
257
+ });
258
+ for await (const event of handle.events()) render(event);
259
+ const result = await handle.result();
260
+ // runtime.run(input, options) returns that same terminal result.
261
+ ```
262
+
263
+ A `RunHandle` reports exactly one terminal result: `completed`, `failed`,
264
+ `cancelled`, `stopped`, or `awaiting_input`. The terminal event contains that same
265
+ result and final cursor. `cancel()` records intent and propagates the signal;
266
+ settlement and resource release wait until owned work quiesces. A provider or
267
+ custom driver that ignores cancellation can delay settlement. Started external
268
+ effects are never rolled back. Closing an event iterator closes only that viewer.
269
+ Concurrent iterator reads are ordered. The default event history retains 256
270
+ events; reconnect with `events({ after: cursor })`. Older cursors receive
271
+ `HarnessEventGapError`. Large terminal outputs become scoped artifact references;
272
+ retrieve them with `runtime.getArtifact(identity, sessionId, artifactId)`.
273
+
274
+ Built-in drivers support queued `follow_up` input. Custom drivers may declare
275
+ `steer` and consume it at boundaries with `services.takeInput()`. Unsupported
276
+ controls throw `HarnessUnsupportedError`. Interrupt-and-replace, pause/resume,
277
+ remote policy coverage, and durable recovery are rejected by this local runtime.
278
+ In-memory events, artifacts, and sessions are explicitly non-durable.
279
+
280
+ Host grants are upper bounds. Controllers select only approved tools/model roles;
281
+ omitting required tools or selecting unsupported model options fails closed.
282
+ Model output limits are clamped to the remaining token allowance and reservations
283
+ prevent concurrent calls from sharing that allowance. Reported usage includes
284
+ input and provider reasoning tokens, so a provider's actual accounting may exceed
285
+ an output estimate; the runtime blocks subsequent calls when exhausted. Model
286
+ call/tool attempt limits are enforced independently. A context policy returns an
287
+ immutable request projection and provenance; canonical history stays intact.
288
+ Opaque provider continuation cannot migrate between model roles.
289
+
290
+ ```ts
291
+ const completionPolicy = {
292
+ id: "require-evidence",
293
+ evaluate: async ({ text, revision }) => text.includes("Evidence:")
294
+ ? { action: "accept", reason: "Evidence included" }
295
+ : { action: "revise", reason: "Missing evidence", instruction: "Include Evidence: and its source." },
296
+ };
297
+ // Supply completionPolicy and budgets: { maxRevisions: 1 } to HarnessRuntime.
298
+ // Revisions cannot execute tool effects unless allowRevisionEffects is explicitly true.
299
+ ```
300
+
301
+ ## Workflows, teams and custom drivers
302
+
303
+ `workflowDriver(workflow)` accepts a JSON object input patch and preserves the
304
+ Workflow constructor's other initial state. Every function step is dispatched as
305
+ `workflow:<stepName>` through normal validation, policy, approval, and budget
306
+ checks. A deterministic approval workflow can grant only `workflow:submit`, set
307
+ `executionPolicy.decide` to `ask`, and inject its host `ApprovalManager`. Its
308
+ pending requests include the verified tenant and actor. A denied step returns a
309
+ failed terminal result and does not execute the callback. Controlled function
310
+ steps do not automatically retry effects.
311
+
312
+ `teamDriver(team)` uses the existing Team algorithms. Member Agents receive the
313
+ same identity, cancellation, policy, and aggregate model/tool budgets. Remote Team
314
+ members fail explicitly because this runtime cannot intercept their effects.
315
+ Root history and each delegated Agent's complete tool/provider conversation are
316
+ retained separately in the session snapshot's `history` and `conversations`.
317
+ Failed/aborted partial transcripts are retained with `replayable: false`.
318
+
319
+ ```ts
320
+ import type { ExecutionDriver } from "@agentium/harness";
321
+ import { testDriverContract } from "@agentium/harness/testing";
322
+
323
+ const deterministic: ExecutionDriver = {
324
+ id: "example/deterministic", version: 1,
325
+ capabilities: { controls: [], durable: false, policyCoverage: "local", controlledExecution: true },
326
+ async start(request, services) {
327
+ services.append([{ role: "user", content: request.input }]);
328
+ const result = await services.dispatch({ id: "read-1", name: "lookup", arguments: { id: "42" } });
329
+ const text = result.error ?? String(result.result);
330
+ services.append([{ role: "assistant", content: text }]);
331
+ return { text };
332
+ },
333
+ };
334
+ await testDriverContract(deterministic, {
335
+ tools: [lookup], grants: { toolIds: ["lookup"], modelRoles: [] },
336
+ });
337
+ ```
338
+
339
+ The explicit testing subpath has no Vitest dependency and checks terminal/result
340
+ agreement, ordered identities, final cursor, and settled-result immutability. It
341
+ executes a caller-supplied fixture; applications must additionally test their own
342
+ driver's effects, cancellation cooperation, and control boundaries.
343
+
344
+ ## Session ownership and portable runtime references
345
+
346
+ The default store rejects a second writer to the same tenant/actor/session with
347
+ `HarnessSessionConflict`. Its declared guarantees are process-local single-writer
348
+ safety, no compare-and-swap, and no durability. Different tenants remain isolated.
349
+ Session resources acquired through `services.resource(id, "session", initialize)`
350
+ are reused until `runtime.resources.closeSession(identity, sessionId)` after all
351
+ leases release. Run resources close once in reverse acquisition order; host-owned
352
+ clients are never disposed. Cleanup diagnostics do not replace the primary result.
353
+ Applications own retention of session/artifact stores and explicit session cleanup.
354
+
355
+ Configure a definition on the runtime. Core Agent has no harness composition
356
+ option. Runtime bindings apply their tools, prompts,
357
+ context sources, and middleware to custom-driver model/effect services as well.
358
+ The Agent adapter uses `history` plus `ephemeral: true` semantics, leaving canonical
359
+ persistence to the harness store and avoiding duplicate Agent session records.
360
+ Outside a harness, `agent.run` and `agent.stream` accept `ephemeral: true` to use
361
+ externally owned history without automatic session/memory persistence.
362
+
363
+ Local `defineHarness({ runtime: { driver, controller, contextPolicy,
364
+ completionPolicy } })` accepts trusted direct implementations. Portable manifests
365
+ contain only `runtime` registry references `{ id, version }`, plus role-to-host
366
+ binding preferences. For export, provide matching `runtimeReferences` and
367
+ `runtimeRegistry` mappings. `loadManifest(manifest, abilityFactories,
368
+ runtimeRegistry)` resolves only explicitly approved implementations and fails on
369
+ missing or ambiguous references. Executable references without an export mapping
370
+ fail with their exact `runtime.<field>` path. Credentials and clients remain local.
371
+
372
+ `Agent`'s `checkpointing` option now records tool-roundtrip transcript/state
373
+ snapshots and exposes the configured `checkpointManager`. These snapshots support
374
+ inspection; deleting later snapshots does not undo effects or resume a run. Durable
375
+ execution requires the separate recovery/operation-ledger contract in Plan 004.
376
+
377
+ Controlled execution rejects ordinary Agent reflection, model-backed compression/tool-result summarization,
378
+ and exception-based handoff before starting work. Use `completionPolicy`,
379
+ `contextPolicy`, or Team/custom-driver delegation, respectively, so every model
380
+ call and revision remains subject to the runtime's budgets and effect policy.
381
+ Pure context trimming remains available. Ordinary Agent configurations retain
382
+ their existing behavior. A delegated `RunOpts.executionPolicy` is an additional
383
+ mandatory restriction and cannot relax either the Agent policy or runtime grants.
384
+
385
+ ### Budgeted reflection and summaries
386
+
387
+ Use `reflectionPolicy` and `summaryContextPolicy` for model-backed completion and
388
+ context processing in H2. Their auxiliary models are explicit host bindings:
389
+
390
+ ```ts
391
+ import { reflectionPolicy, summaryContextPolicy } from "@agentium/harness";
392
+
393
+ const runtime = new HarnessRuntime({
394
+ driver: agentDriver(agent),
395
+ grants: { toolIds: [], modelRoles: ["main", "critic", "summary"] },
396
+ models: {
397
+ critic: { provider: criticModel, options: ["maxTokens"] },
398
+ summary: { provider: summaryModel, options: ["maxTokens"] },
399
+ },
400
+ budgets: { maxModelCalls: 12, maxTokens: 16000, maxRevisions: 1 },
401
+ completionPolicy: reflectionPolicy({
402
+ modelRole: "critic", criteria: "Support factual claims with supplied evidence.",
403
+ maxTokens: 512,
404
+ }),
405
+ contextPolicy: summaryContextPolicy({
406
+ modelRole: "summary", maxContextTokens: 6000, keepRecentTurns: 2,
407
+ summaryMaxTokens: 1000,
408
+ }),
409
+ });
410
+ ```
411
+
412
+ The critic must return a strict JSON completion decision. Malformed decisions,
413
+ truncated responses and unexpected tool requests fail closed. Revisions share
414
+ the runtime's aggregate budgets and cannot introduce effects unless the host
415
+ enables `allowRevisionEffects`. The policies themselves never execute tools.
416
+
417
+ Summaries replace only older complete turns in the model request. Host
418
+ instructions and the selected recent whole turns remain intact, including opaque
419
+ provider continuation and tool/result groups. Summaries are labelled untrusted
420
+ historical data; canonical history remains unchanged. The helper rejects an
421
+ indivisible recent turn, oversized source or oversized summary instead of silently
422
+ truncating it. `maxInputBytes` defaults to 65536. Token sizing is an estimate;
423
+ provider context accounting can differ. Already bounded requests make no summary
424
+ call. A new oversized projection may incur another bounded summary call.
425
+
426
+ Custom drivers can use `services.controlModel(role, messages, options)`. A harness-specific policy can narrow its neutral `ctx.executionServices` to `HarnessExecutionServices` to access that extension.
427
+ This requires an explicitly granted and bound role, with allowed option keys.
428
+ Calls share model/token budgets, cancellation, event accounting and owned-work
429
+ settlement. They carry no tools or provider continuation and do not change the
430
+ task's active role/tools. They skip task controllers, context projections and ability
431
+ middleware to prevent policy recursion; their authority comes from the explicit
432
+ host role grant. Legacy Agent reflection/compression configuration remains guarded
433
+ under H2: choose these policy ports explicitly.
434
+
435
+ Custom drivers can register additional asynchronous delegated work through
436
+ `services.runOwned(operation)`; the runtime waits for that work before releasing
437
+ its session lease and resources. Built-in Agent tool execution uses this ownership
438
+ path. Team broadcast and collaboration wait for every started member, including binding cleanup, to settle when one fails.
439
+ The conformance helper also disposes resources created in its fixture session,
440
+ including when validation fails, while preserving the primary failure.
441
+
442
+ ### Durable watches
443
+
444
+ `DurableWatch`, `defineWatch`, and `gmailWatchSource` provide explicitly activated,
445
+ authorized watches over the core durable task/action contracts. The host supplies
446
+ the persistent store, scheduler, authenticated Gmail client and trigger verifier,
447
+ and a scoped notification connector. Bounded reads, cursor/digest commits, quiet
448
+ hours, send caps and ambiguous-effect reconciliation are included. See the
449
+ [watch guide](src/watch/README.md) for setup, crash-recovery guarantees and the
450
+ remaining live Gmail/Pub/Sub delivery gates.
451
+
452
+ ### Approved MCP resources
453
+
454
+ `mcpResources` adds text documents as bounded context through a host-authenticated MCP client. It accepts clients structurally, including both MCP SDK generations; Agentium does not install a resource SDK automatically.
455
+
456
+ ```ts
457
+ import { defineHarness, mcpResources } from '@agentium/harness';
458
+
459
+ const definition = defineHarness({ abilities: [mcpResources({
460
+ id: 'approved-documents',
461
+ resources: [{ uri: 'docs://policies/shipping', mimeTypes: ['text/markdown'] }],
462
+ authorize: (grant, ctx) => host.canRead(ctx.tenantId, ctx.userId, grant.uri),
463
+ connect: async (ctx) => {
464
+ const client = await host.connectMCP(ctx); // authenticates this principal; respects ctx.signal
465
+ return { client, dispose: () => client.close() };
466
+ },
467
+ })] });
468
+ ```
469
+
470
+ `host` represents application-owned authentication, authorization and connection code. Grant URIs and MIME types are exact. An optional host `select(query, ctx)` can choose a subset of those grants; no server-wide discovery expands access. Setup defaults to a five-second deadline (`connectTimeoutMs`), and a late connection is disposed. Resource reads obey the shared context entry/byte/deadline budget and run cancellation. Authorization is rechecked before reads and before returning content. SDK resource caching is bypassed; v1 clients ignore that extra option and do not implement the v2 response cache. Every returned URI/MIME is checked. Text is labelled source data, with provenance, never inserted as system instructions. Binary documents require an explicit host decoder. Include only decoded text from an approved format/URI; this ability does not execute or fetch resource URIs itself.
471
+
472
+ ### Custom driver and controller telemetry
473
+
474
+ ```ts
475
+ import { EventBus } from '@agentium/core';
476
+ import { HarnessRuntime } from '@agentium/harness';
477
+ import { instrumentBus } from '@agentium/observability';
478
+
479
+ const telemetry = new EventBus();
480
+ const observation = instrumentBus(telemetry, { exporters: ['console'] });
481
+ const runtime = new HarnessRuntime({ ...runtimeConfig, telemetry });
482
+ await runtime.run('task', { identity, sessionId });
483
+ await observation.shutdown();
484
+ ```
485
+
486
+ The runtime emits one run lifecycle, uniquely identified model invocations for `services.model`, `streamModel` and `controlModel`, and controller spans for `prepareRun`, `prepareStep` and completion evaluation. Decisions include action, selected model role and active tool count. Success, failure and cancellation close their spans. Observation failures do not control execution. Prompts, output text, controller explanations and tool arguments are omitted from these events.
487
+
488
+ Use this bus with its own collector when observing the harness. Do not attach the same tracer or metrics collector to both the harness bus and the wrapped Agent bus: those layers share execution IDs and describe overlapping work. Direct provider calls that bypass the execution services also bypass runtime budgets and telemetry; custom drivers should use the services. Transport/toolkit-specific tracing remains available on their existing buses.
@@ -0,0 +1,39 @@
1
+ import type { RunContext } from "@agentium/core";
2
+ import type { AbilityBinding } from "./runtime/index.js";
3
+ import { type AbilityFactory, createHarnessDefinition, describeHarnessDefinition, exportHarnessManifest, extendHarnessDefinition, hashHarnessManifest, type JsonObject, type LocalAbilityUse, loadHarnessManifest } from "./runtime/index.js";
4
+ export interface AbilityDescription {
5
+ toolNames: readonly string[];
6
+ requirements: readonly string[];
7
+ runtimeDependent?: boolean;
8
+ }
9
+ export interface AbilityDefinition<Options> {
10
+ type: string;
11
+ version?: number;
12
+ /** Pure validation/snapshotting. Preserve caller-owned service references; never freeze clients. */
13
+ validate: (options: Options) => Options;
14
+ describe: (options: Options) => AbilityDescription;
15
+ bind: (options: Options, ctx: RunContext) => AbilityBinding | Promise<AbilityBinding>;
16
+ /** Explicit trusted mapping. Omit for local callbacks/services that cannot be serialized. */
17
+ portable?: {
18
+ validateOptions: (options: JsonObject) => JsonObject;
19
+ toOptions: (options: JsonObject) => Options;
20
+ toJSON: (options: Options) => JsonObject;
21
+ };
22
+ }
23
+ export interface Ability<Options> {
24
+ (options: Options, config?: {
25
+ instanceId?: string;
26
+ }): LocalAbilityUse;
27
+ readonly type: string;
28
+ readonly version: number;
29
+ readonly factory?: AbilityFactory;
30
+ }
31
+ /** Capture typed local options; erase only the wrapper so heterogeneous abilities compose without casts. */
32
+ export declare function defineAbility<Options>(definition: AbilityDefinition<Options>): Ability<Options>;
33
+ export declare const defineHarness: typeof createHarnessDefinition;
34
+ export declare const extendHarness: typeof extendHarnessDefinition;
35
+ export declare const describeHarness: typeof describeHarnessDefinition;
36
+ export declare const exportManifest: typeof exportHarnessManifest;
37
+ export declare const loadManifest: typeof loadHarnessManifest;
38
+ export declare const hashManifest: typeof hashHarnessManifest;
39
+ //# sourceMappingURL=definition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../src/definition.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EACL,KAAK,cAAc,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB,CAAC,OAAO;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oGAAoG;IACpG,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC;IACxC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,kBAAkB,CAAC;IACnD,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,KAAK,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACtF,6FAA6F;IAC7F,QAAQ,CAAC,EAAE;QACT,eAAe,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,UAAU,CAAC;QACrD,SAAS,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC;QAC5C,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,UAAU,CAAC;KAC1C,CAAC;CACH;AAED,MAAM,WAAW,OAAO,CAAC,OAAO;IAC9B,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,eAAe,CAAC;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;CACnC;AAqBD,4GAA4G;AAC5G,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAyD/F;AAED,eAAO,MAAM,aAAa,gCAA0B,CAAC;AACrD,eAAO,MAAM,aAAa,gCAA0B,CAAC;AACrD,eAAO,MAAM,eAAe,kCAA4B,CAAC;AACzD,eAAO,MAAM,cAAc,8BAAwB,CAAC;AACpD,eAAO,MAAM,YAAY,4BAAsB,CAAC;AAEhD,eAAO,MAAM,YAAY,4BAAsB,CAAC"}