@intx/harness 0.1.2
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 +38 -0
- package/package.json +19 -0
- package/src/config.ts +135 -0
- package/src/connector-router.test.ts +718 -0
- package/src/connector-router.ts +304 -0
- package/src/deploy-tree.test.ts +51 -0
- package/src/deploy-tree.ts +35 -0
- package/src/harness.test.ts +1747 -0
- package/src/harness.ts +379 -0
- package/src/index.ts +31 -0
- package/src/merge-tool-runners.test.ts +149 -0
- package/src/merge-tool-runners.ts +90 -0
- package/src/runtime-capabilities.test.ts +19 -0
- package/src/runtime-capabilities.ts +22 -0
- package/tsconfig.json +4 -0
- package/tsconfig.tsbuildinfo +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @intx/harness
|
|
2
|
+
|
|
3
|
+
Agent harness runtime. Composes the inference reactor, tool
|
|
4
|
+
runners, deploy-tree reader, default director, runtime
|
|
5
|
+
capabilities, and connector router into a single supervisor that
|
|
6
|
+
sits between the message transport and the reactor.
|
|
7
|
+
|
|
8
|
+
The harness watches the agent's INBOX, routes inbound messages by
|
|
9
|
+
thread, sends connector replies with the right threading headers,
|
|
10
|
+
and drives the reactor loop to completion. Consumed by
|
|
11
|
+
`@intx/agent` for in-process agents and by `@intx/hub-agent` for
|
|
12
|
+
sidecar-hosted agents.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createHarness, mergeToolRunners } from "@intx/harness";
|
|
16
|
+
|
|
17
|
+
const harness = createHarness({
|
|
18
|
+
address: "agent@tenant.interchange.network",
|
|
19
|
+
systemPrompt,
|
|
20
|
+
source, // InferenceSource: id, provider, model, apiKey, baseURL
|
|
21
|
+
transport, // MessageTransport
|
|
22
|
+
crypto, // CryptoProvider
|
|
23
|
+
storage, // ContextStore
|
|
24
|
+
tools: mergeToolRunners([mailTools, posixTools]),
|
|
25
|
+
onEvent: (event) => {
|
|
26
|
+
// event: InferenceEvent — persist or forward
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
harness.start();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`HarnessConfig` in `src/config.ts` lists the optional fields:
|
|
34
|
+
`director` or `defaultDirectorPolicy` (mutually exclusive),
|
|
35
|
+
`beforeToolExtensions`, `auditStore`, `authorize`, and
|
|
36
|
+
`onConnectorStateChanged`. `mergeToolRunners` composes multiple
|
|
37
|
+
`ToolRunner` implementations into a single runner that dispatches
|
|
38
|
+
by tool definition name.
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intx/harness",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"license": "LGPL-2.1-only",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"default": "./src/index.ts"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@intx/inference": "0.0.0",
|
|
14
|
+
"@intx/log": "0.0.0",
|
|
15
|
+
"@intx/mime": "0.0.0",
|
|
16
|
+
"@intx/types": "0.0.0",
|
|
17
|
+
"arktype": "^2.1.29"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MessageTransport,
|
|
3
|
+
CryptoProvider,
|
|
4
|
+
ConnectorThreadState,
|
|
5
|
+
ContextStore,
|
|
6
|
+
AuditStore,
|
|
7
|
+
ToolRunner,
|
|
8
|
+
ToolDefinition,
|
|
9
|
+
InferenceSource,
|
|
10
|
+
InferenceEvent,
|
|
11
|
+
ReactorDirector,
|
|
12
|
+
BeforeToolExtension,
|
|
13
|
+
} from "@intx/types/runtime";
|
|
14
|
+
import type { AuthzCallResult, DefaultDirectorPolicy } from "@intx/inference";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configuration passed to `createHarness`. All required fields must be
|
|
18
|
+
* provided; none have defaults that silently mask missing values.
|
|
19
|
+
*/
|
|
20
|
+
export type HarnessConfig = {
|
|
21
|
+
/** The agent's SMTP address, e.g. "agent@tenant.interchange.network". */
|
|
22
|
+
address: string;
|
|
23
|
+
|
|
24
|
+
/** System prompt for the agent's reasoning. */
|
|
25
|
+
systemPrompt: string;
|
|
26
|
+
|
|
27
|
+
/** Active inference source (id, provider, model, API key, etc.). */
|
|
28
|
+
source: InferenceSource;
|
|
29
|
+
|
|
30
|
+
/** Message transport implementation (SMTP/IMAP or in-memory). */
|
|
31
|
+
transport: MessageTransport;
|
|
32
|
+
|
|
33
|
+
/** Cryptographic provider for signing outbound messages. */
|
|
34
|
+
crypto: CryptoProvider;
|
|
35
|
+
|
|
36
|
+
/** Context store for persisting message history. */
|
|
37
|
+
storage: ContextStore;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Caller-supplied tool runner with the full set of tool definitions
|
|
41
|
+
* the model should see. The harness forwards this runner directly to
|
|
42
|
+
* the reactor; it does not layer additional tools on top. Callers
|
|
43
|
+
* composing multiple tool packages (e.g. mail + posix) should merge
|
|
44
|
+
* them with `mergeToolRunners` before passing the result here.
|
|
45
|
+
*/
|
|
46
|
+
tools: ToolRunner & { definitions: ToolDefinition[] };
|
|
47
|
+
|
|
48
|
+
/** Callback invoked for every inference event emitted by the reactor. */
|
|
49
|
+
onEvent: (event: InferenceEvent) => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Optional callback invoked whenever the connector router's state changes
|
|
53
|
+
* (commit of a start/continue decision, an outbound reply send advancing
|
|
54
|
+
* lastMessageId, or load-time restore from the context store). Fires only
|
|
55
|
+
* on a real state change, not on no-op operations. Used by the sidecar to
|
|
56
|
+
* lift connector-state updates onto the hub-bound event channel.
|
|
57
|
+
*/
|
|
58
|
+
onConnectorStateChanged?: (state: ConnectorThreadState | null) => void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Optional custom director. When omitted, the default conversational director
|
|
62
|
+
* is used (message.received → infer → execute_tools loop → reply → wait).
|
|
63
|
+
*/
|
|
64
|
+
director?: ReactorDirector;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Policy overrides for the default director. Mutually exclusive with
|
|
68
|
+
* `director`. Each field controls a specific decision point in the default
|
|
69
|
+
* director's event handling loop.
|
|
70
|
+
*/
|
|
71
|
+
defaultDirectorPolicy?: DefaultDirectorPolicy;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extensions that run before each tool call. Return a string to block the
|
|
75
|
+
* call (the string becomes the tool result), or undefined to allow it.
|
|
76
|
+
* When using `authorize`, the harness creates and prepends an authz
|
|
77
|
+
* extension automatically — do not also pass one here.
|
|
78
|
+
*/
|
|
79
|
+
beforeToolExtensions?: BeforeToolExtension[];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Audit store for persisting tool invocation records. When provided,
|
|
83
|
+
* the harness creates an audit collector and flushes completed records
|
|
84
|
+
* at checkpoint boundaries and shutdown.
|
|
85
|
+
*
|
|
86
|
+
* Requires `authorize` to be set so the authz extension's onDecision
|
|
87
|
+
* callback can feed governance decisions to the collector.
|
|
88
|
+
*/
|
|
89
|
+
auditStore?: AuditStore;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Authorization function for tool calls. When provided, the harness
|
|
93
|
+
* constructs an authz extension internally and prepends it to
|
|
94
|
+
* `beforeToolExtensions`. Callers should not also pass a manually
|
|
95
|
+
* constructed authz extension via `beforeToolExtensions`.
|
|
96
|
+
*/
|
|
97
|
+
authorize?: (resource: string, action: string) => Promise<AuthzCallResult>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export function validateConfig(config: HarnessConfig): void {
|
|
101
|
+
if (config.address.trim() === "") {
|
|
102
|
+
throw new Error("HarnessConfig.address must not be empty");
|
|
103
|
+
}
|
|
104
|
+
if (config.systemPrompt.trim() === "") {
|
|
105
|
+
throw new Error("HarnessConfig.systemPrompt must not be empty");
|
|
106
|
+
}
|
|
107
|
+
if (config.source.id.trim() === "") {
|
|
108
|
+
throw new Error("HarnessConfig.source.id must not be empty");
|
|
109
|
+
}
|
|
110
|
+
if (config.source.provider.trim() === "") {
|
|
111
|
+
throw new Error("HarnessConfig.source.provider must not be empty");
|
|
112
|
+
}
|
|
113
|
+
if (config.source.model.trim() === "") {
|
|
114
|
+
throw new Error("HarnessConfig.source.model must not be empty");
|
|
115
|
+
}
|
|
116
|
+
if (config.source.apiKey.trim() === "") {
|
|
117
|
+
throw new Error("HarnessConfig.source.apiKey must not be empty");
|
|
118
|
+
}
|
|
119
|
+
if (config.source.baseURL.trim() === "") {
|
|
120
|
+
throw new Error("HarnessConfig.source.baseURL must not be empty");
|
|
121
|
+
}
|
|
122
|
+
if (
|
|
123
|
+
config.director !== undefined &&
|
|
124
|
+
config.defaultDirectorPolicy !== undefined
|
|
125
|
+
) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"HarnessConfig.director and HarnessConfig.defaultDirectorPolicy are mutually exclusive",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (config.auditStore !== undefined && config.authorize === undefined) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
"HarnessConfig.authorize is required when auditStore is provided",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|