@haywoodmarx/agent-pager 0.1.2 → 0.2.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.
Files changed (68) hide show
  1. package/README.md +5 -5
  2. package/dist/src/broker/address.d.ts +7 -0
  3. package/dist/src/broker/address.js +9 -0
  4. package/dist/src/broker/broker.d.ts +91 -23
  5. package/dist/src/broker/broker.js +505 -123
  6. package/dist/src/broker/operator-projection.js +3 -5
  7. package/dist/src/broker/protocol.d.ts +1 -1
  8. package/dist/src/broker/protocol.js +1 -1
  9. package/dist/src/broker/schema.d.ts +4 -0
  10. package/dist/src/broker/schema.js +9 -6
  11. package/dist/src/broker/snapshot.d.ts +4 -2
  12. package/dist/src/implementations/claude/gateway-cli.js +1 -2
  13. package/dist/src/implementations/claude/gateway.d.ts +11 -4
  14. package/dist/src/implementations/claude/gateway.js +44 -14
  15. package/dist/src/implementations/claude/instructions.js +1 -1
  16. package/dist/src/implementations/model.d.ts +6 -3
  17. package/dist/src/implementations/opencode/plugin.d.ts +7 -2
  18. package/dist/src/implementations/opencode/plugin.js +103 -7
  19. package/dist/src/implementations/pi/broker-client.d.ts +13 -4
  20. package/dist/src/implementations/pi/broker-client.js +71 -9
  21. package/dist/src/implementations/pi/extension.d.ts +7 -2
  22. package/dist/src/implementations/pi/extension.js +15 -3
  23. package/dist/src/index.d.ts +1 -1
  24. package/dist/src/index.js +1 -1
  25. package/dist/src/launch/broker-lock.d.ts +64 -4
  26. package/dist/src/launch/broker-lock.js +149 -7
  27. package/dist/src/launch/cli.js +5 -2
  28. package/dist/src/launch/launch.d.ts +1 -1
  29. package/dist/src/launch/on-demand.d.ts +8 -8
  30. package/dist/src/launch/on-demand.js +54 -35
  31. package/dist/src/launch/runtime-composition.js +2 -0
  32. package/dist/src/launch/runtime.js +4 -14
  33. package/dist/src/launch/service.d.ts +1 -0
  34. package/dist/src/launch/service.js +23 -1
  35. package/dist/src/launch/workspace-branch.d.ts +3 -3
  36. package/dist/src/launch/workspace-branch.js +7 -15
  37. package/dist/src/launch/workspace-root.d.ts +13 -0
  38. package/dist/src/launch/workspace-root.js +27 -0
  39. package/dist/src/messaging/delivery-client.d.ts +17 -0
  40. package/dist/src/messaging/delivery-client.js +31 -1
  41. package/dist/src/messaging/identity-statement.d.ts +7 -0
  42. package/dist/src/messaging/identity-statement.js +8 -0
  43. package/dist/src/messaging/mcp.d.ts +8 -2
  44. package/dist/src/messaging/mcp.js +54 -1
  45. package/dist/src/messaging/notifications.d.ts +31 -3
  46. package/dist/src/messaging/notifications.js +25 -0
  47. package/dist/src/messaging/push-delivery.d.ts +2 -1
  48. package/dist/src/messaging/push-delivery.js +24 -7
  49. package/dist/src/messaging/tools.js +22 -2
  50. package/dist/src/messaging/wake.js +7 -0
  51. package/dist/src/operator/agent-projection.d.ts +8 -5
  52. package/dist/src/operator/agent-projection.js +29 -9
  53. package/dist/src/operator/protocol.d.ts +4 -2
  54. package/dist/src/operator/repository-system.d.ts +29 -0
  55. package/dist/src/operator/repository-system.js +39 -12
  56. package/dist/src/operator/repository.d.ts +28 -11
  57. package/dist/src/operator/repository.js +37 -4
  58. package/dist/src/operator/updates.js +2 -0
  59. package/dist/src/operator/validation.js +11 -1
  60. package/dist/src/shared/versions.d.ts +18 -0
  61. package/dist/src/shared/versions.js +26 -0
  62. package/dist/src/tui/agent-tree.js +2 -2
  63. package/dist/src/tui/app.js +3 -2
  64. package/dist/src/tui/model.js +1 -1
  65. package/dist/src/version.d.ts +7 -0
  66. package/dist/src/version.js +14 -6
  67. package/package.json +1 -1
  68. package/skills/pager/SKILL.md +35 -45
