@jr2/agent-protocol 0.2.1 → 0.4.1
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 +3 -3
- package/package.json +2 -2
- package/src/define-event.ts +47 -25
- package/src/examples.ts +3 -3
- package/src/index.ts +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @jr2/agent-protocol
|
|
2
2
|
|
|
3
|
-
The Orchestrator↔Agent wire for jr2: `defineEvent` and nothing else — a pure leaf
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The Orchestrator↔Agent wire for jr2: `defineEvent` and nothing else — a pure leaf `@jr2/orchestrator` depends on, so an
|
|
4
|
+
event an Agent can deliver is typed once. The Harness reads the same definition off the wire, as the JSON Schema the
|
|
5
|
+
Menu offers its model.
|
|
6
6
|
|
|
7
7
|
You rarely depend on this directly; a Workflow imports what it needs from `@jr2/orchestrator`. Docs, glossary, and
|
|
8
8
|
architecture decisions: [github.com/snapwich/jr2](https://github.com/snapwich/jr2).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jr2/agent-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "The jr2 Orchestrator↔Agent wire: defineEvent and nothing else.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -43,6 +43,6 @@
|
|
|
43
43
|
],
|
|
44
44
|
"scripts": {
|
|
45
45
|
"typecheck": "tsc --noEmit",
|
|
46
|
-
"test": "node --test"
|
|
46
|
+
"test": "node --test --test-global-setup=../../scripts/test-tmpdir.ts"
|
|
47
47
|
}
|
|
48
48
|
}
|
package/src/define-event.ts
CHANGED
|
@@ -10,17 +10,21 @@ import { z } from "zod";
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* How delivering the event resolves for the caller (ADR-0006):
|
|
13
|
-
* - `ack`
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* - `ack` the pick is delivered and answered. A pick that moves the workflow to work inside the
|
|
14
|
+
* Agent's state is answered when that work is done (ADR-0024): the answer is the Machine's,
|
|
15
|
+
* never a schema on the pick, so no pick declares an output.
|
|
16
|
+
* - `poll` the caller pulls queued down-channel messages (cooperative steer checkpoint). Reserved,
|
|
17
|
+
* not built (ADR-0013).
|
|
16
18
|
*/
|
|
17
|
-
export type EventSemantics = "ack" | "
|
|
19
|
+
export type EventSemantics = "ack" | "poll";
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
|
-
* Who may deliver the event (ADR-0015)
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* Who may deliver the event, stated BY KIND (ADR-0015): `agent` (an Agent's pick), `external` (a
|
|
23
|
+
* Gate's caller), `any` (both). Leave it UNSET for the default: each writing of the event — each
|
|
24
|
+
* `on` key — belongs to the party that its state, or the nearest state above it, invokes (an Agent
|
|
25
|
+
* slot, a `gate`, or both); with none above, both. Set it only to give a pick to the other party
|
|
26
|
+
* (`submit: "any"` lets an in-Turn Gate's caller send the Agent's pick) or to hold a root pick back
|
|
27
|
+
* (`report_blocked: "agent"`; `approve: "external"` guarantees no Agent state can offer it).
|
|
24
28
|
*/
|
|
25
29
|
export type EventAudience = "agent" | "external" | "any";
|
|
26
30
|
|
|
@@ -34,9 +38,8 @@ export type EventDef<Name extends string = string, Input extends z.ZodObject = z
|
|
|
34
38
|
/** Input schema — a flat tagged object, never a `oneOf` (ADR-0006 encoding rule). */
|
|
35
39
|
readonly input: Input;
|
|
36
40
|
readonly semantics: EventSemantics;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
readonly output?: z.ZodType;
|
|
41
|
+
/** Absent: each writing's state decides who may send it (see {@link EventAudience}). */
|
|
42
|
+
readonly audience?: EventAudience;
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
/**
|
|
@@ -58,6 +61,18 @@ export type EventFrom<D extends EventDef> =
|
|
|
58
61
|
// `workspace.*`).
|
|
59
62
|
const NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
60
63
|
|
|
64
|
+
/**
|
|
65
|
+
* The field the Orchestrator writes on every event a party delivers into a Machine: its
|
|
66
|
+
* {@link Sender} (ADR-0011, ADR-0015). Plain data on the event, so it rides the mailbox, a delayed
|
|
67
|
+
* event the run host persists, and a restore. Only the Orchestrator writes it: no def may declare
|
|
68
|
+
* it, and a caller that posts it is refused. The Machine's own events (`raise`, `sendTo` itself, a
|
|
69
|
+
* child's `sendParent`) carry none.
|
|
70
|
+
*/
|
|
71
|
+
export const SENDER_KEY = "sender";
|
|
72
|
+
|
|
73
|
+
/** Who delivered an event: an Agent by its slot, or a Gate by its invoke id (ADR-0015). */
|
|
74
|
+
export type Sender = { readonly party: "agent" | "gate"; readonly id: string };
|
|
75
|
+
|
|
61
76
|
/** Define one control event. Pure — returns a frozen def; registration happens nowhere. */
|
|
62
77
|
export function defineEvent<const Name extends string, Input extends z.ZodObject>(def: {
|
|
63
78
|
name: Name;
|
|
@@ -65,9 +80,8 @@ export function defineEvent<const Name extends string, Input extends z.ZodObject
|
|
|
65
80
|
input: Input;
|
|
66
81
|
/** Default: `ack`. */
|
|
67
82
|
semantics?: EventSemantics;
|
|
68
|
-
/** Default:
|
|
83
|
+
/** Default: unset — each writing's state decides (see {@link EventAudience}). */
|
|
69
84
|
audience?: EventAudience;
|
|
70
|
-
output?: z.ZodType;
|
|
71
85
|
}): EventDef<Name, Input> {
|
|
72
86
|
if (!NAME_RE.test(def.name)) {
|
|
73
87
|
throw new Error(
|
|
@@ -75,12 +89,22 @@ export function defineEvent<const Name extends string, Input extends z.ZodObject
|
|
|
75
89
|
`(it becomes an MCP tool name and an xstate event type)`,
|
|
76
90
|
);
|
|
77
91
|
}
|
|
78
|
-
if (
|
|
92
|
+
if (SENDER_KEY in def.input.shape) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`defineEvent: "${def.name}" declares \`${SENDER_KEY}\` in its input — the Orchestrator writes that field ` +
|
|
95
|
+
`on every delivered event (who sent it); name the payload field something else`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
// Retired (ADR-0006): a pick's answer is the Machine's — the last reply since the pick, or what an
|
|
99
|
+
// `answer` action sets (ADR-0024) — so a schema on the pick would promise a shape nothing keeps.
|
|
100
|
+
const loose = def as { semantics?: unknown; output?: unknown };
|
|
101
|
+
if (loose.semantics === "deferred" || loose.output !== undefined) {
|
|
79
102
|
throw new Error(
|
|
80
|
-
`defineEvent: "${def.name}"
|
|
103
|
+
`defineEvent: "${def.name}" declares ${loose.output !== undefined ? "an `output`" : "`deferred`"}, which is retired — ` +
|
|
104
|
+
`every pick is answered when the work it started is done, and the answer is the Machine's, not the def's`,
|
|
81
105
|
);
|
|
82
106
|
}
|
|
83
|
-
return Object.freeze({ semantics: "ack" as const,
|
|
107
|
+
return Object.freeze({ semantics: "ack" as const, ...def });
|
|
84
108
|
}
|
|
85
109
|
|
|
86
110
|
/** Duck-type guard for manifest validation (defs are plain data, not branded). */
|
|
@@ -102,13 +126,11 @@ export function isEventDef(value: unknown): value is EventDef {
|
|
|
102
126
|
* Workflow: a nested Machine is reached by `import` and never carries the name `jr2 run` addresses
|
|
103
127
|
* (ADR-0049), so naming it a workflow here would misreport where the bad def lives.
|
|
104
128
|
*
|
|
105
|
-
* `
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* downgrading it to `ack` would be worse than useless: the Agent would be handed a tool whose
|
|
111
|
-
* contract — "this call returns the Machine's answer" — is a lie.
|
|
129
|
+
* `poll` is RESERVED, not implemented (ADR-0013): the wire leaves room for it — a surface listing
|
|
130
|
+
* ships each def's `semantics` — but nothing drains a steer inbox today. Registering one therefore
|
|
131
|
+
* FAILS here. Quietly downgrading it to `ack` would hand the Agent a tool whose contract is a lie.
|
|
132
|
+
* (A pick whose answer waits on the Machine's work needs no semantics of its own: every `ack` pick
|
|
133
|
+
* is answered at Rest, ADR-0024.)
|
|
112
134
|
*/
|
|
113
135
|
export function eventMap(machine: string, events: readonly unknown[]): Map<string, EventDef> {
|
|
114
136
|
const map = new Map<string, EventDef>();
|
|
@@ -120,8 +142,8 @@ export function eventMap(machine: string, events: readonly unknown[]): Map<strin
|
|
|
120
142
|
if (def.semantics !== "ack") {
|
|
121
143
|
throw new Error(
|
|
122
144
|
`machine "${machine}": event "${def.name}" is \`${def.semantics}\`, which is reserved but ` +
|
|
123
|
-
`NOT IMPLEMENTED (ADR-0013). Only \`ack\` events can be delivered today;
|
|
124
|
-
`
|
|
145
|
+
`NOT IMPLEMENTED (ADR-0013). Only \`ack\` events can be delivered today; nothing drains a ` +
|
|
146
|
+
`steer inbox, and degrading it to \`ack\` would hand the Agent a lying tool contract.`,
|
|
125
147
|
);
|
|
126
148
|
}
|
|
127
149
|
map.set(def.name, def);
|
package/src/examples.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// a workflow's own `jr2Setup({ events })` list (ADR-0015). ADR-0006's "standard library" framing
|
|
4
4
|
// is retired: nothing in jr2 consumes these by name.
|
|
5
5
|
//
|
|
6
|
-
// All `ack
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// All `ack`, as every pick is. The other semantics (`poll`) is reserved and NOT implemented
|
|
7
|
+
// (ADR-0013), so machine-build refuses it; an example that could not be used in a workflow would
|
|
8
|
+
// be a trap, not a starting point.
|
|
9
9
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { defineEvent } from "./define-event.ts";
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// The minimal shared vocabulary both sides must agree on to talk: the `defineEvent` mechanism
|
|
4
4
|
// (workflows declare their own control events — jr2 ships none, ADR-0011) and an example event
|
|
5
5
|
// set. A pure leaf — no MCP server, no flue, no xstate. (The old `mcpPath` addressing rule died
|
|
6
|
-
// with ADR-0013: the
|
|
6
|
+
// with ADR-0013: the Harness reads its Menu at `/agents/<iid>/surface` and builds its own paths.)
|
|
7
7
|
|
|
8
8
|
export * from "./define-event.ts";
|
|
9
9
|
export * from "./examples.ts";
|