@salesforce/sfdx-agent-sdk 0.4.0 → 0.6.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/README.md +228 -35
- package/dist/agent-manager.d.ts +155 -87
- package/dist/agent-manager.js +161 -110
- package/dist/agent.d.ts +15 -0
- package/dist/agent.js +18 -0
- package/dist/harness/agent-harness.d.ts +45 -1
- package/dist/harness/harness-config.d.ts +10 -0
- package/dist/harness/harness-config.js +10 -0
- package/dist/harness/harness-factory.d.ts +2 -2
- package/dist/harness/index.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/internal/agent-identity-store.d.ts +41 -0
- package/dist/internal/agent-identity-store.js +141 -0
- package/dist/mcp-config.d.ts +64 -1
- package/dist/types/usage.d.ts +3 -1
- package/package.json +18 -11
package/README.md
CHANGED
|
@@ -6,16 +6,32 @@ without coupling consumer code to a specific AI framework.
|
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
9
|
-
> **Closed source.** This package is published to npm under the [Salesforce Public Code License](../../LICENSE.txt) and
|
|
9
|
+
> **Closed source.** This package is published to npm under the [Salesforce Public Code License](../../LICENSE.txt) and
|
|
10
|
+
> is for use by Salesforce only.
|
|
10
11
|
|
|
11
12
|
```typescript
|
|
12
13
|
import { createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
13
14
|
import { MastraHarnessFactory } from '@salesforce/sfdx-agent-harness-mastra';
|
|
14
15
|
|
|
15
|
-
// 1. Create the manager
|
|
16
|
+
// 1. Create the manager. This validates the storage folder, gates the harness's
|
|
17
|
+
// protocol version, and replays any agents the SDK persisted on a prior run
|
|
18
|
+
// (one JSON file per agent under `${storageRootFolder}/agents/`).
|
|
16
19
|
const manager = await createAgentManager('/path/to/storage', new MastraHarnessFactory());
|
|
17
20
|
|
|
18
|
-
//
|
|
21
|
+
// Bridge SDK logs into your host logger so you observe restore failures + warnings.
|
|
22
|
+
manager.onLog((record) => {
|
|
23
|
+
console[record.level](record.message, record.context);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Boot-time restore failures are queryable as instance state — use them to seed
|
|
27
|
+
// per-agent UI state, not for logging (the SDK already emitted each via onLog).
|
|
28
|
+
for (const failure of manager.getRestoreFailures()) {
|
|
29
|
+
// mark `failure.agentId` as `error` in your application state
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 2. Create an agent. The identity triple `{ agentId, projectRoot, config }` is
|
|
33
|
+
// persisted to disk; the next call to `createAgentManager` over the same
|
|
34
|
+
// storage folder will replay this agent automatically.
|
|
19
35
|
const agent = await manager.createAgent('/path/to/project', {
|
|
20
36
|
agentId: 'developer-assistant',
|
|
21
37
|
modelId: 'llmgateway__OpenAIGPT5',
|
|
@@ -35,35 +51,83 @@ for await (const event of eventStream) {
|
|
|
35
51
|
}
|
|
36
52
|
}
|
|
37
53
|
|
|
38
|
-
// 4. Shut down
|
|
54
|
+
// 4. Shut down. The harness is torn down; persisted identity files are NOT
|
|
55
|
+
// removed, so a subsequent `createAgentManager` call restores them.
|
|
39
56
|
await manager.shutdown();
|
|
40
57
|
```
|
|
41
58
|
|
|
42
59
|
## API Reference
|
|
43
60
|
|
|
44
|
-
### `createAgentManager(storageRootFolder, harnessFactory): Promise<AgentManager
|
|
61
|
+
### `createAgentManager<F>(storageRootFolder, harnessFactory, connectivityResolver?): Promise<AgentManager<H>>`
|
|
45
62
|
|
|
46
63
|
Factory function that creates an `AgentManager` backed by the provided `HarnessFactory`. The `storageRootFolder` must be
|
|
47
|
-
an existing directory and is used for persistent state (
|
|
48
|
-
|
|
64
|
+
an existing directory and is used for persistent state (the harness's runtime data plus the SDK's per-agent identity
|
|
65
|
+
files at `${storageRootFolder}/agents/<id>.json`). The SDK verifies that the constructed harness uses a supported
|
|
66
|
+
protocol version, replays any persisted agents the harness can still serve, and returns the manager. The optional
|
|
67
|
+
`connectivityResolver` overrides the default sf-CLI-based org resolution — used by e2e tests and custom-auth
|
|
68
|
+
deployments; production callers leave it unset.
|
|
69
|
+
|
|
70
|
+
The harness type `H` is **inferred from the factory's `create()` return type**, so consumers don't pass an explicit type
|
|
71
|
+
argument:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { MastraHarnessFactory } from '@salesforce/sfdx-agent-harness-mastra';
|
|
75
|
+
|
|
76
|
+
const manager = await createAgentManager(storageRoot, new MastraHarnessFactory());
|
|
77
|
+
// ^? AgentManager<MastraAgentHarness>
|
|
78
|
+
```
|
|
49
79
|
|
|
50
|
-
|
|
80
|
+
When the factory's `create()` is typed as `Promise<AgentHarness>` (the default), the manager is
|
|
81
|
+
`AgentManager<AgentHarness>` and behaves exactly as before. Harness packages that ship a branded subtype (e.g.
|
|
82
|
+
`MastraAgentHarness`) lift consumers into that subtype automatically — see "Harness Extensibility" below.
|
|
51
83
|
|
|
52
|
-
|
|
84
|
+
Restore failures (a persisted record the SDK could not bring back online — e.g. missing project directory, harness
|
|
85
|
+
rejection, thread rehydration failure) are queryable on the returned manager via `getRestoreFailures()`. Soft skips
|
|
86
|
+
inside the persistence directory (corrupt JSON, harness-id mismatch) are silently dropped from the restore pass and emit
|
|
87
|
+
a `warn` on the SDK's log bus.
|
|
53
88
|
|
|
54
|
-
|
|
55
|
-
| -------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
-
| `createAgent` | `(projectRoot: string, config?: AgentConfig & { agentId?: string }, options?: { abortSignal?: AbortSignal }) => Promise<Agent>` | Create and register a new agent. `projectRoot` must be an existing directory. If `agentId` is omitted a UUID is generated. |
|
|
57
|
-
| `getAgent` | `(agentId: string) => Agent` | Retrieve an agent by ID. Throws `AgentSDKError` (`AGENT_NOT_FOUND`) if missing. |
|
|
58
|
-
| `getAgentIds` | `() => string[]` | List all managed agent IDs. |
|
|
59
|
-
| `destroyAgent` | `(agentId: string) => Promise<void>` | Destroy an agent and release its resources. Throws `AgentSDKError` (`AGENT_NOT_FOUND`) if missing. |
|
|
60
|
-
| `shutdown` | `() => Promise<void>` | Destroy all agents and shut down the harness. |
|
|
61
|
-
| `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry across all managed agents. |
|
|
62
|
-
| `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to structured logs across all managed agents. |
|
|
89
|
+
### `AgentManager<H extends AgentHarness = AgentHarness>`
|
|
63
90
|
|
|
64
|
-
|
|
91
|
+
Top-level orchestrator that owns the harness and manages agent lifecycle. `AgentManager` is an interface; the concrete
|
|
92
|
+
implementation is internal — `createAgentManager` is the only public entry point.
|
|
65
93
|
|
|
66
|
-
|
|
94
|
+
The optional `H` type parameter (default `AgentHarness`) lets harness-aware consumers reach harness-specific features
|
|
95
|
+
through a typed `manager.extensions` slot. `createAgentManager` infers `H` from the factory; you usually don't write it
|
|
96
|
+
explicitly. The `createAgent` config parameter narrows automatically when the harness brands itself with
|
|
97
|
+
`WithAgentConfig` — see "Harness Extensibility" below.
|
|
98
|
+
|
|
99
|
+
| Property / Method | Signature | Description |
|
|
100
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
101
|
+
| `extensions` | `H['extensions']` | Harness-specific extensions namespace (read-only). Re-exposes the harness's `extensions` slot typed off `H`. Per-agent accessors take the agent id as their first argument. The SDK never reads or interprets this — see "Harness Extensibility" below. |
|
|
102
|
+
| `createAgent` | `(projectRoot: string, config?: ConfigOf<H> & { agentId?: string }, options?: { abortSignal?: AbortSignal }) => Promise<Agent<H>>` | Create and register a new agent and persist its identity triple. `projectRoot` must be an existing directory. If `agentId` is omitted a UUID is generated. The config type is inferred from the harness — see `ConfigOf<H>`. |
|
|
103
|
+
| `getAgent` | `(agentId: string) => Agent<H>` | Retrieve a live agent by ID. Throws `AgentSDKError` (`AGENT_NOT_FOUND`) for unknown ids and for ids that are only present in `getRestoreFailures()`. |
|
|
104
|
+
| `getAgentIds` | `() => string[]` | List all live agent IDs (successful + successfully restored). Failed-restore agents are not included — query `getRestoreFailures()` separately. |
|
|
105
|
+
| `destroyAgent` | `(agentId: string) => Promise<void>` | Destroy an agent, remove its identity record from disk, and clear any matching `getRestoreFailures()` entry. Failed-restore-only ids are accepted (no harness call made). |
|
|
106
|
+
| `shutdown` | `() => Promise<void>` | Destroy all live agents and shut down the harness. Identity files survive (that's the whole point) — restart `createAgentManager` over the same root to bring them back. |
|
|
107
|
+
| `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry across all managed agents. |
|
|
108
|
+
| `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to structured logs across all managed agents. Bridge this into your host logger to observe restore-failure events + soft-skip warnings. |
|
|
109
|
+
| `getRestoreFailures` | `() => RestoreFailure[]` | Snapshot of agents the SDK could not restore on this boot. Each entry carries the persisted `{ agentId, projectRoot, config }` plus the underlying error. |
|
|
110
|
+
|
|
111
|
+
#### `RestoreFailure`
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
type RestoreFailure = {
|
|
115
|
+
agentId: string;
|
|
116
|
+
projectRoot: string;
|
|
117
|
+
config: AgentConfig;
|
|
118
|
+
error: unknown;
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Returned by `AgentManager.getRestoreFailures()`. Use it to seed `error`-state placeholders in your application; do
|
|
123
|
+
**not** iterate it for logging — the SDK already emitted each failure via `onLog` at `error` level during the restore
|
|
124
|
+
pass, before this function returned.
|
|
125
|
+
|
|
126
|
+
### `Agent<H extends AgentHarness = AgentHarness>`
|
|
127
|
+
|
|
128
|
+
A configured AI agent. Factory for chat sessions. The optional `H` type parameter is currently informational on `Agent`
|
|
129
|
+
— harness-specific features are reached through `manager.extensions`, not `agent.extensions`. The default `AgentHarness`
|
|
130
|
+
keeps unparameterized call sites working.
|
|
67
131
|
|
|
68
132
|
| Method | Signature | Description |
|
|
69
133
|
| ---------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
@@ -134,16 +198,16 @@ Discriminated union (`event.type`) of streaming events:
|
|
|
134
198
|
|
|
135
199
|
#### `AgentConfig`
|
|
136
200
|
|
|
137
|
-
| Field | Type | Description
|
|
138
|
-
| --------------- | ------------------ |
|
|
139
|
-
| `orgAlias?` | `string` | Salesforce org alias or username. Falls back to project/default org.
|
|
140
|
-
| `modelId?` | `ModelName` | LLM model identifier (e.g. `'llmgateway__OpenAIGPT5'`).
|
|
141
|
-
| `name?` | `string` | Human-readable agent name.
|
|
142
|
-
| `description?` | `string` | Agent purpose description.
|
|
143
|
-
| `instructions?` | `string` | System instructions for the agent.
|
|
144
|
-
| `tools?` | `ToolDefinition[]` | Consumer-executed tool schemas.
|
|
145
|
-
| `mcpServers?` | `MCPConfiguration` | MCP server connections.
|
|
146
|
-
| `skills?` | `string[]` | Each entry is either an individual skill folder (containing `SKILL.md`) or a parent folder containing skill subfolders. Relative and absolute paths supported; forms can be mixed in the same array.
|
|
201
|
+
| Field | Type | Description |
|
|
202
|
+
| --------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
203
|
+
| `orgAlias?` | `string` | Salesforce org alias or username. Falls back to project/default org. |
|
|
204
|
+
| `modelId?` | `ModelName` | LLM model identifier (e.g. `'llmgateway__OpenAIGPT5'`). |
|
|
205
|
+
| `name?` | `string` | Human-readable agent name. |
|
|
206
|
+
| `description?` | `string` | Agent purpose description. |
|
|
207
|
+
| `instructions?` | `string` | System instructions for the agent. |
|
|
208
|
+
| `tools?` | `ToolDefinition[]` | Consumer-executed tool schemas. |
|
|
209
|
+
| `mcpServers?` | `MCPConfiguration` | MCP server connections. |
|
|
210
|
+
| `skills?` | `string[]` | Each entry is either an individual skill folder (containing `SKILL.md`) or a parent folder containing skill subfolders. Relative and absolute paths supported; forms can be mixed in the same array. |
|
|
147
211
|
| `rules?` | `string[]` | Each entry is either an individual `.md` rule file or a directory of `.md` rule files (scanned one level deep, alphabetical, non-`.md` skipped). Bodies are composed verbatim into the agent's effective system prompt; YAML frontmatter is optional and stripped if present. Matches Claude Code's `.claude/rules/*.md` convention. |
|
|
148
212
|
|
|
149
213
|
#### `StreamOptions`
|
|
@@ -184,9 +248,53 @@ type MCPRemoteServerConfig = {
|
|
|
184
248
|
| -------- | ------------------------------------------------------ | --------------------------------------- |
|
|
185
249
|
| `name` | `string` | Server identifier. |
|
|
186
250
|
| `status` | `'connected' \| 'connecting' \| 'disabled' \| 'error'` | Connection state. |
|
|
187
|
-
| `tools` | `
|
|
251
|
+
| `tools` | [`McpToolInfo[]`](#mcptoolinfo) | Discovered tools (name + metadata). |
|
|
188
252
|
| `error?` | `string` | Error message when status is `'error'`. |
|
|
189
253
|
|
|
254
|
+
#### `McpToolInfo`
|
|
255
|
+
|
|
256
|
+
Runtime metadata for a single MCP-discovered tool. Optional fields are populated when the underlying harness can supply
|
|
257
|
+
them from its MCP client; harnesses whose runtime does not expose a given field leave it `undefined`. Consumers must
|
|
258
|
+
treat every field except `name` as optional.
|
|
259
|
+
|
|
260
|
+
| Field | Type | Description |
|
|
261
|
+
| -------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
262
|
+
| `name` | `string` | Tool name as exposed to the LLM, including any harness-applied namespacing. |
|
|
263
|
+
| `description?` | `string` | Human-readable description of what the tool does. |
|
|
264
|
+
| `inputSchema?` | `Record<string, unknown>` | Tool input parameters as a [**JSON Schema**](https://json-schema.org/) object (the MCP wire format). |
|
|
265
|
+
| `annotations?` | [`McpToolAnnotations`](#mcptoolannotations) | Behavioral / UI-presentation hints declared by the MCP server. |
|
|
266
|
+
|
|
267
|
+
**`inputSchema` is a JSON Schema object, not a Zod schema.** It is typed as `Record<string, unknown>` so this package
|
|
268
|
+
incurs no `zod` or `@types/json-schema` dependency. If you need a Zod schema at runtime, convert with a library such as
|
|
269
|
+
[`json-schema-to-zod`](https://www.npmjs.com/package/json-schema-to-zod); for runtime validation, feed the schema to a
|
|
270
|
+
JSON Schema validator such as [AJV](https://ajv.js.org/).
|
|
271
|
+
|
|
272
|
+
The exact set of keys present on `inputSchema` depends on the harness's normalization step. The Mastra harness, for
|
|
273
|
+
example, reaches consumers after passing through `@mastra/schema-compat`'s converter, which adds a `$schema` annotation
|
|
274
|
+
(typically `http://json-schema.org/draft-07/schema#`) and `additionalProperties: false` even when the source MCP server
|
|
275
|
+
did not declare them. Both are valid JSON Schema annotations and are forwarded untouched.
|
|
276
|
+
|
|
277
|
+
**Why no `outputSchema` field?** The MCP protocol carries an optional `outputSchema` per tool, but neither shipped
|
|
278
|
+
harness can supply it: Mastra's `@mastra/mcp` strips it from each wrapped tool before the harness sees it (to keep
|
|
279
|
+
`CallToolResult` validation correct), and the Claude Agent SDK's MCP status surface omits the field entirely. We
|
|
280
|
+
deliberately keep it off the SDK contract rather than ship an always-`undefined` field consumers would have to ignore;
|
|
281
|
+
adding it later if a harness gains the data is non-breaking.
|
|
282
|
+
|
|
283
|
+
#### `McpToolAnnotations`
|
|
284
|
+
|
|
285
|
+
Mirrors the
|
|
286
|
+
[MCP protocol's `Tool.annotations` shape](https://spec.modelcontextprotocol.io/specification/server/tools/#tool-annotations).
|
|
287
|
+
Each field is optional because MCP servers populate annotations à la carte; absence means "the server did not declare
|
|
288
|
+
this hint," not "false."
|
|
289
|
+
|
|
290
|
+
| Field | Type | Description |
|
|
291
|
+
| ------------------ | --------- | ------------------------------------------------------------------------------ |
|
|
292
|
+
| `title?` | `string` | Human-readable label suitable for UI display (vs. the machine `name`). |
|
|
293
|
+
| `readOnlyHint?` | `boolean` | When `true`, the tool only reads data and has no side effects. |
|
|
294
|
+
| `destructiveHint?` | `boolean` | When `true`, the tool may perform destructive updates to its environment. |
|
|
295
|
+
| `idempotentHint?` | `boolean` | When `true`, repeated calls with the same arguments have no additional effect. |
|
|
296
|
+
| `openWorldHint?` | `boolean` | When `true`, the tool may interact with an open world of external entities. |
|
|
297
|
+
|
|
190
298
|
### Tool Types
|
|
191
299
|
|
|
192
300
|
```typescript
|
|
@@ -296,7 +404,14 @@ const agent = await manager.createAgent('/project', {
|
|
|
296
404
|
// Poll until connected
|
|
297
405
|
const servers = agent.getMcpServerInfo();
|
|
298
406
|
for (const s of servers) {
|
|
299
|
-
|
|
407
|
+
const toolNames = s.tools.map((t) => t.name).join(', ');
|
|
408
|
+
console.log(`${s.name}: ${s.status}, tools: ${toolNames}`);
|
|
409
|
+
|
|
410
|
+
// Each tool also carries description / inputSchema / annotations when available.
|
|
411
|
+
for (const tool of s.tools) {
|
|
412
|
+
if (tool.description) console.log(` ${tool.name} — ${tool.description}`);
|
|
413
|
+
// tool.inputSchema is a JSON Schema object; convert with json-schema-to-zod if you need Zod.
|
|
414
|
+
}
|
|
300
415
|
}
|
|
301
416
|
```
|
|
302
417
|
|
|
@@ -377,6 +492,84 @@ Returns `true` if the URL matches a Salesforce Hosted MCP Server endpoint (prod,
|
|
|
377
492
|
The SDK exposes typed `TelemetryEvent` / `LogRecord` streams at the manager, agent, and session scopes. See
|
|
378
493
|
[Telemetry & Logs](#telemetry--logs) below for the event catalog, emit contract, and structured-log table.
|
|
379
494
|
|
|
495
|
+
### Harness Extensibility
|
|
496
|
+
|
|
497
|
+
The `AgentHarness` contract is the SDK's lowest-common-denominator surface — every harness implements it, and the SDK
|
|
498
|
+
consumes only that. Features a single harness has but the contract can't generalize over (Mastra workflows, Mastra's
|
|
499
|
+
`ToolSearchProcessor`, structured working memory, etc.) live on a typed `extensions` slot the harness package owns. The
|
|
500
|
+
SDK never reads or interprets `extensions`; it just threads the type through.
|
|
501
|
+
|
|
502
|
+
For most consumers, the inference machinery means **you never write the type parameters**:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import { createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
506
|
+
import { MastraHarnessFactory } from '@salesforce/sfdx-agent-harness-mastra';
|
|
507
|
+
|
|
508
|
+
const manager = await createAgentManager(storageRoot, new MastraHarnessFactory());
|
|
509
|
+
// ^? AgentManager<MastraAgentHarness> (inferred from the factory)
|
|
510
|
+
|
|
511
|
+
const agent = await manager.createAgent(projectRoot, {
|
|
512
|
+
instructions: '...',
|
|
513
|
+
toolSearch: { pool: '*' }, // ← Mastra-specific config; autocompletes via WithAgentConfig
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const processor = manager.extensions.mastra.getToolSearchProcessor(agent.getId());
|
|
517
|
+
// ^? ToolSearchHandle | undefined
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Per-agent accessors (like `getToolSearchProcessor` above) take the agent id as their first argument; orchestrator-level
|
|
521
|
+
accessors (e.g. workflow registries) don't. Everything lives on `manager.extensions` regardless of scope — the SDK chose
|
|
522
|
+
the harness-scoped shape for simplicity over a per-feature scope split. See `ARCHITECTURE.md` → "Harness Extensibility"
|
|
523
|
+
for the rationale.
|
|
524
|
+
|
|
525
|
+
#### `AgentHarness.extensions: Record<string, unknown>`
|
|
526
|
+
|
|
527
|
+
Passthrough slot on the harness contract. Concrete harnesses narrow it on their branded subtype (e.g.
|
|
528
|
+
`MastraAgentHarness` declares `extensions: { mastra: { ... } }`). The SDK never reads this; it surfaces it as
|
|
529
|
+
`AgentManager.extensions` typed off the harness type.
|
|
530
|
+
|
|
531
|
+
#### `WithAgentConfig<H extends AgentHarness, C extends AgentConfig>`
|
|
532
|
+
|
|
533
|
+
Opt-in helper for harnesses that accept extra `AgentConfig` fields. Wrap your branded harness type with this helper and
|
|
534
|
+
`AgentManager.createAgent`'s config parameter narrows to `C` automatically:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
import type { AgentHarness, WithAgentConfig, AgentConfig } from '@salesforce/sfdx-agent-sdk';
|
|
538
|
+
|
|
539
|
+
type MyAgentConfig = AgentConfig & { custom?: { ... } };
|
|
540
|
+
|
|
541
|
+
type MyAgentHarness = WithAgentConfig<
|
|
542
|
+
AgentHarness & {
|
|
543
|
+
readonly harnessId: 'my-harness';
|
|
544
|
+
readonly extensions: { 'my-harness': { ... } };
|
|
545
|
+
},
|
|
546
|
+
MyAgentConfig
|
|
547
|
+
>;
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
The brand is type-only; it never exists at runtime. Harnesses that don't accept extra config fields leave
|
|
551
|
+
`WithAgentConfig` unused — the base `AgentHarness` interface stays untouched.
|
|
552
|
+
|
|
553
|
+
#### `ConfigOf<H extends AgentHarness>`
|
|
554
|
+
|
|
555
|
+
Resolves the `AgentConfig` subtype declared by a harness via `WithAgentConfig`. Falls back to plain `AgentConfig` when
|
|
556
|
+
the harness wasn't branded. Used internally by `AgentManager.createAgent`'s parameter type; harness-package authors
|
|
557
|
+
don't usually reference it directly.
|
|
558
|
+
|
|
559
|
+
#### Decision rule: cross-harness contract vs. extension
|
|
560
|
+
|
|
561
|
+
When deciding where a new harness-package feature should live:
|
|
562
|
+
|
|
563
|
+
| Question | Answer → Where it goes |
|
|
564
|
+
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
565
|
+
| Could a second harness implement this with similar semantics? | Likely cross-harness contract |
|
|
566
|
+
| Is this an operational handle on a harness-internal runtime object (e.g. processor, workspace)? | Extension namespace |
|
|
567
|
+
| Does this require harness-specific config that doesn't generalize? | Extension (extra fields on a `WithAgentConfig`-branded type) |
|
|
568
|
+
| Is the polyfill cheap and faithful across harnesses? | Cross-harness contract with a polyfilled default |
|
|
569
|
+
|
|
570
|
+
Extensions are the default for "Mastra has a thing nothing else has." See `ARCHITECTURE.md` → "Harness Extensibility"
|
|
571
|
+
for the full design including the `WithAgentConfig` rationale.
|
|
572
|
+
|
|
380
573
|
## Writing a Harness
|
|
381
574
|
|
|
382
575
|
The SDK ships no harness implementation. Consumers pick one by passing a `HarnessFactory` instance to
|
|
@@ -388,7 +581,7 @@ Harness authors implement two interfaces and can compose one helper class, all e
|
|
|
388
581
|
|
|
389
582
|
| Export | Role |
|
|
390
583
|
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
391
|
-
| `HarnessFactory
|
|
584
|
+
| `HarnessFactory<H>` | Construct a harness of type `H` bound to a storage root. Declares `harnessId` and `protocolVersion`. Default `H = AgentHarness`. |
|
|
392
585
|
| `AgentHarness` | Runtime contract: agent / thread / stream / tool / message lifecycle. Declares its own `harnessId` and `protocolVersion`. |
|
|
393
586
|
| `SUPPORTED_PROTOCOL_VERSIONS` | Readonly list of harness protocol versions this SDK accepts. `createAgentManager` checks both the factory and the constructed harness. |
|
|
394
587
|
| `HarnessBusOwner` | Composition helper owning telemetry + log buses with `dispose()` semantics. Reuse it instead of reimplementing bus plumbing. |
|
|
@@ -419,10 +612,10 @@ class MyHarness implements AgentHarness {
|
|
|
419
612
|
// implement the remaining AgentHarness methods (createAgent, stream, …)
|
|
420
613
|
}
|
|
421
614
|
|
|
422
|
-
export class MyHarnessFactory implements HarnessFactory {
|
|
615
|
+
export class MyHarnessFactory implements HarnessFactory<MyHarness> {
|
|
423
616
|
readonly harnessId = 'my-harness';
|
|
424
617
|
readonly protocolVersion = 1;
|
|
425
|
-
async create(storageRootFolder: string): Promise<
|
|
618
|
+
async create(storageRootFolder: string): Promise<MyHarness> {
|
|
426
619
|
return new MyHarness(storageRootFolder);
|
|
427
620
|
}
|
|
428
621
|
}
|