@@ -1,4 +1,36 @@
1
1
  import { createHash } from "node:crypto";
2
+ /**
3
+ * Wrap a Git port so each path is inspected at most once per expiry window.
4
+ *
5
+ * A thrown inspection is remembered like an answered one, and rethrown for the rest of its window.
6
+ * @param git - Port that performs the inspection when nothing current is remembered.
7
+ * @param options - Clock and expiry window.
8
+ * @returns A port answering repeated requests for one path from the last inspection within the window.
9
+ */
10
+ export function rememberRepositoryInspections(git, options) {
11
+ const remembered = new Map();
12
+ const inspectAndRemember = (path, now) => {
13
+ let outcome;
14
+ try {
15
+ outcome = { kind: "answered", inspection: git.inspect(path) };
16
+ }
17
+ catch (error) {
18
+ outcome = { kind: "threw", error };
19
+ }
20
+ remembered.set(path, { outcome, expiresAt: now + options.ttlMs });
21
+ return outcome;
22
+ };
23
+ return {
24
+ inspect: (path) => {
25
+ const now = options.now();
26
+ const current = remembered.get(path);
27
+ const outcome = current && current.expiresAt > now ? current.outcome : inspectAndRemember(path, now);
28
+ if (outcome.kind === "threw")
29
+ throw outcome.error;
30
+ return outcome.inspection;
31
+ },
32
+ };
33
+ }
2
34
  /** Reports invalid or unavailable repository projection inputs. */
3
35
  class RepositoryProjectionError extends Error {
4
36
  }
@@ -17,14 +49,15 @@ export class RepositoryDiscoveryError extends RepositoryProjectionError {
17
49
  }
18
50
  }
19
51
  /**
20
- * Project one broker workspace into repository grouping and worktree detail.
52
+ * Project one directory, a broker workspace or an agent's working directory, into repository
53
+ * grouping and worktree detail.
21
54
  *
22
- * A workspace whose path no longer resolves is projected from its recorded path alone. Neither
55
+ * A directory whose path no longer resolves is projected from its recorded path alone. Neither
23
56
  * canonicalization nor Git discovery can run against a removed directory, and both throw rather
24
57
  * than reporting absence, so the removal is detected before either is attempted.
25
- * @param workspacePath - Absolute broker workspace path to inspect.
58
+ * @param workspacePath - Absolute broker workspace path, or agent working directory, to inspect.
26
59
  * @param dependencies - Explicit filesystem and Git discovery collaborators.
27
- * @returns Presentation-only repository and worktree values, and whether the workspace still resolves.
60
+ * @returns Presentation-only repository and worktree values, and whether the directory still resolves.
28
61
  */
