@ziggs-ai/agent-sdk 0.16.1 → 0.18.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 (113) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +110 -88
  3. package/dist/{AgentHost.d.ts → Agent.d.ts} +50 -27
  4. package/dist/{AgentHost.js → Agent.js} +135 -129
  5. package/dist/adapters/AnthropicAdapter.d.ts +1 -1
  6. package/dist/adapters/AnthropicAdapter.js +1 -1
  7. package/dist/{agent/ClaudeSdkAgent.d.ts → brain/ClaudeBrain.d.ts} +23 -11
  8. package/dist/{agent/ClaudeSdkAgent.js → brain/ClaudeBrain.js} +99 -99
  9. package/dist/{agent/Agent.d.ts → brain/ZiggsBrain.d.ts} +23 -20
  10. package/dist/{agent/Agent.js → brain/ZiggsBrain.js} +12 -11
  11. package/dist/{agent → brain}/claudeCognition.d.ts +1 -1
  12. package/dist/{agent → brain}/claudeCognition.js +1 -1
  13. package/dist/{agent → brain}/claudeSdkProtocol.d.ts +1 -1
  14. package/dist/{agent → brain}/claudeSdkProtocol.js +1 -1
  15. package/dist/{agent → brain}/claudeSdkTools.d.ts +1 -1
  16. package/dist/{agent → brain}/claudeSdkTools.js +5 -14
  17. package/dist/{agent → brain}/hostedAgentConnection.d.ts +1 -1
  18. package/dist/{agent → brain}/hostedAgentConnection.js +3 -3
  19. package/dist/brain/index.d.ts +6 -6
  20. package/dist/brain/index.js +2 -2
  21. package/dist/brain/onBehalfAgreement.d.ts +51 -0
  22. package/dist/brain/onBehalfAgreement.js +99 -0
  23. package/dist/{agent → brain}/toolManagerToSdkMcp.js +10 -1
  24. package/dist/defineAgent.js +4 -4
  25. package/dist/formatters/HistoryFormatter.js +2 -1
  26. package/dist/index.d.ts +30 -31
  27. package/dist/index.js +24 -25
  28. package/dist/{adapters/McpAdapter.d.ts → mcp/createMcpAgent.d.ts} +16 -15
  29. package/dist/{adapters/McpAdapter.js → mcp/createMcpAgent.js} +82 -46
  30. package/dist/memory/MemoryStore.d.ts +18 -1
  31. package/dist/memory/MemoryStore.js +43 -0
  32. package/dist/{server → platform}/ConnectionPool.d.ts +9 -3
  33. package/dist/{server → platform}/ConnectionPool.js +16 -4
  34. package/dist/{server → platform}/EventQueue.d.ts +1 -1
  35. package/dist/{server → platform}/StateTimers.d.ts +1 -1
  36. package/dist/{server → platform}/StateTimers.js +1 -1
  37. package/dist/{server → platform}/ZiggsEffectHandler.d.ts +4 -4
  38. package/dist/{server → platform}/ZiggsEffectHandler.js +2 -2
  39. package/dist/{server → platform}/inboxLoop.d.ts +34 -1
  40. package/dist/{server → platform}/inboxLoop.js +122 -14
  41. package/dist/{server → platform}/polls/PollKind.d.ts +4 -7
  42. package/dist/{server → platform}/polls/PollKind.js +3 -6
  43. package/dist/{server → platform}/polls/PollRunner.d.ts +5 -6
  44. package/dist/{server → platform}/polls/PollRunner.js +24 -12
  45. package/dist/platform/polls/declaredPollRunner.d.ts +4 -0
  46. package/dist/platform/polls/declaredPollRunner.js +29 -0
  47. package/dist/{server → platform}/tools/tier2/mcpConnectionTools.js +32 -18
  48. package/dist/runtime/bootstrapCtx.d.ts +1 -1
  49. package/dist/runtime/bootstrapCtx.js +1 -1
  50. package/dist/runtime/buildOutcome.js +1 -1
  51. package/dist/runtime/runTurn.d.ts +1 -1
  52. package/dist/runtime/runTurn.js +18 -18
  53. package/dist/runtime/validateWorkflow.js +1 -1
  54. package/dist/skills/parseSkill.d.ts +7 -2
  55. package/dist/skills/parseSkill.js +22 -5
  56. package/dist/skills/skillReadReference.d.ts +6 -0
  57. package/dist/skills/skillReadReference.js +27 -0
  58. package/dist/tools/needs.js +2 -2
  59. package/dist/tools/toolCreds.d.ts +1 -1
  60. package/dist/tools/toolCreds.js +1 -1
  61. package/dist/types.d.ts +3 -3
  62. package/package.json +2 -2
  63. package/dist/server/createHealthServer.d.ts +0 -7
  64. package/dist/server/createHealthServer.js +0 -75
  65. package/dist/server/runLauncher.d.ts +0 -22
  66. package/dist/server/runLauncher.js +0 -160
  67. /package/dist/{agent → brain}/toolManagerToSdkMcp.d.ts +0 -0
  68. /package/dist/{server → platform}/EventQueue.js +0 -0
  69. /package/dist/{server → platform}/OutboxBuffer.d.ts +0 -0
  70. /package/dist/{server → platform}/OutboxBuffer.js +0 -0
  71. /package/dist/{server → platform}/SeenMessages.d.ts +0 -0
  72. /package/dist/{server → platform}/SeenMessages.js +0 -0
  73. /package/dist/{server → platform}/agreements/AgreementService.d.ts +0 -0
  74. /package/dist/{server → platform}/agreements/AgreementService.js +0 -0
  75. /package/dist/{server → platform}/proactive/ProactiveTrigger.d.ts +0 -0
  76. /package/dist/{server → platform}/proactive/ProactiveTrigger.js +0 -0
  77. /package/dist/{server → platform}/requestMatch.d.ts +0 -0
  78. /package/dist/{server → platform}/requestMatch.js +0 -0
  79. /package/dist/{server → platform}/tasks/TaskService.d.ts +0 -0
  80. /package/dist/{server → platform}/tasks/TaskService.js +0 -0
  81. /package/dist/{server → platform}/tasks/index.d.ts +0 -0
  82. /package/dist/{server → platform}/tasks/index.js +0 -0
  83. /package/dist/{server → platform}/telemetryIngest.d.ts +0 -0
  84. /package/dist/{server → platform}/telemetryIngest.js +0 -0
  85. /package/dist/{server → platform}/tools/index.d.ts +0 -0
  86. /package/dist/{server → platform}/tools/index.js +0 -0
  87. /package/dist/{server → platform}/tools/tier1/protocolRunner.d.ts +0 -0
  88. /package/dist/{server → platform}/tools/tier1/protocolRunner.js +0 -0
  89. /package/dist/{server → platform}/tools/tier1/protocolTools.d.ts +0 -0
  90. /package/dist/{server → platform}/tools/tier1/protocolTools.js +0 -0
  91. /package/dist/{server → platform}/tools/tier2/artifactTools.d.ts +0 -0
  92. /package/dist/{server → platform}/tools/tier2/artifactTools.js +0 -0
  93. /package/dist/{server → platform}/tools/tier2/chatTools.d.ts +0 -0
  94. /package/dist/{server → platform}/tools/tier2/chatTools.js +0 -0
  95. /package/dist/{server → platform}/tools/tier2/connectionTools.d.ts +0 -0
  96. /package/dist/{server → platform}/tools/tier2/connectionTools.js +0 -0
  97. /package/dist/{server → platform}/tools/tier2/contextTools.d.ts +0 -0
  98. /package/dist/{server → platform}/tools/tier2/contextTools.js +0 -0
  99. /package/dist/{server → platform}/tools/tier2/discoveryTools.d.ts +0 -0
  100. /package/dist/{server → platform}/tools/tier2/discoveryTools.js +0 -0
  101. /package/dist/{server → platform}/tools/tier2/grantsTools.d.ts +0 -0
  102. /package/dist/{server → platform}/tools/tier2/grantsTools.js +0 -0
  103. /package/dist/{server → platform}/tools/tier2/introductionTools.d.ts +0 -0
  104. /package/dist/{server → platform}/tools/tier2/introductionTools.js +0 -0
  105. /package/dist/{server → platform}/tools/tier2/linkTools.d.ts +0 -0
  106. /package/dist/{server → platform}/tools/tier2/linkTools.js +0 -0
  107. /package/dist/{server → platform}/tools/tier2/marketplaceTools.d.ts +0 -0
  108. /package/dist/{server → platform}/tools/tier2/marketplaceTools.js +0 -0
  109. /package/dist/{server → platform}/tools/tier2/mcpConnectionTools.d.ts +0 -0
  110. /package/dist/{server → platform}/tools/tier2/paymentTools.d.ts +0 -0
  111. /package/dist/{server → platform}/tools/tier2/paymentTools.js +0 -0
  112. /package/dist/{server → platform}/tools/tier2/taskTools.d.ts +0 -0
  113. /package/dist/{server → platform}/tools/tier2/taskTools.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
