pi-radar 0.2.0 → 0.3.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.
@@ -19,6 +19,70 @@ export interface TaskLaneStatus {
19
19
  available: boolean;
20
20
  error: string | null;
21
21
  }
22
+ /**
23
+ * Inputs for publishing an immutable handoff. The wire request ID carries the
24
+ * stable communication identity; the daemon derives the sender execution.
25
+ */
26
+ export interface HandoffPublishParamsDeserialize {
27
+ content: string;
28
+ correlation_id?: string | null;
29
+ recipient_subject_id: string;
30
+ sender_agent_id: string;
31
+ summary: string;
32
+ }
33
+ /**
34
+ * A bounded inbox page by stable handoff ID. Payloads are fetched one at a
35
+ * time with `communication.get`, keeping each response within the wire bound.
36
+ */
37
+ export interface HandoffInboxSerialize {
38
+ handoff_ids: string[];
39
+ }
40
+ /**
41
+ * Immutable handoff content. The request id is the stable communication id.
42
+ */
43
+ export interface HandoffSerialize {
44
+ content: string;
45
+ correlation_id?: string | null;
46
+ id: string;
47
+ published_at_unix_ms: number;
48
+ recipient_subject_id: string;
49
+ sender: HandoffIdentity;
50
+ summary: string;
51
+ version: number;
52
+ }
53
+ /**
54
+ * The current managed Pi execution that published or received a handoff.
55
+ * `subject_id` is stable across verified successors; `execution_id` and
56
+ * generation identify the exact execution that took the action.
57
+ */
58
+ export interface HandoffIdentity {
59
+ agent_id: string;
60
+ execution_generation: number;
61
+ execution_id: string;
62
+ session_id: string;
63
+ subject_id: string;
64
+ }
65
+ /**
66
+ * Durable recipient-consumer receipt, stored separately from immutable content.
67
+ */
68
+ export interface HandoffReceiptSerialize {
69
+ handoff_id: string;
70
+ received_at_unix_ms: number;
71
+ recipient: HandoffIdentity;
72
+ version: number;
73
+ }
74
+ /**
75
+ * The current managed Pi execution that published or received a handoff.
76
+ * `subject_id` is stable across verified successors; `execution_id` and
77
+ * generation identify the exact execution that took the action.
78
+ */
79
+ export interface HandoffIdentity {
80
+ agent_id: string;
81
+ execution_generation: number;
82
+ execution_id: string;
83
+ session_id: string;
84
+ subject_id: string;
85
+ }
22
86
  /**
23
87
  * Managed owner/parent metadata accepted with a spawn intent. Domain validation
24
88
  * and parent-binding checks remain in the daemon handler.
@@ -6,6 +6,10 @@ import type {
6
6
  DaemonPingSerialize,
7
7
  LaunchIdentityMarkerSerialize,
8
8
  FactsSnapshotDeserialize,
9
+ HandoffInboxSerialize,
10
+ HandoffReceiptSerialize,
11
+ HandoffPublishParamsDeserialize,
12
+ HandoffSerialize,
9
13
  ManagedLaunchMetadataDeserialize,
10
14
  SpawnExitObservationSerialize,
11
15
  } from "./generated-contracts.ts";
@@ -36,6 +40,7 @@ export const RADAR_SPAWN_CONTRACT = "spawn.v1";
36
40
  export const RADAR_SPAWN_CLOSE_CONTRACT = "spawn.close.v1";
37
41
  export const RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT = "spawn.launch_context.v1";
38
42
  export const RADAR_EVENTS_CONTRACT = "events.v1";
43
+ export const RADAR_COMMUNICATION_CONTRACT = "communication.handoff.v1";
39
44
  /** The default audit identity; callers can supply their own requester. */
40
45
  export const RADAR_REQUESTER = "pi-radar";
41
46
  /** The daemon's own line bound; nothing longer is sent or accepted. */
@@ -162,6 +167,9 @@ export type RadarBackgroundTask = NonNullable<
162
167
  FactsSnapshotDeserialize["background_tasks"]
163
168
  >[number];
164
169
  export type RadarFactsSnapshot = FactsSnapshotDeserialize;