29
62
  export function projectRepositoryLocation(workspacePath, dependencies) {
30
63
  if (!dependencies.fileSystem.exists(workspacePath)) {
@@ -2,6 +2,7 @@ import { projectOperatorAgents } from "./agent-projection.js";
2
2
  import { OPERATOR_PROTOCOL_VERSION, } from "./protocol.js";
3
3
  import { operatorDiagnostic } from "./diagnostics.js";
4
4
  import { projectOperatorTraffic } from "./traffic-projection.js";
5
+ import { PAGER_VERSION } from "../version.js";
5
6
  /** Bounds retained stream-integrity diagnostics. */
6
7
  const UPDATE_DIAGNOSTIC_LIMIT = 64;
7
8
  /** Broker tables whose rows feed the repository and agent halves of an operator snapshot. */
@@ -181,6 +182,7 @@ export class OperatorUpdateService {
181
182
  return {
182
183
  protocolVersion: OPERATOR_PROTOCOL_VERSION,
183
184
  epoch: cursor.epoch,
185
+ brokerVersion: PAGER_VERSION,
184
186
  cursor,
185
187
  generatedAt: this.#now(),
186
188
  repositories: projected.repositories,
@@ -173,7 +173,16 @@ export function validateOperatorSnapshot(value) {
173
173
  const record = readRecord(value, "snapshot");
174
174
  exactFields({
175
175
  record,
176
- required: ["protocolVersion", "epoch", "cursor", "generatedAt", "repositories", "agents", "traffic"],
176
+ required: [
177
+ "protocolVersion",
178
+ "epoch",
179
+ "brokerVersion",
180
+ "cursor",
181
+ "generatedAt",
182
+ "repositories",
183
+ "agents",
184
+ "traffic",
185
+ ],
177
186
  optional: [],
178
187
  path: "snapshot",
179
188
  });
@@ -207,6 +216,7 @@ export function validateOperatorSnapshot(value) {
207
216
  const snapshot = {
208
217
  protocolVersion,
209
218
  epoch,
219
+ brokerVersion: readNonemptyString(record.brokerVersion, "snapshot.brokerVersion"),
210
220
  cursor,
211
221
  generatedAt: readTimestamp(record.generatedAt, "snapshot.generatedAt"),
212
222
  repositories,
@@ -0,0 +1,18 @@
1
+ /** A `major.minor.patch` version as three numbers. */
2
+ export type NumericVersion = readonly [number, number, number];
3
+ /**
4
+ * Read the `major.minor.patch` numbers of a version string.
5
+ * @param version - Version text, such as a runtime's reported version or a package release.
6
+ * @param extent - `prefix` accepts trailing text such as a prerelease or build suffix and ignores it;
7
+ * `exact` rejects any version carrying one.
8
+ * @returns - The three numbers, or undefined when the text does not start with, or is not exactly, a
9
+ * numeric version.
10
+ */
11
+ export declare function parseNumericVersion(version: string, extent: "prefix" | "exact"): NumericVersion | undefined;
12
+ /**
13
+ * Order two numeric versions.
14
+ * @param left - First version.
15
+ * @param right - Second version.
16
+ * @returns - Negative when `left` is lower, positive when it is higher, zero when they are equal.
17
+ */
18
+ export declare function compareNumericVersions(left: NumericVersion, right: NumericVersion): number;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Read the `major.minor.patch` numbers of a version string.
3
+ * @param version - Version text, such as a runtime's reported version or a package release.
4
+ * @param extent - `prefix` accepts trailing text such as a prerelease or build suffix and ignores it;
5
+ * `exact` rejects any version carrying one.
6
+ * @returns - The three numbers, or undefined when the text does not start with, or is not exactly, a
7
+ * numeric version.
8
+ */
9
+ export function parseNumericVersion(version, extent) {
10
+ const match = (extent === "exact" ? /^(\d+)\.(\d+)\.(\d+)$/u : /^(\d+)\.(\d+)\.(\d+)/u).exec(version);
11
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined;
12
+ }
13
+ /**
14
+ * Order two numeric versions.
15
+ * @param left - First version.
16
+ * @param right - Second version.
17
+ * @returns - Negative when `left` is lower, positive when it is higher, zero when they are equal.
18
+ */
19
+ export function compareNumericVersions(left, right) {
20
+ for (let index = 0; index < left.length; index += 1) {
21
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
22
+ if (difference !== 0)
23
+ return difference;
24
+ }
25
+ return 0;
26
+ }
@@ -611,8 +611,8 @@ function archiveStatusLabel(agent) {
611
611
  return "archived by operator";
612
612
  case "lifecycle":
613
613
  return agent.lifecycle;
614
- case "workspace_removed":
615
- return "workspace removed";
614
+ case "directory_removed":
615
+ return "directory removed";
616
616
  }
617
617
  }
618
618
  const TREE_STATE_COLUMN = 2;
@@ -8,6 +8,7 @@ import { Line, Overlay, PaneBar, Rule, VERTICAL_SCROLL_STYLE, useTuiModel } from
8
8
  import { AgentTrafficLog, MessageDetail, MessageOverlays, selectNewestTuiTraffic, stepTuiTrafficSelection, } from "./message-views.js";
9
9
  import { AGENT_TREE_COLUMNS, sidePaneWidths, MESSAGE_LOG_COLUMNS, PORTRAIT_BREAKPOINT_COLUMNS, agentColor, activeAgentCount, agentLogScope, brokerIndicator, COLLAPSED_LOG_ROWS, describeCommandOutcome, diagnosticColor, fitBarLeading, formatClock, formatDiagnostic, selectedMessage, selectMessageBodyRows, selectTuiTraffic, } from "./presentation.js";
10
10
  import { dimTuiTheme } from "./theme.js";
11
+ import { PAGER_VERSION } from "../version.js";
11
12
  /* eslint-enable jsdoc/require-jsdoc -- The collaborator declarations end here. */
12
13
  /** Rows the wide layout spends on fixed chrome rather than pane content. */
13
14
  const WIDE_PANE_CHROME_ROWS = 7;
@@ -274,11 +275,11 @@ function FollowFooter({ state, theme }) {
274
275
  }
275
276
  function StatusBar({ state, theme }) {
276
277
  const broker = brokerIndicator(state.connection, theme);
277
- return (_jsxs("box", { height: 1, flexShrink: 0, flexDirection: "row", paddingLeft: 1, paddingRight: 1, justifyContent: "space-between", children: [_jsx("text", { fg: theme.structure.emphasis, children: "pager" }), _jsx("box", { flexDirection: "row", children: _jsxs("text", { fg: theme.structure.rulesAndLabels, children: [_jsx("span", { fg: broker.color, children: broker.glyph }), " broker ", `${String(state.unreadMessageIds.size)} unread ${String(activeAgentCount(state))} agents`] }) })] }));
278
+ return (_jsxs("box", { height: 1, flexShrink: 0, flexDirection: "row", paddingLeft: 1, paddingRight: 1, justifyContent: "space-between", children: [_jsx("text", { fg: theme.structure.emphasis, children: "pager" }), _jsx("box", { flexDirection: "row", children: _jsxs("text", { fg: theme.structure.rulesAndLabels, children: [_jsx("span", { fg: broker.color, children: broker.glyph }), " broker ", `${String(state.unreadMessageIds.size)} unread ${String(activeAgentCount(state))} agents v${PAGER_VERSION}`] }) })] }));
278
279
  }
279
280
  function PortraitStatusBar({ state, theme }) {
280
281
  const broker = brokerIndicator(state.connection, theme);
281
- return (_jsx(PaneBar, { left: _jsx("text", { fg: theme.structure.emphasis, children: "pager" }), right: _jsxs("text", { fg: theme.structure.rulesAndLabels, children: [_jsx("span", { fg: broker.color, children: broker.glyph }), ` ${String(state.unreadMessageIds.size)} unread · ${String(activeAgentCount(state))} agents`] }) }));
282
+ return (_jsx(PaneBar, { left: _jsx("text", { fg: theme.structure.emphasis, children: "pager" }), right: _jsxs("text", { fg: theme.structure.rulesAndLabels, children: [_jsx("span", { fg: broker.color, children: broker.glyph }), ` ${String(state.unreadMessageIds.size)} unread · ${String(activeAgentCount(state))} agents · v${PAGER_VERSION}`] }) }));
282
283
  }
283
284
  const HELP_KEYS = [
284
285
  ["↑↓", "move within the focused pane"],
@@ -349,7 +349,7 @@ export class TuiOperatorModel {
349
349
  if (!this.#active(generation))
350
350
  return;
351
351
  if (this.#state.connection !== "healthy")
352
- this.reportDiagnostic("info", "connection", `connected: snapshot at sequence ${String(snapshot.cursor.sequence)}`);
352
+ this.reportDiagnostic("info", "connection", `connected: broker ${snapshot.brokerVersion}, snapshot at sequence ${String(snapshot.cursor.sequence)}`);
353
353
  // A fetched snapshot is installed as a fresh start rather than a replacement, so unread identities
354
354
  // begin empty from it.
355
355
  this.observeSnapshot(structuredClone(snapshot), false);
@@ -9,3 +9,10 @@
9
9
  export declare function manifestVersion(contents: string, source: string): string;
10
10
  /** Names the published package version, read once from the nearest `package.json`. */
11
11
  export declare const PAGER_VERSION: string;
12
+ /**
13
+ * Reports whether this code runs from a git checkout, detected by a `.git` entry beside
14
+ * `package.json`, which a published tarball never carries.
15
+ *
16
+ * A checkout's version names its last release, not the unreleased code it runs.
17
+ */
18
+ export declare const PAGER_SOURCE_CHECKOUT: boolean;
@@ -1,4 +1,4 @@
1
- import { readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { isPlainObject } from "./shared/guards.js";
@@ -24,19 +24,19 @@ export function manifestVersion(contents, source) {
24
24
  return parsed.version;
25
25
  }
26
26
  /**
27
- * Read the published package version from the nearest `package.json` above this file.
27
+ * Find the nearest `package.json` above this file and read the version it declares.
28
28
  *
29
29
  * Walks up rather than using one fixed relative path: this module runs both directly from `src/`
30
30
  * (Bun executing TypeScript unbuilt) and from the compiled `dist/src/` tree, one directory deeper, so
31
31
  * no single relative path reaches `package.json` in both.
32
- * @returns - The version field from the nearest ancestor `package.json`.
32
+ * @returns - The directory holding that `package.json`, and its version field.
33
33
  */
34
- function readPackageVersion() {
34
+ function readPackage() {
35
35
  let directory = dirname(fileURLToPath(import.meta.url));
36
36
  for (;;) {
37
37
  try {
38
38
  const path = join(directory, "package.json");
39
- return manifestVersion(readFileSync(path, "utf8"), path);
39
+ return { root: directory, version: manifestVersion(readFileSync(path, "utf8"), path) };
40
40
  }
41
41
  catch (error) {
42
42
  if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT"))
@@ -48,5 +48,13 @@ function readPackageVersion() {
48
48
  directory = parent;
49
49
  }
50
50
  }
51
+ const PACKAGE = readPackage();
51
52
  /** Names the published package version, read once from the nearest `package.json`. */
52
- export const PAGER_VERSION = readPackageVersion();
53
+ export const PAGER_VERSION = PACKAGE.version;
54
+ /**
55
+ * Reports whether this code runs from a git checkout, detected by a `.git` entry beside
56
+ * `package.json`, which a published tarball never carries.
57
+ *
58
+ * A checkout's version names its last release, not the unreleased code it runs.
59
+ */
60
+ export const PAGER_SOURCE_CHECKOUT = existsSync(join(PACKAGE.root, ".git"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haywoodmarx/agent-pager",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Durable local messaging between agent implementations.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -5,18 +5,17 @@ description: Send and receive durable messages between agents over the Pager MCP
5
5
 
6
6
  # Pager
7
7
 
8
+ This skill covers receiving and acknowledging messages, your identity, sending, and what each error code asks you to do.
9
+
8
10
  ## When a message arrives
9
11
 
10
- Pager delivers to your session. Something arrives in one of two forms:
12
+ Pager delivers to your session in one of two forms:
11
13
 
12
- ```
13
- Something arrived — which form?
14
- ├─ a delivery whose first line is "pager push", carrying messages already claimed for you,
15
- │ each with its claimToken
16
- │ └─ ack, then work
17
- └─ a body-free line beginning "pager-inbox" with reason "operator_ping"
18
- └─ a human pinged you. Nothing to fetch
19
- ```
14
+ - A delivery whose first line is `pager push` carries messages already claimed for you, each with its `claimToken`. Acknowledge them, then do the work.
15
+ - A body-free line beginning `pager-inbox` with reason `operator_ping` means a human pinged you. There is nothing to fetch.
16
+ - A body-free line beginning `pager-inbox` with reason `session_claimed` means another session took your alias over and ended your session. Your next call runs as a fresh identity. There is nothing to fetch, and messages for the old alias now go to the new holder.
17
+
18
+ The calls:
20
19
 
21
20
  ```jsonc
22
21
  // only to recover a claim_expired message: fetch_agent_messages { "limit": 50 } →
@@ -40,34 +39,27 @@ A token in `rejected` acknowledged nothing. Its `reason` says what to do:
40
39
  | `foreign_token` | The token belongs to another agent. Ack only tokens delivered to you |
41
40
  | `unknown_token` | No such token. Check that it was copied whole from the claim |
42
41
 
43
- Ack as soon as you hold a message, within 5 minutes, or it is redelivered. Ack means received and
44
- yours to act on, not finished. Redelivery backs off and gives up after 24 hours from that message's
45
- arrival: one still unclaimed past that budget is dead-lettered, with no wake to the recipient.
42
+ Ack as soon as you hold a message, within 5 minutes, or the broker redelivers it. An ack means the message is received and yours to act on, not that the work is finished. The broker retries redelivery with growing delays for up to 24 hours from the message's arrival. It dead-letters a message still unclaimed after that, without waking the recipient.
46
43
 
47
- A message carrying `to: *@workspace` (`broadcastAddress` in a fetch) is one copy of a broadcast to
48
- that workspace's agents. A reply to its `fromAddress` reaches the sender alone.
44
+ A message carrying `to: *@workspace`, shown as `broadcastAddress` in a fetch, is one copy of a broadcast to that workspace's agents. A reply to its `fromAddress` reaches the sender alone.
49
45
 
50
- ## Identity
46
+ ## Your identity and alias
51
47
 
52
- `whoami` returns your agent id, current alias, and prior aliases.
48
+ Call `whoami` for the address an agent in another workspace uses to reach you.
53
49
 
54
- `update_agent_alias` replaces your alias and durable session name; it fails with `alias_conflict` if
55
- the alias is taken. Prior names stay reserved, so a stale sender gets `recipient_alias_changed` rather
56
- than a silent redirect.
50
+ `update_agent_alias` replaces your alias and durable session name. `move_to_workspace` moves you into another workspace, named as `list_agents` reports it or as an absolute directory, with an optional new alias. Both fail with `alias_conflict` naming the holder when the alias is taken there. Pass `force` only when that holder is your own earlier session. You take its identity over, it is told, and its session ends, so its next call runs as a fresh unnamed agent. Every name and address you held stays reserved, so a stale sender gets `recipient_alias_changed` rather than a silent redirect.
57
51
 
58
- ## Sending
52
+ ## Sending a message
59
53
 
60
- An address is either a bare alias (an agent in your own workspace) or `alias@workspace`, naming the
61
- workspace explicitly — your own or another's.
54
+ Your workspace is your Git repository, so agents in its other worktrees are in it too. Choose the address by who you are addressing:
62
55
 
63
- ```
64
- Who are you addressing?
65
- ├─ answering a message → its `from` or `fromAddress`. There is no reply tool
66
- ├─ an id or address a listing gave you → send it back verbatim
67
- ├─ an agent in your own workspace → its alias
68
- ├─ an agent anywhere else → alias@workspace
69
- └─ every agent in a workspace → broadcast_agent_message
70
- ```
56
+ - Answering a message: its `from` or `fromAddress`. There is no reply tool.
57
+ - An id or address a listing gave you: send it back verbatim.
58
+ - An agent in your own workspace: its alias.
59
+ - An agent in another workspace: `alias@workspace`.
60
+ - Every agent in a workspace: `broadcast_agent_message`.
61
+
62
+ The calls:
71
63
 
72
64
  ```jsonc
73
65
  // send_agent_message
@@ -82,31 +74,29 @@ Who are you addressing?
82
74
  // → { "address": "*@web-app", "sent": [{ "messageId": "msg_…", "to": "pgr://agent/…" }], "refused": [] }
83
75
  ```
84
76
 
85
- A broadcast skips archived agents and ended ephemeral sessions. Each entry in `refused` is a
86
- recipient a rate or inbox limit turned away, with the error `code` the table below explains; the rest
87
- still received it.
77
+ A broadcast skips archived agents and ended ephemeral sessions. Each entry in `refused` is a recipient that a rate or inbox limit turned away, with an error `code` from the error code table. Every other recipient received the message.
88
78
 
89
- - `list_team_members` lists your workspace; `list_agents` lists everyone with the `address` to reach them.
90
- - `subject` is required and capped at 40 characters. Write it like a commit summary — the operator scans a column of them, so put the detail in `body`.
91
- - `type` is required free text capped at 8 characters, for a coarse category like `"brief"`, `"idle"`, or `"status"`. Validated the same way as `subject`; carried through the claim and shown in the operator's views. The broker enforces no fixed vocabulary.
92
- - The broker treats `body` as opaque; it does not define an envelope shape. Send the detail itself — a plain sentence, not an envelope.
93
- - Cap 12 KB. Send a path or an id, not a file.
79
+ - `list_team_members` lists your workspace. `list_agents` lists every agent with the `address` that reaches it.
80
+ - `subject` is required and capped at 40 characters. Write it like a commit summary, because the operator scans a column of them, and put the detail in `body`.
81
+ - `type` is required free text capped at 8 characters, naming a coarse category such as `"brief"`, `"idle"` or `"status"`. The broker enforces no fixed vocabulary. The recipient sees it in the claim, and the operator sees it in their views.
82
+ - The broker treats `body` as opaque text. Write the detail itself in plain sentences, not a structured envelope.
83
+ - `body` is capped at 12 KB. Send a path or an id, not a file's contents.
94
84
  - Add `idempotencyKey` on any send you might retry.
95
- - `get_message_status { messageId }` for something you sent.
85
+ - Call `get_message_status { messageId }` to check a message you sent.
96
86
 
97
- ## On error
87
+ ## Error codes
98
88
 
99
89
  | Code | Do |
100
90
  | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
101
91
  | `unknown_recipient` | `list_agents`, then use a listed `address`. Do not retry the same one |
102
- | `recipient_alias_changed` | Resend to the alias named in the error |
103
- | `alias_conflict` | Requested alias is taken in this workspace, or is a previous name reserved for its old holder. Pick another |
92
+ | `recipient_alias_changed` | Resend to the address named in the error |
93
+ | `alias_conflict` | Requested alias is taken in that workspace, or is a previous name reserved for its old holder. Pick another, or `force` if it is your own |
104
94
  | `conflict` | Rate (60/min per pair) or inbox (1000) limit: back off, do not loop. Any other cause: the request cannot succeed as sent, so read the message |
105
95
  | `oversized_content` | The named field (body, subject, or type) exceeds its limit; shorten that field |
106
96
  | `validation_failed` | The error message names the cause (a field, an unknown id, a malformed request); read it, do not retry unchanged |
107
97
 
108
- ## Never
98
+ ## Never do these
109
99
 
110
- - Poll `fetch_agent_messages` on a timer — messages are delivered to you.
100
+ - Poll `fetch_agent_messages` on a timer. The broker delivers messages to you.
111
101
  - Ack a message you have not read.
112
- - Put tokens, credentials, or reservations in a body. Bodies are shown in operator tools.
102
+ - Put tokens, credentials, or reservations in a body. Operator tools show bodies.