2
2
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
- import { AgentHost, type AgentHostOptions } from '../AgentHost.js';
3
+ import { type Agent, type AgentOptions } from '../Agent.js';
4
4
  import type { ToolDefinition } from '../tools/defineTool.js';
5
5
  import type { Workflow } from '../types.js';
6
6
  export type McpTransportConfig = {
@@ -34,7 +34,7 @@ export type McpTransportConfig = {
34
34
  operatorKey?: string;
35
35
  /** Ziggs backend base URL — defaults to BACKEND_URL / ZIGGS_BACKEND_URL. */
36
36
  baseUrl?: string;
37
- /** Agent to impersonate (X-Agent-Id) — defaults to the host agentId. */
37
+ /** Agent to impersonate (X-Agent-Id) — defaults to the agent agentId. */
38
38
  agentId?: string;
39
39
  } | {
40
40
  /**
@@ -51,7 +51,7 @@ export type McpTransportConfig = {
51
51
  operatorKey?: string;
52
52
  /** Ziggs backend base URL — defaults to BACKEND_URL / ZIGGS_BACKEND_URL. */
53
53
  baseUrl?: string;
54
- /** Agent to impersonate (X-Agent-Id) — defaults to the host agentId. */
54
+ /** Agent to impersonate (X-Agent-Id) — defaults to the agent agentId. */
55
55
  agentId?: string;
56
56
  /**
57
57
  * The MCP server URL this agent fronts. Not used for gateway calls (the
@@ -84,6 +84,7 @@ export declare function resolveZiggsGatewayTarget(config: Extract<McpTransportCo
84
84
  type: 'ziggs';
85
85
  }>, defaults?: {
86
86
  agentId?: string;
87
+ laneId?: string;
87
88
  }): {
88
89
  url: URL;
89
90
  headers: Record<string, string>;
@@ -95,11 +96,11 @@ export declare function resolveZiggsGatewayTarget(config: Extract<McpTransportCo
95
96
  */
96
97
  export declare function resolvePerUserGatewayTarget(config: Extract<McpTransportConfig, {
97
98
  type: 'ziggs-per-user';
98
- }>, agentId: string, userId: string, onBehalfOfAgreementId?: string): {
99
+ }>, agentId: string, userId: string, onBehalfOfAgreementId?: string, laneId?: string): {
99
100
  url: URL;
100
101
  headers: Record<string, string>;
101
102
  };
102
- export interface CreateMcpAgentOptions extends Omit<AgentHostOptions, 'tools' | 'workflow' | 'states' | 'initial'> {
103
+ export interface CreateMcpAgentOptions extends Omit<AgentOptions, 'tools' | 'workflow' | 'states' | 'initial'> {
103
104
  /** MCP server connection — stdio (local process) or HTTP (remote). */
104
105
  transport: McpTransportConfig;
105
106
  /** Additional Ziggs tools to register alongside the MCP-derived ones. */
@@ -142,8 +143,8 @@ export interface CreateMcpAgentOptions extends Omit<AgentHostOptions, 'tools' |
142
143
  */
143
144
  export declare function mcpToolBudget(maxTools: number, extraToolCount: number): number;
144
145
  export interface McpAgentHandle {
145
- host: AgentHost;
146
- /** Disconnect the MCP client (call on shutdown alongside host.disconnect()). */
146
+ agent: Agent;
147
+ /** Disconnect the MCP client (call on shutdown alongside agent.disconnect()). */
147
148
  closeMcp: () => Promise<void>;
148
149
  /** Names of MCP tools that were mapped to Ziggs tools on startup. */
149
150
  toolNames: string[];
@@ -164,35 +165,35 @@ export declare const MAX_TOOL_DESC_CHARS = 2000;
164
165
  /** Truncate one description at a sentence/newline boundary near the cap. */
165
166
  export declare function trimDescription(desc: string | undefined): string | undefined;
166
167
  /**
167
- * Wraps any MCP server as a Ziggs AgentHost.
168
+ * Wraps any MCP server as a Ziggs Agent.
168
169
  *
169
170
  * 1. Opens a connection to the MCP server (stdio, HTTP, or the Ziggs gateway).
170
171
  * 2. Calls tools/list and maps each tool to a Ziggs ToolDefinition.
171
- * 3. Returns an AgentHost pre-loaded with those tools, ready to connectAsync().
172
+ * 3. Returns an Agent pre-loaded with those tools, ready to connectAsync().
172
173
  *
173
174
  * Usage (local stdio server):
174
- * const { host, closeMcp } = await createMcpAgent({
175
+ * const { agent, closeMcp } = await createMcpAgent({
175
176
  * transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] },
176
177
  * agentId: 'my-github-agent',
177
178
  * name: 'GitHub Agent',
178
179
  * description: 'Wraps the GitHub MCP server',
179
180
  * });
180
- * await host.connectAsync();
181
+ * await agent.connectAsync();
181
182
  *
182
183
  * Usage (OAuth-protected remote server, brokered through Ziggs — the user
183
184
  * connected the server and issued this agent a grant; the agent never sees
184
185
  * the user's token):
185
- * const { host, closeMcp } = await createMcpAgent({
186
+ * const { agent, closeMcp } = await createMcpAgent({
186
187
  * transport: { type: 'ziggs', connectionId: 'con_abc', grantId: 'cgr_xyz' },
187
188
  * agentId: 'my-notion-agent',
188
189
  * name: 'Notion Agent',
189
190
  * description: 'Works in the owner\'s Notion via their MCP connection',
190
191
  * });
191
- * await host.connectAsync();
192
+ * await agent.connectAsync();
192
193
  *
193
194
  * process.on('SIGTERM', async () => {
194
- * host.disconnect();
195
- * await host.drain();
195
+ * agent.disconnect();
196
+ * await agent.drain();
196
197
  * await closeMcp();
197
198
  * });
198
199
  */
@@ -1,8 +1,10 @@
1
+ import { resolveWebAppOrigin, connectionsSettingsAppUrl, laneHeader } from '@ziggs-ai/api-client';
1
2
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
3
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
4
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
5
  import { ConnectionsClient, getBackendUrl } from '@ziggs-ai/api-client';
5
- import { AgentHost } from '../AgentHost.js';
6
+ import { createAgent } from '../Agent.js';
7
+ import { laneFromToolContext } from '../tools/toolCreds.js';
6
8
  import { thinkingDefaults } from '../runtime/defaults.js';
7
9
  /**
8
10
  * Typed failure from the Ziggs MCP gateway. `code` is the stable
@@ -71,6 +73,12 @@ export function resolveZiggsGatewayTarget(config, defaults = {}) {
71
73
  authorization: `Bearer ${operatorKey}`,
72
74
  'x-agent-id': agentId,
73
75
  'x-grant-id': config.grantId,
76
+ // The lane, so the broker can tell who this wake acts for. Until it was
77
+ // sent, this door was the unfenced one: the named-connector proxy states
78
+ // its lane, so the same account and the same grant got two different
79
+ // answers depending on which door the agent used. Built by the shared
80
+ // helper, because a copy of this header going missing is invisible.
81
+ ...laneHeader(defaults.laneId),
74
82
  },
75
83
  };
76
84
  }
@@ -79,7 +87,7 @@ export function resolveZiggsGatewayTarget(config, defaults = {}) {
79
87
  * the gateway resolves the invoking user's own connection + grant from
80
88
  * `x-on-behalf-of-user` + the impersonated agent.
81
89
  */
82
- export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfAgreementId) {
90
+ export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfAgreementId, laneId) {
83
91
  const operatorKey = config.operatorKey || process.env.ZIGGS_OPERATOR_KEY;
84
92
  if (!operatorKey) {
85
93
  throw new Error('ziggs-per-user MCP transport requires an operatorKey (or ZIGGS_OPERATOR_KEY)');
@@ -103,6 +111,8 @@ export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfA
103
111
  // agent (senderId), not the end-user who owns the connection. The gateway
104
112
  // uses this agreement to resolve the delegation root's user.
105
113
  ...(onBehalfOfAgreementId ? { 'x-on-behalf-of-agreement': onBehalfOfAgreementId } : {}),
114
+ // The lane, for the same reason as the path-id door above.
115
+ ...laneHeader(laneId),
106
116
  },
107
117
  };
108
118
  }
@@ -127,6 +137,15 @@ export function buildTransport(config, defaults = {}) {
127
137
  });
128
138
  }