170
+ export type RadarHandoff = HandoffSerialize;
171
+ export type RadarHandoffInbox = HandoffInboxSerialize;
172
+ export type RadarHandoffReceipt = HandoffReceiptSerialize;
165
173
 
166
174
  /**
167
175
  * The assignment channel's interim fields. `request` is the owner's active
@@ -508,6 +516,46 @@ function numberAt(value: unknown, field: string): number | undefined {
508
516
  : undefined;
509
517
  }
510
518
 
519
+ function handoffAt(value: unknown): HandoffSerialize | undefined {
520
+ const version = numberAt(value, "version");
521
+ const id = stringAt(value, "id");
522
+ const sender = recordAt(value, "sender");
523
+ const senderGeneration = numberAt(sender, "execution_generation");
524
+ const target = stringAt(value, "recipient_subject_id");
525
+ const summary = stringAt(value, "summary");
526
+ const content = stringAt(value, "content");
527
+ const published = numberAt(value, "published_at_unix_ms");
528
+ const correlation = typeof value === "object" && value !== null
529
+ ? (value as Record<string, unknown>).correlation_id
530
+ : undefined;
531
+ if (
532
+ version !== 1 || !isUuid(id) || !isUuid(stringAt(sender, "agent_id")) ||
533
+ !isUuid(stringAt(sender, "session_id")) || !isUuid(stringAt(sender, "subject_id")) ||
534
+ !isUuid(stringAt(sender, "execution_id")) || !Number.isSafeInteger(senderGeneration) ||
535
+ (senderGeneration ?? 0) < 1 || !isUuid(target) || summary === undefined || content === undefined ||
536
+ published === undefined || !Number.isSafeInteger(published) || published < 0 ||
537
+ (correlation !== undefined && correlation !== null &&
538
+ (typeof correlation !== "string" || correlation.length === 0 ||
539
+ Buffer.byteLength(correlation, "utf8") > 256 || /\p{Cc}/u.test(correlation)))
540
+ ) return undefined;
541
+ return value as HandoffSerialize;
542
+ }
543
+
544
+ function handoffReceiptAt(value: unknown): HandoffReceiptSerialize | undefined {
545
+ const version = numberAt(value, "version");
546
+ const handoffId = stringAt(value, "handoff_id");
547
+ const recipient = recordAt(value, "recipient");
548
+ const generation = numberAt(recipient, "execution_generation");
549
+ const received = numberAt(value, "received_at_unix_ms");
550
+ if (
551
+ version !== 1 || !isUuid(handoffId) || !isUuid(stringAt(recipient, "agent_id")) ||
552
+ !isUuid(stringAt(recipient, "session_id")) || !isUuid(stringAt(recipient, "subject_id")) ||
553
+ !isUuid(stringAt(recipient, "execution_id")) || !Number.isSafeInteger(generation) ||
554
+ (generation ?? 0) < 1 || received === undefined || !Number.isSafeInteger(received) || received < 0
555
+ ) return undefined;
556
+ return value as HandoffReceiptSerialize;
557
+ }
558
+
511
559
  /** A writer binding a channel or context reply hands back to its publisher. */
512
560
  function writerBinding(value: unknown): RadarWriterBinding | undefined {
513
561
  const handle = stringAt(value, "handle");
@@ -662,6 +710,7 @@ const RADAR_MANAGED_IDENTITY_ENV = new Set([
662
710
  "PI_RADAR_MANAGED_SUBJECT_ID",
663
711
  "PI_RADAR_MANAGED_LAUNCH_ID",
664
712
  "PI_RADAR_MANAGED_EXECUTION_GENERATION",
713
+ "PI_RADAR_MANAGED_PARENT_SUBJECT_ID",
665
714
  ]);
666
715
 
667
716
  /**
@@ -1015,6 +1064,12 @@ export type RadarClient = {
1015
1064
  agentGet(agent_id: string): Promise<RadarResult<RadarAgentProcess>>;
1016
1065
  observe(): Promise<RadarResult<RadarInventory>>;
1017
1066
  /** Read the durable event journal after `after`, waiting at most `wait_ms`. */
1067
+ communicationPublish(request: HandoffPublishParamsDeserialize & {
1068
+ request_id: string;
1069
+ }): Promise<RadarResult<{ handoff: HandoffSerialize; created: boolean }>>;
1070
+ communicationInbox(request: { agent_id: string }): Promise<RadarResult<HandoffInboxSerialize>>;
1071
+ communicationGet(request: { agent_id: string; handoff_id: string }): Promise<RadarResult<{ handoff: HandoffSerialize; receipt: HandoffReceiptSerialize | null }>>;
1072
+ communicationAck(request: { agent_id: string; handoff_id: string }): Promise<RadarResult<{ receipt: HandoffReceiptSerialize; created: boolean }>>;
1018
1073
  events(request: {
1019
1074
  after: number;
1020
1075
  epoch?: string;
@@ -1666,6 +1721,56 @@ export function createRadarClient(options: RadarClientOptions = {}): RadarClient
1666
1721
  return protocol("spawn.close reply carries no close outcome");
1667
1722
  });
1668
1723
  },
