@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,31 +1,33 @@
1
+ import { agreementLaneId, agreementIdFromLane } from '@ziggs-ai/api-client';
2
+ import { createDeclaredPollRunner } from './platform/polls/declaredPollRunner.js';
1
3
  import { randomUUID } from 'node:crypto';
2
- import { Agent } from './agent/Agent.js';
3
- import { ClaudeSdkAgent } from './agent/ClaudeSdkAgent.js';
4
- import { applyBrainToHostOptions, isClaudeCognition, } from './brain/index.js';
5
- import { CLAUDE_SDK_TOOLS } from './agent/claudeSdkTools.js';
6
- import { resolveClaudeModel } from './agent/claudeCognition.js';
7
- import { CHAT_TOOLS } from './server/tools/tier2/chatTools.js';
8
- import { EventQueue } from './server/EventQueue.js';
9
- import { StateTimers } from './server/StateTimers.js';
10
- import { createZiggsEffectHandler } from './server/ZiggsEffectHandler.js';
4
+ import { ZiggsBrain } from './brain/ZiggsBrain.js';
5
+ import { ClaudeBrain } from './brain/ClaudeBrain.js';
6
+ import { applyBrainToAgentOptions, isClaudeCognition, } from './brain/index.js';
7
+ import { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
8
+ import { resolveClaudeModel } from './brain/claudeCognition.js';
9
+ import { CHAT_TOOLS } from './platform/tools/tier2/chatTools.js';
10
+ import { EventQueue } from './platform/EventQueue.js';
11
+ import { StateTimers } from './platform/StateTimers.js';
12
+ import { createZiggsEffectHandler } from './platform/ZiggsEffectHandler.js';
11
13
  import { normalizeIncomingEvent } from './ingress/normalizeIncoming.js';
12
14
  import { PromptBuilder } from './runtime/PromptBuilder.js';
13
- import { InMemoryStore } from './memory/MemoryStore.js';
15
+ import { InMemoryStore, memorySectionForWake, partitionMemoryStore, } from './memory/MemoryStore.js';
14
16
  import { ToolManager } from './tools/ToolManager.js';
15
17
  import { toolName } from './tools/defineTool.js';
16
- import { PROTOCOL_TOOLS } from './server/tools/tier1/protocolTools.js';
17
- import { marketplaceViewTool } from './server/tools/tier2/marketplaceTools.js';
18
+ import { PROTOCOL_TOOLS } from './platform/tools/tier1/protocolTools.js';
19
+ import { marketplaceViewTool } from './platform/tools/tier2/marketplaceTools.js';
18
20
  import { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
19
21
  import { AnthropicAdapter } from './adapters/AnthropicAdapter.js';
20
22
  import { ArtifactsClient, ConnectionsClient, ContextReadClient, TelemetryClient, sendChatMessage, } from '@ziggs-ai/api-client';
21
23
  import { normalizeNeeds, unmetNeeds, unmetNeedsMessage, } from './tools/needs.js';
22
- import { TaskService } from './server/tasks/TaskService.js';
23
- import { AgreementService } from './server/agreements/AgreementService.js';
24
- import { runInboxLoop } from './server/inboxLoop.js';
25
- import { requestExactMatch } from './server/requestMatch.js';
26
- import { SeenMessages } from './server/SeenMessages.js';
24
+ import { TaskService } from './platform/tasks/TaskService.js';
25
+ import { AgreementService } from './platform/agreements/AgreementService.js';
26
+ import { runInboxLoop } from './platform/inboxLoop.js';
27
+ import { requestExactMatch } from './platform/requestMatch.js';
28
+ import { SeenMessages } from './platform/SeenMessages.js';
27
29
  import { runtimeLog } from './shared/runtimeLog.js';
28
- import { isTelemetryIngestEnabled, sendTelemetry, } from './server/telemetryIngest.js';
30
+ import { isTelemetryIngestEnabled, sendTelemetry, } from './platform/telemetryIngest.js';
29
31
  import { buildWakeOutcomePayload, classifyWakeOutcome, } from './runtime/wakeOutcome.js';
30
32
  /**
31
33
  * The single fleet-wide default model — the one place the fallback lives.
@@ -36,9 +38,9 @@ import { buildWakeOutcomePayload, classifyWakeOutcome, } from './runtime/wakeOut
36
38
  */
37
39
  export const DEFAULT_MODEL = 'claude-haiku-4-5';
38
40
  // ConnectionPool lives in a separate module; resolved via a computed-path
39
- // dynamic import to avoid a circular import with AgentHost.
41
+ // dynamic import to avoid a circular import with Agent.
40
42
  async function loadConnectionPool() {
41
- const modPath = './server/ConnectionPool.js';
43
+ const modPath = './platform/ConnectionPool.js';
42
44
  const mod = await import(/* @vite-ignore */ modPath);
43
45
  return mod.ConnectionPool;
44
46
  }
@@ -57,7 +59,7 @@ export function laneSessionIdForTask(task) {
57
59
  return task.originChatId;
58
60
  }
59
61
  if (typeof task.agreementId === 'string' && task.agreementId) {
60
- return `agrn-${task.agreementId}`;
62
+ return agreementLaneId(task.agreementId);
61
63
  }
62
64
  return 'default';
63
65
  }
@@ -67,16 +69,11 @@ export function workScopeFromWake(event, sessionId) {
67
69
  const resource = rec.resource && typeof rec.resource === 'object'
68
70
  ? rec.resource
69
71
  : {};
70
- const fromAgrn = (id) => {
71
- if (typeof id !== 'string' || !id.startsWith('agrn-'))
72
- return undefined;
73
- return id.slice('agrn-'.length) || undefined;
74
- };
75
72
  const agreementId = (typeof rec.agreementId === 'string' && rec.agreementId) ||
76
73
  (typeof resource.agreementId === 'string' && resource.agreementId) ||
77
- fromAgrn(rec.chatId) ||
78
- fromAgrn(resource.chatId) ||
79
- fromAgrn(sessionId) ||
74
+ agreementIdFromLane(rec.chatId) ||
75
+ agreementIdFromLane(resource.chatId) ||
76
+ agreementIdFromLane(sessionId) ||
80
77
  undefined;
81
78
  const taskId = (typeof rec.taskId === 'string' && rec.taskId) ||
82
79
  (typeof resource.taskId === 'string' && resource.taskId) ||
@@ -132,7 +129,7 @@ export function warnOnShadowedPlatformTools(platformTools, ownTools, agentId) {
132
129
  .filter((name) => platform.has(name));
133
130
  if (shadowed.length === 0)
134
131
  return;
135
- console.warn(`[AgentHost] ${agentId}: tool name(s) ${shadowed.join(', ')} shadow the platform tools of the same name — ` +
132
+ console.warn(`[Agent] ${agentId}: tool name(s) ${shadowed.join(', ')} shadow the platform tools of the same name — ` +
136
133
  'the agent will call yours, not Ziggs\'s. Rename them unless that is deliberate.');
137
134
  }
138
135
  function isLlmAdapter(v) {
@@ -178,19 +175,18 @@ function createBackendServices(options) {
178
175
  };
179
176
  }
180
177
  /**
181
- * AgentHost — the Server. Owns a pure {@link Agent}, an {@link EffectHandler},
182
- * a transport (Ziggs WebSocket), and the lane router (per-session FIFO queue
183
- * + in-memory session state).
184
- *
185
- * transport (WS) → handleMessage → agent.tick(.. , eff) → ..
178
+ * One agent identity and its body: inbox, tools, contracts, memory and lanes.
179
+ * The brain decides on each wake; platform services perform its effects.
180
+ * connectAsync starts outbound inbox reads and declared external polls.
181
+ * An Agent never opens a listening socket.
186
182
  */
187
- export class AgentHost {
183
+ export class Agent {
188
184
  options;
189
185
  toolManager;
190
186
  // Retained for external inspection. Undefined for the Claude brain, which
191
187
  // drives query() directly and needs no LLM adapter.
192
188
  llm;
193
- agent;
189
+ brain;
194
190
  eff;
195
191
  eventQueue;
196
192
  sessions = new Map();
@@ -207,6 +203,8 @@ export class AgentHost {
207
203
  /** The per-agent inbox long-poll loop — this agent's sole wake source. */
208
204
  inboxLoop = null;
209
205
  inboxLoopEnabled;
206
+ started = false;
207
+ pollRunner;
210
208
  /** Short-lived grant-provider cache for the declared-needs gate. */
211
209
  /**
212
210
  * Lanes where an unmet-need refusal has already been allowed its one turn to
@@ -218,7 +216,7 @@ export class AgentHost {
218
216
  unmetTasksPendingAsk = new Map();
219
217
  grantProviderCache = null;
220
218
  constructor(options = {}) {
221
- const resolved = applyBrainToHostOptions(options);
219
+ const resolved = applyBrainToAgentOptions(options);
222
220
  this.options = {
223
221
  openaiKey: resolved.openaiKey || process.env.OPENAI_API_KEY,
224
222
  anthropicKey: resolved.anthropicKey || process.env.ANTHROPIC_API_KEY,
@@ -251,6 +249,8 @@ export class AgentHost {
251
249
  inboxCatchUp: resolved.inboxCatchUp,
252
250
  telemetryIngest: resolved.telemetryIngest,
253
251
  brain: resolved.brain,
252
+ polls: resolved.polls,
253
+ pollKinds: resolved.pollKinds,
254
254
  cognition: resolved.cognition ?? 'fsm',
255
255
  claudeMaxTurns: resolved.claudeMaxTurns,
256
256
  claudeStatelessPerWake: resolved.claudeStatelessPerWake,
@@ -263,12 +263,15 @@ export class AgentHost {
263
263
  // default.
264
264
  this.tickTimeoutMs =
265
265
  options.tickTimeoutMs ??
266
- (isClaudeCognition(options.cognition)
266
+ (isClaudeCognition(resolved.cognition)
267
267
  ? Number(process.env.CLAUDE_SDK_TICK_TIMEOUT_MS ?? 300_000)
268
268
  : 120_000);
269
269
  this.memory = options.memory ?? new InMemoryStore();
270
270
  this.sessionStore = options.sessionStore ?? null;
271
271
  this._validate();
272
+ this.pollRunner = createDeclaredPollRunner(this.options, {
273
+ sendTo: (_agentId, text, metadata) => this.handleMessage(text, metadata ?? {}),
274
+ });
272
275
  this._setup();
273
276
  }
274
277
  _validate() {
@@ -331,7 +334,7 @@ export class AgentHost {
331
334
  }
332
335
  if (!workflow?.states) {
333
336
  // The Claude brain has no FSM — it drives query() per wake and parks. It
334
- // still plugs into the host's lane/timer machinery via a trivial parked
337
+ // still plugs into the agent's lane/timer machinery via a trivial parked
335
338
  // workflow, synthesized here so callers don't hand-write a dummy one.
336
339
  if (isClaudeCognition(this.options.cognition)) {
337
340
  workflow = {
@@ -342,7 +345,7 @@ export class AgentHost {
342
345
  };
343
346
  }
344
347
  else {
345
- throw new Error('AgentHost: workflow (or states + initial) is required');
348
+ throw new Error('Agent: workflow (or states + initial) is required');
346
349
  }
347
350
  }
348
351
  this.eff = createZiggsEffectHandler({
@@ -359,8 +362,8 @@ export class AgentHost {
359
362
  telemetryIngest: this.options.telemetryIngest,
360
363
  effectTap: this.options.effectTap,
361
364
  });
362
- this.agent = isClaudeCognition(this.options.cognition)
363
- ? new ClaudeSdkAgent({
365
+ this.brain = isClaudeCognition(this.options.cognition)
366
+ ? new ClaudeBrain({
364
367
  workflow,
365
368
  toolManager: services.toolManager,
366
369
  description: this.options.description,
@@ -376,7 +379,7 @@ export class AgentHost {
376
379
  maxTurns: this.options.claudeMaxTurns,
377
380
  statelessPerWake: this.options.claudeStatelessPerWake,
378
381
  })
379
- : new Agent({
382
+ : new ZiggsBrain({
380
383
  workflow,
381
384
  toolManager: services.toolManager,
382
385
  promptBuilder: services.promptBuilder,
@@ -387,7 +390,7 @@ export class AgentHost {
387
390
  // than left as an unhandled rejection (made enqueue reject).
388
391
  this.stateTimers = new StateTimers((ev, laneKey) => {
389
392
  void this.eventQueue.enqueue(ev, laneKey).catch((err) => {
390
- runtimeLog.warn('AgentHost', `state-timer frame failed laneKey=${laneKey}: ${err?.message || err}`);
393
+ runtimeLog.warn('Agent', `state-timer frame failed laneKey=${laneKey}: ${err?.message || err}`);
391
394
  });
392
395
  });
393
396
  }
@@ -429,7 +432,7 @@ export class AgentHost {
429
432
  event.resourceId) ||
430
433
  null;
431
434
  if (agreementId)
432
- sessionId = `agrn-${agreementId}`;
435
+ sessionId = agreementLaneId(agreementId);
433
436
  }
434
437
  if (!sessionId)
435
438
  return;
@@ -456,14 +459,14 @@ export class AgentHost {
456
459
  async handleMessage(text, metadata = {}) {
457
460
  const messageId = metadata.messageId;
458
461
  if (messageId && this.seenMessages.has(messageId)) {
459
- runtimeLog.debug('AgentHost', `skip duplicate messageId=${messageId}`);
462
+ runtimeLog.debug('Agent', `skip duplicate messageId=${messageId}`);
460
463
  return;
461
464
  }
462
465
  if (messageId)
463
466
  this.seenMessages.mark(messageId);
464
467
  const ownAgentId = this._resolveOwnAgentId();
465
468
  if (!ownAgentId) {
466
- runtimeLog.warn('AgentHost', 'handleMessage: ownAgentId missing — dropping event');
469
+ runtimeLog.warn('Agent', 'handleMessage: ownAgentId missing — dropping event');
467
470
  return;
468
471
  }
469
472
  let enrichedText = typeof text === 'string' ? text : '';
@@ -483,14 +486,14 @@ export class AgentHost {
483
486
  const normalized = normalizeIncomingEvent({ text: enrichedText, metadata, ownAgentId });
484
487
  const sessionId = this.sessionResolver(normalized, ownAgentId);
485
488
  if (!sessionId) {
486
- runtimeLog.warn('AgentHost', `session resolver returned null ownAgentId=${ownAgentId} chatId=${normalized.chatId ?? 'null'}`);
489
+ runtimeLog.warn('Agent', `session resolver returned null ownAgentId=${ownAgentId} chatId=${normalized.chatId ?? 'null'}`);
487
490
  return;
488
491
  }
489
492
  const laneKey = `${ownAgentId}:${sessionId}`;
490
493
  if (!this.sessions.has(laneKey)) {
491
494
  this.sessions.set(laneKey, { sessionId, ownAgentId });
492
495
  }
493
- // The inbox loop owns ack (act-before-ack, delivery law); the host only
496
+ // The inbox loop owns ack (act-before-ack, delivery law); the agent only
494
497
  // routes the event onto its lane. No live-path watermark ack — there is no
495
498
  // separate live/push channel to reconcile against anymore.
496
499
  //
@@ -536,6 +539,13 @@ export class AgentHost {
536
539
  llmCalls,
537
540
  }), isTelemetryIngestEnabled(this.options.telemetryIngest));
538
541
  }
542
+ memoryForWake(session) {
543
+ return partitionMemoryStore(this.memory, memorySectionForWake({
544
+ sessionId: session.sessionId,
545
+ activeAgreementId: session.ctx
546
+ ?.activeAgreementId,
547
+ }));
548
+ }
539
549
  /**
540
550
  * A chat-lane session that still carries a cancelled hire from a previous
541
551
  * engagement will agreement_get that dead row and stop. Drop it so this
@@ -546,7 +556,7 @@ export class AgentHost {
546
556
  const pinned = ctx?.activeAgreementId;
547
557
  if (!pinned || !this.agreementService)
548
558
  return;
549
- if (session.sessionId === `agrn-${pinned}`)
559
+ if (session.sessionId === agreementLaneId(pinned))
550
560
  return;
551
561
  try {
552
562
  const agreement = await this.agreementService.get(pinned);
@@ -564,9 +574,7 @@ export class AgentHost {
564
574
  }
565
575
  }
566
576
  async laneAgreementIsTerminal(sessionId) {
567
- if (!sessionId.startsWith('agrn-'))
568
- return false;
569
- const agreementId = sessionId.slice('agrn-'.length);
577
+ const agreementId = agreementIdFromLane(sessionId);
570
578
  if (!agreementId || !this.agreementService)
571
579
  return false;
572
580
  try {
@@ -793,7 +801,7 @@ export class AgentHost {
793
801
  }
794
802
  }
795
803
  catch (err) {
796
- runtimeLog.warn('AgentHost', `sessionStore.load failed laneKey=${laneKey}: ${err?.message || err}`);
804
+ runtimeLog.warn('Agent', `sessionStore.load failed laneKey=${laneKey}: ${err?.message || err}`);
797
805
  }
798
806
  }
799
807
  await this.dropStaleCancelledAgreementSession(session);
@@ -802,6 +810,7 @@ export class AgentHost {
802
810
  sessionId: session.sessionId,
803
811
  laneKey,
804
812
  };
813
+ const memory = this.memoryForWake(session);
805
814
  // Recorded here rather than after the tick: this is the last point where the
806
815
  // input is still exactly what the brain will see, and a wake that goes on to
807
816
  // throw is the one most worth replaying.
@@ -817,7 +826,7 @@ export class AgentHost {
817
826
  });
818
827
  }
819
828
  catch (err) {
820
- runtimeLog.warn('AgentHost', `wakeTap failed: ${err?.message || err}`);
829
+ runtimeLog.warn('Agent', `wakeTap failed: ${err?.message || err}`);
821
830
  }
822
831
  }
823
832
  // The watchdog timer must be cleared once the tick settles — leaving it
@@ -842,7 +851,7 @@ export class AgentHost {
842
851
  };
843
852
  try {
844
853
  const result = await Promise.race([
845
- this.agent.tick({ event: frame, ctx: session.ctx, state: session.state, identity, eff, memory: this.memory, transitionTap: this.options.transitionTap, signal: tickAbort.signal }),
854
+ this.brain.tick({ event: frame, ctx: session.ctx, state: session.state, identity, eff, memory, transitionTap: this.options.transitionTap, signal: tickAbort.signal }),
846
855
  new Promise((_, reject) => {
847
856
  tickWatchdog = setTimeout(() => {
848
857
  tickAbort.abort(new Error('tick timeout'));
@@ -867,7 +876,7 @@ export class AgentHost {
867
876
  // these lines for one lane. Tokens are billable input (incl. cache).
868
877
  const m = result.metrics;
869
878
  if (m && (m.tokens?.total ?? 0) > 0) {
870
- runtimeLog.info('AgentHost', `wake agent=${identity.agentId} lane=${laneKey} llmCalls=${m.llmCalls} ` +
879
+ runtimeLog.info('Agent', `wake agent=${identity.agentId} lane=${laneKey} llmCalls=${m.llmCalls} ` +
871
880
  `tokens_in=${m.tokens.input} tokens_out=${m.tokens.output} total=${m.tokens.total}`);
872
881
  }
873
882
  // Single source of truth for usage/cost: emit the tick's
@@ -885,12 +894,12 @@ export class AgentHost {
885
894
  });
886
895
  }
887
896
  catch (err) {
888
- runtimeLog.warn('AgentHost', `metricsTap failed: ${err?.message || err}`);
897
+ runtimeLog.warn('Agent', `metricsTap failed: ${err?.message || err}`);
889
898
  }
890
899
  }
891
900
  if (this.sessionStore) {
892
901
  this.sessionStore.save(laneKey, { ctx: session.ctx, state: session.state }).catch((err) => {
893
- runtimeLog.warn('AgentHost', `sessionStore.save failed laneKey=${laneKey}: ${err?.message || err}`);
902
+ runtimeLog.warn('Agent', `sessionStore.save failed laneKey=${laneKey}: ${err?.message || err}`);
894
903
  });
895
904
  }
896
905
  // Arm only when the timeout was handled: an unhandled state-timeout must
@@ -919,12 +928,12 @@ export class AgentHost {
919
928
  kind: 'no-op',
920
929
  reason: 'tick_failed',
921
930
  });
922
- runtimeLog.warn('AgentHost', `tick failed laneKey=${laneKey}: ${err?.message || err}`);
931
+ runtimeLog.warn('Agent', `tick failed laneKey=${laneKey}: ${err?.message || err}`);
923
932
  // Deliberate re-arm on failure. Exact scenario: the tick timed out at
924
933
  // tickTimeoutMs (or sessionStore.load threw) leaving session.state at a
925
934
  // timed parked state — without re-arming, that lane would never wake
926
935
  // again unless a real event happens to arrive.
927
- this._armIfTimedParked(laneKey, session.state ?? this.agent.workflow.initial);
936
+ this._armIfTimedParked(laneKey, session.state ?? this.brain.workflow.initial);
928
937
  // Best-effort: also notify the user so the conversation isn't silently
929
938
  // stuck. Complements the timer re-arm above — that recovers a
930
939
  // parked lane; this tells a waiting human.
@@ -950,12 +959,12 @@ export class AgentHost {
950
959
  }
951
960
  }
952
961
  /**
953
- * Re-run one recorded wake against THIS host's agent, and nothing else.
962
+ * Re-run one recorded wake against THIS agent, and nothing else.
954
963
  *
955
964
  * The dev loop for a prompt: record a wake off a live run, edit the agent, and
956
965
  * see what it does differently in seconds instead of standing a world back up
957
966
  * for one turn. The brain, tools and effect handler are the real ones, so the
958
- * turn is real — its tool calls hit the backend this host is pointed at, which
967
+ * turn is real — its tool calls hit the backend this agent is pointed at, which
959
968
  * is why it belongs against a disposable world and not a shared one.
960
969
  *
961
970
  * Deliberately NOT routed through `_processEvent`: everything that method does
@@ -971,19 +980,23 @@ export class AgentHost {
971
980
  sessionId: record.sessionId,
972
981
  laneKey: record.laneKey || record.sessionId,
973
982
  };
983
+ const memory = this.memoryForWake({
984
+ sessionId: record.sessionId,
985
+ ctx: record.ctx,
986
+ });
974
987
  // Same watchdog as a live wake, for the same reason: an unaborted Claude
975
988
  // query outlives the promise that abandoned it and keeps spending.
976
989
  const abort = new AbortController();
977
990
  let watchdog;
978
991
  try {
979
992
  const result = await Promise.race([
980
- this.agent.tick({
993
+ this.brain.tick({
981
994
  event: record.event,
982
995
  ctx: record.ctx,
983
996
  state: record.state,
984
997
  identity,
985
998
  eff: this.eff,
986
- memory: this.memory,
999
+ memory,
987
1000
  transitionTap: this.options.transitionTap,
988
1001
  signal: abort.signal,
989
1002
  }),
@@ -1006,7 +1019,7 @@ export class AgentHost {
1006
1019
  });
1007
1020
  }
1008
1021
  catch (err) {
1009
- runtimeLog.warn('AgentHost', `metricsTap failed: ${err?.message || err}`);
1022
+ runtimeLog.warn('Agent', `metricsTap failed: ${err?.message || err}`);
1010
1023
  }
1011
1024
  }
1012
1025
  return result;
@@ -1019,25 +1032,33 @@ export class AgentHost {
1019
1032
  _armIfTimedParked(laneKey, stateName) {
1020
1033
  if (!stateName)
1021
1034
  return;
1022
- const sd = this.agent.workflow.states[stateName];
1035
+ const sd = this.brain.workflow.states[stateName];
1023
1036
  if (sd?.kind !== 'parked' || sd.timeoutMs === undefined)
1024
1037
  return;
1025
1038
  this.stateTimers.arm(laneKey, stateName, sd.timeoutMs);
1026
1039
  }
1027
1040
  _resolveOwnAgentId() {
1028
- // The host is constructed for exactly one agent; its own id is the only
1041
+ // The body is constructed for exactly one identity; its own id is the only
1029
1042
  // source. (The socket-frame-era metadata fallbacks — self/agent/ziggsAgentId
1030
1043
  // — are gone: nothing on the inbox wire ever carried them.)
1031
1044
  return this.options.agentId || null;
1032
1045
  }
1033
1046
  async connectAsync(_timeout) {
1034
- // Bootstrap active assigned tasks once (HTTP pull → lane), then start the
1035
- // per-agent inbox long-poll loop — the agent's sole wake source. There is
1036
- // no socket to connect and no reconnect: a crash is just the loop's next
1037
- // iteration (the cursor is server-side). `_timeout` is accepted for
1038
- // backward-compatible call sites but no longer gates a handshake.
1047
+ if (this.started)
1048
+ return this;
1049
+ // Start the per-agent inbox long-poll loop — the agent's sole wake source.
1050
+ // There is no socket to connect and no reconnect: a crash is just the
1051
+ // loop's next iteration (the cursor is server-side). `_timeout` is accepted
1052
+ // for backward-compatible call sites but no longer gates a handshake.
1053
+ //
1054
+ // There is no separate catch-up sweep here any more. `tasksAwaitingMe` is
1055
+ // re-derived from the collection on every pass and redelivers an open
1056
+ // assigned task until it closes, so the loop's first pass IS the catch-up —
1057
+ // a restarted agent sees its standing work with nothing extra to run. The
1058
+ // sweep that used to sit here pulled the same list over
1059
+ // `GET /agents/:id/tasks` and reached one gate the inbox does not, which is
1060
+ // how a withheld join node got handed to its assignee before its turn.
1039
1061
  if (this.inboxLoopEnabled) {
1040
- await this._bootstrapAssignedActiveTasks();
1041
1062
  const label = this.options.name || 'agent';
1042
1063
  this.inboxLoop = runInboxLoop({
1043
1064
  creds: {
@@ -1070,10 +1091,12 @@ export class AgentHost {
1070
1091
  },
1071
1092
  });
1072
1093
  }
1094
+ this.pollRunner?.start();
1095
+ this.started = true;
1073
1096
  return this;
1074
1097
  }
1075
1098
  /**
1076
- * plain string compare against {@link AgentHostOptions.requestMatchTags}
1099
+ * plain string compare against {@link AgentOptions.requestMatchTags}
1077
1100
  * and `specialization`. No match → return (caller acks). Match → normal wake.
1078
1101
  */
1079
1102
  async triageRequestDelivery(request) {
@@ -1093,52 +1116,6 @@ export class AgentHost {
1093
1116
  agreementId: request.agreementId,
1094
1117
  });
1095
1118
  }
1096
- async _bootstrapAssignedActiveTasks() {
1097
- const agentId = this.options.agentId;
1098
- if (!agentId || !this.taskService)
1099
- return;
1100
- let tasks = [];
1101
- try {
1102
- tasks = await this.taskService.getActiveTasksForAgent(agentId);
1103
- }
1104
- catch (err) {
1105
- runtimeLog.warn(this.options.name || 'agent', `active-task bootstrap skipped: ${err?.message ?? err}`);
1106
- return;
1107
- }
1108
- for (const row of tasks) {
1109
- const taskId = row.taskId;
1110
- if (!taskId)
1111
- continue;
1112
- if (row.assigneeId && row.assigneeId !== agentId)
1113
- continue;
1114
- let full;
1115
- try {
1116
- full = (await this.taskService.getTask(taskId));
1117
- }
1118
- catch {
1119
- continue;
1120
- }
1121
- const state = String(full.state ?? '');
1122
- if (state !== 'active' && state !== 'in-progress')
1123
- continue;
1124
- const sessionId = laneSessionIdForTask(full);
1125
- const laneKey = `${agentId}:${sessionId}`;
1126
- if (!this.sessions.has(laneKey)) {
1127
- this.sessions.set(laneKey, { sessionId, ownAgentId: agentId });
1128
- }
1129
- runtimeLog.info(this.options.name || 'agent', `bootstrap task-assigned ${taskId} (state=${state})`);
1130
- await this.eventQueue.enqueue({
1131
- type: 'task_result',
1132
- result: {
1133
- ...full,
1134
- taskId: full.taskId ?? taskId,
1135
- state,
1136
- providerIsYou: true,
1137
- },
1138
- chatId: sessionId,
1139
- }, laneKey);
1140
- }
1141
- }
1142
1119
  /** replay task-assigned for an existing active task (demo recovery). */
1143
1120
  async wakeTaskAssignment(taskId) {
1144
1121
  const agentId = this.options.agentId;
@@ -1166,7 +1143,13 @@ export class AgentHost {
1166
1143
  chatId: sessionId,
1167
1144
  }, laneKey);
1168
1145
  }
1169
- disconnect() { this.stateTimers.clearAll(); void this.inboxLoop?.stop(); this.inboxLoop = null; }
1146
+ disconnect() {
1147
+ this.started = false;
1148
+ this.pollRunner?.stop();
1149
+ this.stateTimers.clearAll();
1150
+ void this.inboxLoop?.stop();
1151
+ this.inboxLoop = null;
1152
+ }
1170
1153
  /**
1171
1154
  * Waits for in-flight ticks to finish. Call before process.exit() on SIGTERM.
1172
1155
  * A timeout is returned, not thrown — the caller names it (lab verdict, fleet
@@ -1174,20 +1157,43 @@ export class AgentHost {
1174
1157
  */
1175
1158
  drain(timeoutMs = 30_000) { return this.eventQueue.waitForIdle(timeoutMs); }
1176
1159
  registerTool(tool) { return this.toolManager.register(tool); }
1177
- isConnected() { return this.inboxLoop?.running ?? false; }
1160
+ isConnected() { return this.started && (!this.inboxLoopEnabled || (this.inboxLoop?.running ?? false)); }
1161
+ /**
1162
+ * Whether this agent is still reading its inbox, and when it last did. One
1163
+ * signal, three readers: the store's online badge, an operator's health
1164
+ * check, and monitoring. `lastPassAt` is the read itself; `lastAttemptAt`
1165
+ * says the loop still turns when the server is the thing that is down.
1166
+ * A `pollingInbox: false` agent (inboxCatchUp disabled, lab and eval paths)
1167
+ * has nothing to be stale about and reports no timestamps.
1168
+ */
1169
+ inboxLiveness() {
1170
+ return {
1171
+ agentId: this.options.agentId,
1172
+ connected: this.isConnected(),
1173
+ pollingInbox: this.inboxLoopEnabled,
1174
+ lastPassAt: this.inboxLoop?.lastPassAt ?? null,
1175
+ lastAttemptAt: this.inboxLoop?.lastAttemptAt ?? null,
1176
+ };
1177
+ }
1178
1178
  }
1179
1179
  /**
1180
- * createAgent(config) → AgentHost
1180
+ * createAgent(config) → Agent
1181
1181
  * createAgentPool([cfg, ...]) → ConnectionPool (lazy fleet, async-loaded)
1182
1182
  */
1183
1183
  export function createAgent(options = {}) {
1184
- return new AgentHost(options);
1184
+ return new Agent(options);
1185
1185
  }
1186
1186
  export async function createAgentPool(configs) {
1187
1187
  const ConnectionPool = await loadConnectionPool();
1188
1188
  const pool = new ConnectionPool();
1189
1189
  pool.register(configs);
1190
- await pool.connectAll();
1191
- return pool;
1190
+ try {
1191
+ await pool.connectAll();
1192
+ return pool;
1193
+ }
1194
+ catch (err) {
1195
+ await pool.shutdown();
1196
+ throw err;
1197
+ }
1192
1198
  }
1193
- export default AgentHost;
1199
+ export default Agent;
@@ -7,7 +7,7 @@ interface AdapterOptions {
7
7
  /**
8
8
  * Anthropic adapter satisfying the same LlmAdapter contract as OpenAIAdapter:
9
9
  * OpenAI-wire-shaped LlmMessage[] in, { content, tool_calls, usage } out.
10
- * Selected by AgentHost when the configured model id starts with "claude".
10
+ * Selected by Agent when the configured model id starts with "claude".
11
11
  */
12
12
  export declare class AnthropicAdapter {
13
13
  private client;
@@ -8,7 +8,7 @@ const MAX_COMPLETION_TOKENS = 16000;
8
8
  /**
9
9
  * Anthropic adapter satisfying the same LlmAdapter contract as OpenAIAdapter:
10
10
  * OpenAI-wire-shaped LlmMessage[] in, { content, tool_calls, usage } out.
11
- * Selected by AgentHost when the configured model id starts with "claude".
11
+ * Selected by Agent when the configured model id starts with "claude".
12
12
  */
13
13
  export class AnthropicAdapter {
14
14
  client;