129
139
  if (config.type === 'ziggs') {
140
+ /**
141
+ * No lane, and there cannot be one: this client is built ONCE at boot and
142
+ * shared by every wake, so any lane baked into its headers would be the
143
+ * wrong one for all but the first. The `ziggs` transport is the legacy
144
+ * fleet entry (`scripts/run-mcp-fleet.ts`) — a fixed connection + grant the
145
+ * operator configured for itself, so the lane-less fence (its own estate)
146
+ * is the right answer for it. A per-lane client belongs to `ziggs-per-user`,
147
+ * which builds one per wake and states the lane there.
148
+ */
130
149
  const target = resolveZiggsGatewayTarget(config, defaults);
131
150
  return new StreamableHTTPClientTransport(target.url, {
132
151
  requestInit: { headers: target.headers },
@@ -253,7 +272,7 @@ function trimToolDescriptions(tools, agentId) {
253
272
  return { ...t, description: nextDesc };
254
273
  });
255
274
  if (trimmedCount > 0) {
256
- console.warn(`[McpAdapter] ${agentId}: trimmed ${trimmedCount} oversized tool description(s) to ${MAX_TOOL_DESC_CHARS} chars, saving ~${Math.round(savedChars / 4 / 1000)}K tokens/turn.`);
275
+ console.warn(`[createMcpAgent] ${agentId}: trimmed ${trimmedCount} oversized tool description(s) to ${MAX_TOOL_DESC_CHARS} chars, saving ~${Math.round(savedChars / 4 / 1000)}K tokens/turn.`);
257
276
  }
258
277
  return out;
259
278
  }
@@ -267,7 +286,7 @@ function filterToolsByAllowlist(tools, allowedTools, agentId) {
267
286
  return tools;
268
287
  const patterns = allowedTools.map((p) => p.toLowerCase());
269
288
  const kept = tools.filter((t) => patterns.some((p) => t.name.toLowerCase().includes(p)));
270
- console.warn(`[McpAdapter] ${agentId}: tool allowlist active — bound ${kept.length}/${tools.length} tools matching [${allowedTools.join(', ')}]${kept.length === 0 ? ' ⚠️ NONE MATCHED — check patterns against the server\'s tool names' : ': ' + kept.map((t) => t.name).join(', ')}.`);
289
+ console.warn(`[createMcpAgent] ${agentId}: tool allowlist active — bound ${kept.length}/${tools.length} tools matching [${allowedTools.join(', ')}]${kept.length === 0 ? ' ⚠️ NONE MATCHED — check patterns against the server\'s tool names' : ': ' + kept.map((t) => t.name).join(', ')}.`);
271
290
  return kept;
272
291
  }
273
292
  /**
@@ -283,48 +302,48 @@ function capTools(tools, agentId, maxTools, extraToolCount, allowedTools) {
283
302
  const budget = mcpToolBudget(maxTools, extraToolCount);
284
303
  if (trimmed.length <= budget)
285
304
  return trimmed;
286
- console.warn(`[McpAdapter] ${agentId}: MCP server exposes ${trimmed.length} tools, over the ${maxTools}-tool LLM cap; binding the first ${budget}, dropping ${trimmed.length - budget}.`);
305
+ console.warn(`[createMcpAgent] ${agentId}: MCP server exposes ${trimmed.length} tools, over the ${maxTools}-tool LLM cap; binding the first ${budget}, dropping ${trimmed.length - budget}.`);
287
306
  return trimmed.slice(0, budget);
288
307
  }
289
308
  /**
290
- * Wraps any MCP server as a Ziggs AgentHost.
309
+ * Wraps any MCP server as a Ziggs Agent.
291
310
  *
292
311
  * 1. Opens a connection to the MCP server (stdio, HTTP, or the Ziggs gateway).
293
312
  * 2. Calls tools/list and maps each tool to a Ziggs ToolDefinition.
294
- * 3. Returns an AgentHost pre-loaded with those tools, ready to connectAsync().
313
+ * 3. Returns an Agent pre-loaded with those tools, ready to connectAsync().
295
314
  *
296
315
  * Usage (local stdio server):
297
- * const { host, closeMcp } = await createMcpAgent({
316
+ * const { agent, closeMcp } = await createMcpAgent({
298
317
  * transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] },
299
318
  * agentId: 'my-github-agent',
300
319
  * name: 'GitHub Agent',
301
320
  * description: 'Wraps the GitHub MCP server',
302
321
  * });
303
- * await host.connectAsync();
322
+ * await agent.connectAsync();
304
323
  *
305
324
  * Usage (OAuth-protected remote server, brokered through Ziggs — the user
306
325
  * connected the server and issued this agent a grant; the agent never sees
307
326
  * the user's token):
308
- * const { host, closeMcp } = await createMcpAgent({
327
+ * const { agent, closeMcp } = await createMcpAgent({
309
328
  * transport: { type: 'ziggs', connectionId: 'con_abc', grantId: 'cgr_xyz' },
310
329
  * agentId: 'my-notion-agent',
311
330
  * name: 'Notion Agent',
312
331
  * description: 'Works in the owner\'s Notion via their MCP connection',
313
332
  * });
314
- * await host.connectAsync();
333
+ * await agent.connectAsync();
315
334
  *
316
335
  * process.on('SIGTERM', async () => {
317
- * host.disconnect();
318
- * await host.drain();
336
+ * agent.disconnect();
337
+ * await agent.drain();
319
338
  * await closeMcp();
320
339
  * });
321
340
  */
322
341
  export async function createMcpAgent(options) {
323
- const { transport: transportConfig, extraTools = [], workflow, maxTools = DEFAULT_MAX_TOOLS, allowedTools, toolSchema, ...hostOptions } = options;
342
+ const { transport: transportConfig, extraTools = [], workflow, maxTools = DEFAULT_MAX_TOOLS, allowedTools, toolSchema, ...agentOptions } = options;
324
343
  // ── Per-user path: no boot connection; one MCP client per invoking
325
344
  // user, each carrying that user's on-behalf-of header for an isolated session.
326
345
  if (transportConfig.type === 'ziggs-per-user') {
327
- const agentId = transportConfig.agentId || hostOptions.agentId;
346
+ const agentId = transportConfig.agentId || agentOptions.agentId;
328
347
  if (!agentId) {
329
348
  throw new Error('ziggs-per-user MCP transport requires an agentId to impersonate');
330
349
  }
@@ -333,14 +352,17 @@ export async function createMcpAgent(options) {
333
352
  }
334
353
  const tools = capTools(toolSchema, agentId, maxTools, extraTools.length, allowedTools);
335
354
  const clients = new Map();
336
- const clientFor = (userId, onBehalfOfAgreementId) => {
337
- // Key by user + agreement: the gateway may resolve a different end-user
338
- // per agreement (delegation), so sessions must not be shared across them.
339
- const cacheKey = `${userId}|${onBehalfOfAgreementId ?? ''}`;
355
+ const clientFor = (userId, onBehalfOfAgreementId, laneId) => {
356
+ // Key by user + agreement + lane: the gateway may resolve a different
357
+ // end-user per agreement (delegation), and the lane decides which party's
358
+ // grants this session may spend, so sessions must not be shared across
359
+ // any of the three. The headers are fixed when the transport is built,
360
+ // so a session reused across lanes would carry the first lane's fence.
361
+ const cacheKey = `${userId}|${onBehalfOfAgreementId ?? ''}|${laneId ?? ''}`;
340
362
  let p = clients.get(cacheKey);
341
363
  if (!p) {
342
364
  p = (async () => {
343
- const target = resolvePerUserGatewayTarget(transportConfig, agentId, userId, onBehalfOfAgreementId);
365
+ const target = resolvePerUserGatewayTarget(transportConfig, agentId, userId, onBehalfOfAgreementId, laneId);
344
366
  const transport = new StreamableHTTPClientTransport(target.url, {
345
367
  requestInit: { headers: target.headers },
346
368
  fetch: wrapGatewayFetch(),
@@ -367,9 +389,7 @@ export async function createMcpAgent(options) {
367
389
  process.env.ZIGGS_BACKEND_URL ||
368
390
  'http://localhost:3000').replace(/\/$/, '');
369
391
  // App URL for the user-facing connect link: api.ziggsai.com → ziggsai.com.
370
- const appUrl = backendBase.includes('localhost')
371
- ? 'http://localhost:1234'
372
- : backendBase.replace(/\/\/api\./, '//');
392
+ const appUrl = resolveWebAppOrigin(undefined, backendBase);
373
393
  const serverLabel = transportConfig.serverUrl
374
394
  ? new URL(transportConfig.serverUrl).host
375
395
  : 'the external service';
@@ -404,6 +424,14 @@ export async function createMcpAgent(options) {
404
424
  // connection-owning end-user; pass the agreement so the gateway resolves
405
425
  // the delegation root's user.
406
426
  const onBehalfOfAgreementId = typeof context.onBehalfOfAgreementId === 'string' ? context.onBehalfOfAgreementId : undefined;
427
+ /**
428
+ * The lane this wake runs in, read the same way every tool reads it.
429
+ * Without it the relay states no lane, the gateway resolves the wake to
430
+ * the agent's own operator, and any grant the CUSTOMER gave is refused —
431
+ * on the door built for exactly that case. Omitting a lane can only
432
+ * narrow reach, never widen it, so the miss never raises.
433
+ */
434
+ const laneId = laneFromToolContext(context);
407
435
  // (B): per-user resolution is intermittently flaky even when the
408
436
  // connection is valid and resolvable — the same on-behalf call fails with
409
437
  // NO_USER_CONNECTION on one wake and succeeds on the next. clientFor never
@@ -414,7 +442,7 @@ export async function createMcpAgent(options) {
414
442
  let lastErr;
415
443
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
416
444
  try {
417
- const c = await clientFor(userId, onBehalfOfAgreementId);
445
+ const c = await clientFor(userId, onBehalfOfAgreementId, laneId);
418
446
  return (await c.callTool({ name, arguments: args }));
419
447
  }
420
448
  catch (err) {
@@ -422,7 +450,7 @@ export async function createMcpAgent(options) {
422
450
  const resolutionMiss = err instanceof ZiggsGatewayError && err.code === 'NO_USER_CONNECTION';
423
451
  const transient = resolutionMiss || !(err instanceof ZiggsGatewayError);
424
452
  if (attempt < MAX_ATTEMPTS && transient) {
425
- console.warn(`[McpAdapter] per-user ${name} attempt ${attempt}/${MAX_ATTEMPTS} failed (${err.message}); retrying with a fresh connection`);
453
+ console.warn(`[createMcpAgent] per-user ${name} attempt ${attempt}/${MAX_ATTEMPTS} failed (${err.message}); retrying with a fresh connection`);
426
454
  await new Promise((r) => setTimeout(r, 500 * attempt));
427
455
  continue;
428
456
  }
@@ -433,7 +461,7 @@ export async function createMcpAgent(options) {
433
461
  lastErr.code === 'NO_USER_CONNECTION' &&
434
462
  transportConfig.serverUrl) {
435
463
  const requestId = await triggerConnectionRequest(userId).catch(() => null);
436
- const link = `${appUrl}/app/access${requestId ? `?request=${encodeURIComponent(requestId)}` : ''}`;
464
+ const link = connectionsSettingsAppUrl(appUrl, requestId);
437
465
  return {
438
466
  content: [
439
467
  {
@@ -445,13 +473,13 @@ export async function createMcpAgent(options) {
445
473
  }
446
474
  throw lastErr;
447
475
  };
448
- const host = new AgentHost({
449
- ...hostOptions,
450
- workflow: workflow ?? buildDefaultWorkflow(hostOptions.description ?? '', tools),
476
+ const agent = createAgent({
477
+ ...agentOptions,
478
+ workflow: workflow ?? buildDefaultWorkflow(agentOptions.description ?? '', tools),
451
479
  tools: [...mapMcpTools(caller, tools), ...extraTools],
452
480
  });
453
481
  return {
454
- host,
482
+ agent,
455
483
  closeMcp: async () => {
456
484
  await Promise.all([...clients.values()].map(p => p.then(c => c.close()).catch(() => { })));
457
485
  },
@@ -459,21 +487,29 @@ export async function createMcpAgent(options) {
459
487
  };
460
488
  }
461
489
  // ── Single-client path (stdio / http / brokered ziggs): discover tools at boot.
462
- const transport = buildTransport(transportConfig, { agentId: hostOptions.agentId });
490
+ const transport = buildTransport(transportConfig, { agentId: agentOptions.agentId });
463
491
  const client = new Client({ name: 'ziggs-mcp-agent', version: '1.0.0' });
464
- await client.connect(transport);
465
- const rawTools = capTools((await client.listTools()).tools, hostOptions.agentId, maxTools, extraTools.length, allowedTools);
466
- const caller = (name, args) => client.callTool({ name, arguments: args });
467
- const mcpTools = mapMcpTools(caller, rawTools);
468
- const resolvedWorkflow = workflow ?? buildDefaultWorkflow(hostOptions.description ?? '', rawTools);
469
- const host = new AgentHost({
470
- ...hostOptions,
471
- workflow: resolvedWorkflow,
472
- tools: [...mcpTools, ...extraTools],
473
- });
474
- return {
475
- host,
476
- closeMcp: () => client.close(),
477
- toolNames: rawTools.map((t) => t.name),
478
- };
492
+ try {
493
+ await client.connect(transport);
494
+ const rawTools = capTools((await client.listTools()).tools, agentOptions.agentId, maxTools, extraTools.length, allowedTools);
495
+ const caller = (name, args) => client.callTool({ name, arguments: args });
496
+ const mcpTools = mapMcpTools(caller, rawTools);
497
+ const resolvedWorkflow = workflow ?? buildDefaultWorkflow(agentOptions.description ?? '', rawTools);
498
+ const agent = createAgent({
499
+ ...agentOptions,
500
+ workflow: resolvedWorkflow,
501
+ tools: [...mcpTools, ...extraTools],
502
+ });
503
+ return {
504
+ agent,
505
+ closeMcp: () => client.close(),
506
+ toolNames: rawTools.map((t) => t.name),
507
+ };
508
+ }
509
+ catch (err) {
510
+ // Validation and tool discovery can fail after stdio has spawned a child.
511
+ // A rejected factory must release the transport it already acquired.
512
+ await client.close().catch(() => { });
513
+ throw err;
514
+ }
479
515
  }
@@ -3,7 +3,9 @@
3
3
  *
4
4
  * This is the SDK's contract; the *storage* is operator-provided. The agent
5
5
  * picks the key shape (often `counterparty:<id>`, sometimes `agreement:<id>:notes`,
6
- * etc.) — the SDK does not enforce one.
6
+ * etc.) — the SDK does not enforce one. The host prefixes every key with
7
+ * the hire the wake is inside (or the agent's own section when there is
8
+ * no hire), so one customer's notes cannot be read in another's wake.
7
9
  *
8
10
  * Reference implementations: `InMemoryStore` (default; non-durable) and
9
11
  * `FileMemoryStore` (single JSON file; useful for local dev). Anything
@@ -53,3 +55,18 @@ export declare class FileMemoryStore implements MemoryStore {
53
55
  }[]>;
54
56
  delete(key: string): Promise<void>;
55
57
  }
58
+ /**
59
+ * Section a wake may read and write. A hire (pinned agreement, or an
60
+ * `agrn-` lane) gets its own prefix. A wake with no hire shares the
61
+ * agent's own section — same as before for in-process tools that key by
62
+ * chat themselves.
63
+ */
64
+ export declare function memorySectionForWake(input: {
65
+ sessionId?: string | null;
66
+ activeAgreementId?: string | null;
67
+ }): string;
68
+ /**
69
+ * Present a store as if it only contained one section. Agent keys stay
70
+ * unchanged; get/put/list/delete all go through `section:`.
71
+ */
72
+ export declare function partitionMemoryStore(store: MemoryStore, section: string): MemoryStore;
@@ -1,3 +1,4 @@
1
+ import { agreementIdFromLane } from '@ziggs-ai/api-client';
1
2
  /** Non-durable reference impl. Per-process; lost on restart. */
2
3
  export class InMemoryStore {
3
4
  store = new Map();
@@ -76,3 +77,45 @@ export class FileMemoryStore {
76
77
  await this._flush();
77
78
  }
78
79
  }
80
+ /**
81
+ * Section a wake may read and write. A hire (pinned agreement, or an
82
+ * `agrn-` lane) gets its own prefix. A wake with no hire shares the
83
+ * agent's own section — same as before for in-process tools that key by
84
+ * chat themselves.
85
+ */
86
+ export function memorySectionForWake(input) {
87
+ const pinned = typeof input.activeAgreementId === 'string'
88
+ ? input.activeAgreementId.trim()
89
+ : '';
90
+ if (pinned)
91
+ return `hire:${pinned}`;
92
+ const fromLane = agreementIdFromAgrnSession(input.sessionId);
93
+ if (fromLane)
94
+ return `hire:${fromLane}`;
95
+ return 'agent';
96
+ }
97
+ function agreementIdFromAgrnSession(sessionId) {
98
+ return agreementIdFromLane(sessionId)?.trim() || null;
99
+ }
100
+ /**
101
+ * Present a store as if it only contained one section. Agent keys stay
102
+ * unchanged; get/put/list/delete all go through `section:`.
103
+ */
104
+ export function partitionMemoryStore(store, section) {
105
+ const prefix = `${section}:`;
106
+ const partitioned = {
107
+ get: (key) => store.get(prefix + key),
108
+ put: (key, value) => store.put(prefix + key, value),
109
+ list: async (keyPrefix) => {
110
+ const rows = await store.list(prefix + keyPrefix);
111
+ return rows.map(({ key, value }) => ({
112
+ key: key.startsWith(prefix) ? key.slice(prefix.length) : key,
113
+ value,
114
+ }));
115
+ },
116
+ };
117
+ if (store.delete) {
118
+ partitioned.delete = (key) => store.delete(prefix + key);
119
+ }
120
+ return partitioned;
121
+ }
@@ -1,4 +1,4 @@
1
- import { AgentHost, type AgentHostOptions } from '../AgentHost.js';
1
+ import { type Agent, type AgentOptions } from '../Agent.js';
2
2
  interface ConnectionManagerMeta {
3
3
  domain?: string;
4
4
  expertise?: string[];
@@ -23,11 +23,11 @@ export declare class ConnectionPool {
23
23
  * registered and never connected is an agent nobody hosts, which is the
24
24
  * failure this pool exists to make impossible.
25
25
  */
26
- register(configs: AgentHostOptions[], metaArr?: ConnectionManagerMeta[]): void;
26
+ register(configs: AgentOptions[], metaArr?: ConnectionManagerMeta[]): void;
27
27
  /** Connect every registered agent. Reports the ones that could not come up. */
28
28
  connectAll(): Promise<void>;
29
29
  /** The host for one agent, connecting it if it somehow is not up yet. */
30
- startAgent(agentId: string): Promise<AgentHost>;
30
+ startAgent(agentId: string): Promise<Agent>;
31
31
  stopAgent(agentId: string): Promise<void>;
32
32
  disconnectAll(): Promise<void>;
33
33
  /**
@@ -68,6 +68,12 @@ export declare class ConnectionPool {
68
68
  query(filter: QueryFilter): string[];
69
69
  list(): string[];
70
70
  listActive(): string[];
71
+ /**
72
+ * Inbox liveness for every live agent in this process. The runner's health
73
+ * route answers from this rather than from "the HTTP server replied", which
74
+ * a wedged process does happily while none of its agents read anything.
75
+ */
76
+ liveness(): ReturnType<Agent['inboxLiveness']>[];
71
77
  get size(): number;
72
78
  getMeta(id: string): ConnectionManagerMeta | undefined;
73
79
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ConnectionPool — the agents this launcher process hosts.
3
3
  *
4
- * Holds N `AgentHostOptions` configs and connects every one of them at boot
4
+ * Holds N `AgentOptions` configs and connects every one of them at boot
5
5
  *. Each host long-polls its own inbox for the life of the process —
6
6
  * there is no agent socket. There is no subset, no cap and no lifecycle policy:
7
7
  * a write releasing that poll is the only signal anything needs.
@@ -14,7 +14,7 @@
14
14
  * answer was "none of them".
15
15
  */
16
16
  import { ConnectionManager } from '@ziggs-ai/api-client';
17
- import { AgentHost } from '../AgentHost.js';
17
+ import { createAgent } from '../Agent.js';
18
18
  import { runtimeLog } from '../shared/runtimeLog.js';
19
19
  export class ConnectionPool {
20
20
  manager = new ConnectionManager();
@@ -48,7 +48,7 @@ export class ConnectionPool {
48
48
  const cfg = this.configs.get(agentId);
49
49
  const originalTap = cfg.effectTap;
50
50
  const pool = this;
51
- const host = new AgentHost({
51
+ const host = createAgent({
52
52
  ...cfg,
53
53
  effectTap(effect, response) {
54
54
  if (effect.kind === 'send-message') {
@@ -72,7 +72,11 @@ export class ConnectionPool {
72
72
  }
73
73
  /** Connect every registered agent. Reports the ones that could not come up. */
74
74
  async connectAll() {
75
- return this.manager.connectAll();
75
+ await this.manager.connectAll();
76
+ const connected = this.listActive().length;
77
+ if (connected !== this.size) {
78
+ throw new Error(`Only ${connected}/${this.size} agents started; process must restart`);
79
+ }
76
80
  }
77
81
  /** The host for one agent, connecting it if it somehow is not up yet. */
78
82
  async startAgent(agentId) {
@@ -181,6 +185,14 @@ export class ConnectionPool {
181
185
  query(filter) { return this.manager.query(filter); }
182
186
  list() { return this.manager.list(); }
183
187
  listActive() { return this.manager.listActive(); }
188
+ /**
189
+ * Inbox liveness for every live agent in this process. The runner's health
190
+ * route answers from this rather than from "the HTTP server replied", which
191
+ * a wedged process does happily while none of its agents read anything.
192
+ */
193
+ liveness() {
194
+ return [...this.hosts.values()].map((host) => host.inboxLiveness());
195
+ }
184
196
  get size() { return this.manager.size; }
185
197
  getMeta(id) { return this.manager.getMeta(id); }
186
198
  }
@@ -7,7 +7,7 @@ export interface EventQueueBusyLane {
7
7
  queued: number;
8
8
  processing: boolean;
9
9
  }
10
- /** Outcome of `waitForIdle` / `AgentHost.drain`. Timeout is data, not a throw. */
10
+ /** Outcome of `waitForIdle` / `Agent.drain`. Timeout is data, not a throw. */
11
11
  export interface WaitForIdleResult {
12
12
  idle: boolean;
13
13
  timedOut: boolean;
@@ -6,7 +6,7 @@ export interface StateTimeoutEvent {
6
6
  }
7
7
  /**
8
8
  * Per-lane liveness timers for parked states declaring timeoutMs. Owned by
9
- * AgentHost. A firing timer enqueues a state-timeout frame on the SAME lane
9
+ * Agent. A firing timer enqueues a state-timeout frame on the SAME lane
10
10
  * (caller-provided enqueue → EventQueue), so it serializes behind any real
11
11
  * event and can never race one. Epochs make stale frames identifiable: a
12
12
  * queued timeout frame whose epoch is not the lane's currently-armed epoch was
@@ -1,7 +1,7 @@
1
1
  import { runtimeLog } from '../shared/runtimeLog.js';
2
2
  /**
3
3
  * Per-lane liveness timers for parked states declaring timeoutMs. Owned by
4
- * AgentHost. A firing timer enqueues a state-timeout frame on the SAME lane
4
+ * Agent. A firing timer enqueues a state-timeout frame on the SAME lane
5
5
  * (caller-provided enqueue → EventQueue), so it serializes behind any real
6
6
  * event and can never race one. Epochs make stale frames identifiable: a
7
7
  * queued timeout frame whose epoch is not the lane's currently-armed epoch was
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Ziggs-flavored EffectHandler.
3
3
  *
4
- * Translates the Agent's pure Effect requests into concrete backend calls:
4
+ * Translates the brain's Effect requests into concrete backend calls:
5
5
  * reads go through the grant-fenced ContextReadClient snapshot, writes through
6
6
  * artifactsClient / taskService / agreementService / messageSender, and LLM
7
- * turns through the adapter. This is the only place where Agent intent meets
7
+ * turns through the adapter. This is the only place where brain intent meets
8
8
  * backend reality.
9
9
  *
10
- * Lives in agent-sdk for now alongside `Agent`; destined for a separate
11
- * `@ziggs-ai/agent-server` package once the SDK split is complete.
10
+ * Belongs to the SDK body. The operator runner owns process startup and
11
+ * health serving; it does not perform the brain’s effects.
12
12
  */
13
13
  import type { Effect, EffectHandler, LlmMessage, LlmResponse, LlmToolSchema, ToolCallResult } from '../types.js';
14
14
  /** Minimal LLM adapter contract — OpenAIAdapter satisfies this. */
@@ -115,7 +115,7 @@ export async function executeToolWithSideEffects(deps, eff, effect, roster) {
115
115
  const argsPreview = previewToolArgs(args);
116
116
  // #6 — breadcrumbs are best-effort. Awaiting them bracketed every
117
117
  // tool call; a slow/wedged artifacts backend stalled the whole wake. Match
118
- // ClaudeSdkAgent thought writes (fire-and-forget).
118
+ // ClaudeBrain thought writes (fire-and-forget).
119
119
  void emitToolOperationArtifact(deps.artifactsClient, effect.sessionId, 'started', {
120
120
  state: 'started',
121
121
  tool: effect.name,
@@ -230,7 +230,7 @@ export function createZiggsEffectHandler(deps) {
230
230
  case 'read-context': {
231
231
  // Grant-fenced aggregate read: the backend composes the ContextSnapshot
232
232
  // (GET /context/snapshot) through the same access + temporal fence as
233
- // the per-type reads. Every producer — ClaudeSdkAgent.tick, runTurn,
233
+ // the per-type reads. Every producer — ClaudeBrain.tick, runTurn,
234
234
  // and the on-demand context_snapshot tool — routes through here.
235
235
  const snap = await buildSnapshot(deps, effect.sessionId, deps.agentId, {
236
236
  limit: effect.opts?.maxMessages,