1724
+ async communicationPublish(request) {
1725
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
1726
+ const answer = await call("communication.publish", {
1727
+ sender_agent_id: request.sender_agent_id,
1728
+ recipient_subject_id: request.recipient_subject_id,
1729
+ ...(request.correlation_id !== undefined ? { correlation_id: request.correlation_id } : {}),
1730
+ summary: request.summary,
1731
+ content: request.content,
1732
+ }, request.request_id);
1733
+ if (answer.ok === false) return answer;
1734
+ const handoff = handoffAt(recordAt(answer.value, "handoff"));
1735
+ const created = recordAt(answer.value, "created");
1736
+ if (handoff === undefined || typeof created !== "boolean")
1737
+ return protocol("communication.publish reply carries no handoff");
1738
+ return { ok: true, value: { handoff, created } };
1739
+ });
1740
+ },
1741
+ async communicationInbox(request) {
1742
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
1743
+ const answer = await call("communication.inbox", request);
1744
+ if (answer.ok === false) return answer;
1745
+ const handoffIds = recordAt(answer.value, "handoff_ids");
1746
+ if (!Array.isArray(handoffIds) || handoffIds.some((id) => !isUuid(id)))
1747
+ return protocol("communication.inbox reply carries malformed handoff IDs");
1748
+ return { ok: true, value: answer.value as HandoffInboxSerialize };
1749
+ });
1750
+ },
1751
+ async communicationGet(request) {
1752
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
1753
+ const answer = await call("communication.get", request);
1754
+ if (answer.ok === false) return answer;
1755
+ const handoff = handoffAt(recordAt(answer.value, "handoff"));
1756
+ const receiptValue = recordAt(answer.value, "receipt");
1757
+ const receipt = receiptValue === null ? null : handoffReceiptAt(receiptValue);
1758
+ if (handoff === undefined || (receiptValue !== null && receipt === undefined))
1759
+ return protocol("communication.get reply carries malformed content or receipt");
1760
+ return { ok: true, value: { handoff, receipt: receipt ?? null } };
1761
+ });
1762
+ },
1763
+ async communicationAck(request) {
1764
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
1765
+ const answer = await call("communication.ack", request);
1766
+ if (answer.ok === false) return answer;
1767
+ const receipt = handoffReceiptAt(recordAt(answer.value, "receipt"));
1768
+ const created = recordAt(answer.value, "created");
1769
+ if (receipt === undefined || typeof created !== "boolean")
1770
+ return protocol("communication.ack reply carries no receipt");
1771
+ return { ok: true, value: { receipt, created } };
1772
+ });
1773
+ },
1669
1774
  async events(request) {
1670
1775
  if (
1671
1776
  !Number.isSafeInteger(request.after) ||
@@ -0,0 +1,66 @@
1
+ import type { HandoffInboxSerialize, HandoffSerialize, HandoffReceiptSerialize } from "./generated-contracts.ts";
2
+ import type { RadarClient, RadarResult } from "./radar-client.ts";
3
+ import type { RadarPublication } from "./radar-publication.ts";
4
+
5
+ export type RadarHandoffPort = {
6
+ /** Publish to this managed execution's Radar-recorded parent. Retry an uncertain result with the same ID and bytes. */
7
+ publish(request: {
8
+ request_id: string;
9
+ correlation_id?: string;
10
+ summary: string;
11
+ content: string;
12
+ }): Promise<RadarResult<{ handoff: HandoffSerialize; created: boolean }>>;
13
+ /** Durable inbox scan for this process's managed subject. Notifications are hints; callers can always rescan. */
14
+ inbox(): Promise<RadarResult<HandoffInboxSerialize>>;
15
+ /** Retrieve the full immutable content even after it has been acknowledged. */
16
+ get(handoff_id: string): Promise<RadarResult<{ handoff: HandoffSerialize; receipt: HandoffReceiptSerialize | null }>>;
17
+ /** Call only after the consumer durably recorded the retrieved payload. */
18
+ ack(handoff_id: string): Promise<RadarResult<{ receipt: HandoffReceiptSerialize; created: boolean }>>;
19
+ };
20
+
21
+ /**
22
+ * Bind handoff operations to this Pi process's Radar registration and the
23
+ * parent subject injected from its accepted managed-launch metadata.
24
+ */
25
+ export function createRadarHandoffPort(
26
+ client: RadarClient,
27
+ publication: Pick<RadarPublication, "subject">,
28
+ parentSubjectId: () => string | undefined = () => process.env.PI_RADAR_MANAGED_PARENT_SUBJECT_ID,
29
+ ): RadarHandoffPort {
30
+ async function agentId(): Promise<RadarResult<string>> {
31
+ const subject = await publication.subject();
32
+ return subject === undefined
33
+ ? { ok: false, code: "absent", message: "this process has no current Radar registration" }
34
+ : { ok: true, value: subject.agent_id };
35
+ }
36
+
37
+ return {
38
+ async publish(request) {
39
+ const identity = await agentId();
40
+ if (identity.ok === false) return identity;
41
+ const parent = parentSubjectId();
42
+ if (parent === undefined)
43
+ return { ok: false, code: "absent", message: "this managed execution has no Radar parent subject" };
44
+ return client.communicationPublish({
45
+ ...request,
46
+ sender_agent_id: identity.value,
47
+ recipient_subject_id: parent,
48
+ });
49
+ },
50
+ async inbox() {
51
+ const identity = await agentId();
52
+ if (identity.ok === false) return identity;
53
+ return client.communicationInbox({ agent_id: identity.value });
54
+ },
55
+ async get(handoff_id) {
56
+ const identity = await agentId();
57
+ if (identity.ok === false) return identity;
58
+ return client.communicationGet({ agent_id: identity.value, handoff_id });
59
+ },
60
+ async ack(handoff_id) {
61
+ const identity = await agentId();
62
+ if (identity.ok === false) return identity;
63
+ return client.communicationAck({ agent_id: identity.value, handoff_id });
64
+ },
65
+ };
66
+ }
@@ -64,9 +64,11 @@ export type LaunchMailbox<State extends LaunchState> = {
64
64
  * different leaves the launch unresolved rather than placed.
65
65
  */
66
66
 
67
- /** The exact parent this launch may use, and nothing inferred about it. */
67
+ /** The exact managed parent this launch may use, and nothing inferred about it. */
68
68
  export type RadarLaunchParent = {
69
69
  agent_id: string;
70
+ /** Stable subject from this process's current Radar registration. */
71
+ subject_id: string;
70
72
  location?: RadarLocation;
71
73
  };
72
74
 
@@ -395,6 +397,10 @@ export async function launchRadarWorker<State extends LaunchState>(
395
397
  return refusedLaunch(
396
398
  "the launch has no exact parent registration to spawn under",
397
399
  );
400
+ if (!isUuid(launch.parent.subject_id))
401
+ return refusedLaunch(
402
+ "the launch has no managed Radar subject for its parent",
403
+ );
398
404
  // A spawn is created under the pane its parent registered, and this port
399
405
  // registers no location it was not given: without one there is nothing to
400
406
  // place the child beside, and a name or session lookup is not a substitute.
@@ -408,6 +414,7 @@ export async function launchRadarWorker<State extends LaunchState>(
408
414
  parent: launch.parent.agent_id,
409
415
  parent_session_id: launch.parent_session_id,
410
416
  executable: launch.executable,
417
+ managed: { parent_subject_id: launch.parent.subject_id },
411
418
  argv: launch.argv,
412
419
  ...(launch.cwd !== "" ? { cwd: launch.cwd } : {}),
413
420
  ...(Object.keys(launch.env).length > 0 ? { env: launch.env } : {}),
@@ -1318,6 +1318,8 @@ export type RadarPublication = {
1318
1318
  /** What a process's own registration resolved to, and where it says it is. */
1319
1319
  export type RadarSubject = {
1320
1320
  agent_id: string;
1321
+ /** Stable managed logical subject, present only for a Radar-issued launch. */
1322
+ subject_id?: string;
1321
1323
  location?: RadarLocation;
1322
1324
  };
1323
1325
 
@@ -1716,8 +1718,10 @@ function createRadarPublicationInner(
1716
1718
  const record = await ensureRegistration();
1717
1719
  if (record === undefined) return undefined;
1718
1720
  const location = record.registration.location;
1721
+ const subjectId = process.env.PI_RADAR_MANAGED_SUBJECT_ID;
1719
1722
  return {
1720
1723
  agent_id: agentId,
1724
+ ...(subjectId !== undefined ? { subject_id: subjectId } : {}),
1721
1725
  ...(location !== undefined ? { location } : {}),
1722
1726
  };
1723
1727
  },
@@ -1,14 +1,17 @@
1
1
  import type { RadarExecutionAdapter } from "./radar-execution.ts";
2
+ import type { RadarHandoffPort } from "./radar-communication.ts";
2
3
  import type { RadarPublication } from "./radar-publication.ts";
3
4
 
4
5
  /** The event channel through which Pi extensions obtain the baseline runtime. */
5
6
  export const RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
6
- export const RADAR_RUNTIME_VERSION = 1 as const;
7
+ export const RADAR_RUNTIME_VERSION = 2 as const;
7
8
 
8
9
  /** The sole baseline publisher's session-scoped runtime, shared with consumers. */
9
10
  export type RadarRuntimeServices = {
10
11
  /** The one baseline publication; Herdsman contributes through its channels. */
11
12
  publication: RadarPublication;
13
+ /** Durable handoffs bound to this process's registration and managed parent. */
14
+ communication: RadarHandoffPort;
12
15
  execution: RadarExecutionAdapter;
13
16
  };
14
17
 
package/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * as client from "./extension/radar-client.ts";
2
+ export * as communication from "./extension/radar-communication.ts";
2
3
  export * as execution from "./extension/radar-execution.ts";
3
4
  export * as inspection from "./extension/radar-inspection.ts";
4
5
  export * as launch from "./extension/radar-launch.ts";
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "pi-radar",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
- "description": "Radar daemon client, publication, execution, location, launch and inspection surface for Pi extensions.",
5
+ "description": "Radar daemon client, communication, publication, execution, location, launch and inspection surface for Pi extensions.",
6
6
  "license": "Apache-2.0",
7
7
  "engines": {
8
8
  "node": ">=22.19.0"
@@ -25,6 +25,10 @@
25
25
  "types": "./extension/radar-client.ts",
26
26
  "import": "./dist/extension/radar-client.js"
27
27
  },
28
+ "./communication": {
29
+ "types": "./extension/radar-communication.ts",
30
+ "import": "./dist/extension/radar-communication.js"
31
+ },
28
32
  "./execution": {
29
33
  "types": "./extension/radar-execution.ts",
30
34
  "import": "./dist/extension/radar-execution.js"
@@ -67,6 +71,7 @@
67
71
  "schema/public/v1/**",
68
72
  "extension/generated-contracts.ts",
69
73
  "extension/radar-client.ts",
74
+ "extension/radar-communication.ts",
70
75
  "extension/radar-execution.ts",
71
76
  "extension/radar-inspection.ts",
72
77
  "extension/radar-launch.ts",
@@ -77,10 +82,10 @@
77
82
  "extension/baseline.ts"
78
83
  ],
79
84
  "scripts": {
80
- "build": "esbuild index.ts extension/generated-contracts.ts extension/radar-client.ts extension/radar-execution.ts extension/radar-inspection.ts extension/radar-launch.ts extension/radar-location.ts extension/radar-publication.ts extension/radar-session-marker.ts extension/radar-runtime.ts extension/baseline.ts --outbase=. --outdir=dist --bundle --platform=node --format=esm --target=node22",
85
+ "build": "esbuild index.ts extension/generated-contracts.ts extension/radar-client.ts extension/radar-communication.ts extension/radar-execution.ts extension/radar-inspection.ts extension/radar-launch.ts extension/radar-location.ts extension/radar-publication.ts extension/radar-session-marker.ts extension/radar-runtime.ts extension/baseline.ts --outbase=. --outdir=dist --bundle --platform=node --format=esm --target=node22",
81
86
  "generate:contracts": "node tools/generate-contracts.mjs",
82
87
  "check:contracts": "node tools/generate-contracts.mjs --check",
83
- "typecheck:contracts": "tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext --moduleResolution NodeNext --allowImportingTsExtensions --types node index.ts extension/baseline.ts extension/generated-contracts.ts extension/generated-contracts.typecheck.ts extension/radar-client.ts extension/radar-execution.ts extension/radar-publication.ts extension/radar-session-marker.ts extension/radar-session-marker.test.ts extension/radar-runtime.ts extension/radar-runtime.test.ts",
88
+ "typecheck:contracts": "tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext --moduleResolution NodeNext --allowImportingTsExtensions --types node index.ts extension/baseline.ts extension/generated-contracts.ts extension/generated-contracts.typecheck.ts extension/radar-client.ts extension/radar-communication.ts extension/radar-execution.ts extension/radar-launch.test.ts extension/radar-publication.ts extension/radar-session-marker.ts extension/radar-session-marker.test.ts extension/radar-runtime.ts extension/radar-runtime.test.ts",
84
89
  "prepack": "npm run build",
85
90
  "test": "npm run check:contracts && npm run typecheck:contracts && node --experimental-strip-types --test extension/*.test.ts"
86
91
  },
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "additionalProperties": false,
4
+ "description": "A bounded inbox page by stable handoff ID. Payloads are fetched one at a\ntime with `communication.get`, keeping each response within the wire bound.",
5
+ "properties": {
6
+ "handoff_ids": {
7
+ "items": {
8
+ "type": "string"
9
+ },
10
+ "type": "array"
11
+ }
12
+ },
13
+ "required": [
14
+ "handoff_ids"
15
+ ],
16
+ "title": "handoff-inbox-deserialize",
17
+ "type": "object"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "additionalProperties": false,
4
+ "description": "A bounded inbox page by stable handoff ID. Payloads are fetched one at a\ntime with `communication.get`, keeping each response within the wire bound.",
5
+ "properties": {
6
+ "handoff_ids": {
7
+ "items": {
8
+ "type": "string"
9
+ },
10
+ "type": "array"
11
+ }
12
+ },
13
+ "required": [
14
+ "handoff_ids"
15
+ ],
16
+ "title": "handoff-inbox-serialize",
17
+ "type": "object"
18
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "additionalProperties": false,
4
+ "description": "Inputs for publishing an immutable handoff. The wire request ID carries the\nstable communication identity; the daemon derives the sender execution.",
5
+ "properties": {
6
+ "content": {
7
+ "type": "string"
8
+ },
9
+ "correlation_id": {
10
+ "type": [
11
+ "string",
12
+ "null"
13
+ ]
14
+ },
15
+ "recipient_subject_id": {
16
+ "type": "string"
17
+ },
18
+ "sender_agent_id": {
19
+ "type": "string"
20
+ },
21
+ "summary": {
22
+ "type": "string"
23
+ }
24
+ },
25
+ "required": [
26
+ "sender_agent_id",
27
+ "recipient_subject_id",
28
+ "summary",
29
+ "content"
30
+ ],
31
+ "title": "handoff-publish-params-deserialize",
32
+ "type": "object"
33
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "additionalProperties": false,
4
+ "description": "Inputs for publishing an immutable handoff. The wire request ID carries the\nstable communication identity; the daemon derives the sender execution.",
5
+ "properties": {
6
+ "content": {
7
+ "type": "string"
8
+ },
9
+ "correlation_id": {
10
+ "type": [
11
+ "string",
12
+ "null"
13
+ ]
14
+ },
15
+ "recipient_subject_id": {
16
+ "type": "string"
17
+ },
18
+ "sender_agent_id": {
19
+ "type": "string"
20
+ },
21
+ "summary": {
22
+ "type": "string"
23
+ }
24
+ },
25
+ "required": [
26
+ "sender_agent_id",
27
+ "recipient_subject_id",
28
+ "summary",
29
+ "content"
30
+ ],
31
+ "title": "handoff-publish-params-serialize",
32
+ "type": "object"
33
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "$defs": {
3
+ "HandoffIdentity": {
4
+ "additionalProperties": false,
5
+ "description": "The current managed Pi execution that published or received a handoff.\n`subject_id` is stable across verified successors; `execution_id` and\ngeneration identify the exact execution that took the action.",
6
+ "properties": {
7
+ "agent_id": {
8
+ "type": "string"
9
+ },
10
+ "execution_generation": {
11
+ "format": "uint32",
12
+ "maximum": 4294967295,
13
+ "minimum": 1,
14
+ "type": "integer"
15
+ },
16
+ "execution_id": {
17
+ "type": "string"
18
+ },
19
+ "session_id": {
20
+ "type": "string"
21
+ },
22
+ "subject_id": {
23
+ "type": "string"
24
+ }
25
+ },
26
+ "required": [
27
+ "agent_id",
28
+ "session_id",
29
+ "subject_id",
30
+ "execution_id",
31
+ "execution_generation"
32
+ ],
33
+ "type": "object"
34
+ }
35
+ },
36
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
37
+ "additionalProperties": false,
38
+ "description": "Durable recipient-consumer receipt, stored separately from immutable content.",
39
+ "properties": {
40
+ "handoff_id": {
41
+ "type": "string"
42
+ },
43
+ "received_at_unix_ms": {
44
+ "format": "uint64",
45
+ "maximum": 9007199254740991,
46
+ "minimum": 0,
47
+ "type": "integer"
48
+ },
49
+ "recipient": {
50
+ "$ref": "#/$defs/HandoffIdentity"
51
+ },
52
+ "version": {
53
+ "format": "uint32",
54
+ "minimum": 0,
55
+ "type": "integer"
56
+ }
57
+ },
58
+ "required": [
59
+ "version",
60
+ "handoff_id",
61
+ "recipient",
62
+ "received_at_unix_ms"
63
+ ],
64
+ "title": "handoff-receipt-deserialize",
65
+ "type": "object"
66
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "$defs": {
3
+ "HandoffIdentity": {
4
+ "additionalProperties": false,
5
+ "description": "The current managed Pi execution that published or received a handoff.\n`subject_id` is stable across verified successors; `execution_id` and\ngeneration identify the exact execution that took the action.",
6
+ "properties": {
7
+ "agent_id": {
8
+ "type": "string"
9
+ },
10
+ "execution_generation": {
11
+ "format": "uint32",
12
+ "maximum": 4294967295,
13
+ "minimum": 1,
14
+ "type": "integer"
15
+ },
16
+ "execution_id": {
17
+ "type": "string"
18
+ },
19
+ "session_id": {
20
+ "type": "string"
21
+ },
22
+ "subject_id": {
23
+ "type": "string"
24
+ }
25
+ },
26
+ "required": [
27
+ "agent_id",
28
+ "session_id",
29
+ "subject_id",
30
+ "execution_id",
31
+ "execution_generation"
32
+ ],
33
+ "type": "object"
34
+ }
35
+ },
36
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
37
+ "additionalProperties": false,
38
+ "description": "Durable recipient-consumer receipt, stored separately from immutable content.",
39
+ "properties": {
40
+ "handoff_id": {
41
+ "type": "string"
42
+ },
43
+ "received_at_unix_ms": {
44
+ "format": "uint64",
45
+ "maximum": 9007199254740991,
46
+ "minimum": 0,
47
+ "type": "integer"
48
+ },
49
+ "recipient": {
50
+ "$ref": "#/$defs/HandoffIdentity"
51
+ },
52
+ "version": {
53
+ "format": "uint32",
54
+ "minimum": 0,
55
+ "type": "integer"
56
+ }
57
+ },
58
+ "required": [
59
+ "version",
60
+ "handoff_id",
61
+ "recipient",
62
+ "received_at_unix_ms"
63
+ ],
64
+ "title": "handoff-receipt-serialize",
65
+ "type": "object"
66
+ }