@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 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
+ }