@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.
- package/CHANGELOG.md +10 -0
- package/README.md +110 -88
- package/dist/{AgentHost.d.ts → Agent.d.ts} +50 -27
- package/dist/{AgentHost.js → Agent.js} +135 -129
- package/dist/adapters/AnthropicAdapter.d.ts +1 -1
- package/dist/adapters/AnthropicAdapter.js +1 -1
- package/dist/{agent/ClaudeSdkAgent.d.ts → brain/ClaudeBrain.d.ts} +23 -11
- package/dist/{agent/ClaudeSdkAgent.js → brain/ClaudeBrain.js} +99 -99
- package/dist/{agent/Agent.d.ts → brain/ZiggsBrain.d.ts} +23 -20
- package/dist/{agent/Agent.js → brain/ZiggsBrain.js} +12 -11
- package/dist/{agent → brain}/claudeCognition.d.ts +1 -1
- package/dist/{agent → brain}/claudeCognition.js +1 -1
- package/dist/{agent → brain}/claudeSdkProtocol.d.ts +1 -1
- package/dist/{agent → brain}/claudeSdkProtocol.js +1 -1
- package/dist/{agent → brain}/claudeSdkTools.d.ts +1 -1
- package/dist/{agent → brain}/claudeSdkTools.js +5 -14
- package/dist/{agent → brain}/hostedAgentConnection.d.ts +1 -1
- package/dist/{agent → brain}/hostedAgentConnection.js +3 -3
- package/dist/brain/index.d.ts +6 -6
- package/dist/brain/index.js +2 -2
- package/dist/brain/onBehalfAgreement.d.ts +51 -0
- package/dist/brain/onBehalfAgreement.js +99 -0
- package/dist/{agent → brain}/toolManagerToSdkMcp.js +10 -1
- package/dist/defineAgent.js +4 -4
- package/dist/formatters/HistoryFormatter.js +2 -1
- package/dist/index.d.ts +30 -31
- package/dist/index.js +24 -25
- package/dist/{adapters/McpAdapter.d.ts → mcp/createMcpAgent.d.ts} +16 -15
- package/dist/{adapters/McpAdapter.js → mcp/createMcpAgent.js} +82 -46
- package/dist/memory/MemoryStore.d.ts +18 -1
- package/dist/memory/MemoryStore.js +43 -0
- package/dist/{server → platform}/ConnectionPool.d.ts +9 -3
- package/dist/{server → platform}/ConnectionPool.js +16 -4
- package/dist/{server → platform}/EventQueue.d.ts +1 -1
- package/dist/{server → platform}/StateTimers.d.ts +1 -1
- package/dist/{server → platform}/StateTimers.js +1 -1
- package/dist/{server → platform}/ZiggsEffectHandler.d.ts +4 -4
- package/dist/{server → platform}/ZiggsEffectHandler.js +2 -2
- package/dist/{server → platform}/inboxLoop.d.ts +34 -1
- package/dist/{server → platform}/inboxLoop.js +122 -14
- package/dist/{server → platform}/polls/PollKind.d.ts +4 -7
- package/dist/{server → platform}/polls/PollKind.js +3 -6
- package/dist/{server → platform}/polls/PollRunner.d.ts +5 -6
- package/dist/{server → platform}/polls/PollRunner.js +24 -12
- package/dist/platform/polls/declaredPollRunner.d.ts +4 -0
- package/dist/platform/polls/declaredPollRunner.js +29 -0
- package/dist/{server → platform}/tools/tier2/mcpConnectionTools.js +32 -18
- package/dist/runtime/bootstrapCtx.d.ts +1 -1
- package/dist/runtime/bootstrapCtx.js +1 -1
- package/dist/runtime/buildOutcome.js +1 -1
- package/dist/runtime/runTurn.d.ts +1 -1
- package/dist/runtime/runTurn.js +18 -18
- package/dist/runtime/validateWorkflow.js +1 -1
- package/dist/skills/parseSkill.d.ts +7 -2
- package/dist/skills/parseSkill.js +22 -5
- package/dist/skills/skillReadReference.d.ts +6 -0
- package/dist/skills/skillReadReference.js +27 -0
- package/dist/tools/needs.js +2 -2
- package/dist/tools/toolCreds.d.ts +1 -1
- package/dist/tools/toolCreds.js +1 -1
- package/dist/types.d.ts +3 -3
- package/package.json +2 -2
- package/dist/server/createHealthServer.d.ts +0 -7
- package/dist/server/createHealthServer.js +0 -75
- package/dist/server/runLauncher.d.ts +0 -22
- package/dist/server/runLauncher.js +0 -160
- /package/dist/{agent → brain}/toolManagerToSdkMcp.d.ts +0 -0
- /package/dist/{server → platform}/EventQueue.js +0 -0
- /package/dist/{server → platform}/OutboxBuffer.d.ts +0 -0
- /package/dist/{server → platform}/OutboxBuffer.js +0 -0
- /package/dist/{server → platform}/SeenMessages.d.ts +0 -0
- /package/dist/{server → platform}/SeenMessages.js +0 -0
- /package/dist/{server → platform}/agreements/AgreementService.d.ts +0 -0
- /package/dist/{server → platform}/agreements/AgreementService.js +0 -0
- /package/dist/{server → platform}/proactive/ProactiveTrigger.d.ts +0 -0
- /package/dist/{server → platform}/proactive/ProactiveTrigger.js +0 -0
- /package/dist/{server → platform}/requestMatch.d.ts +0 -0
- /package/dist/{server → platform}/requestMatch.js +0 -0
- /package/dist/{server → platform}/tasks/TaskService.d.ts +0 -0
- /package/dist/{server → platform}/tasks/TaskService.js +0 -0
- /package/dist/{server → platform}/tasks/index.d.ts +0 -0
- /package/dist/{server → platform}/tasks/index.js +0 -0
- /package/dist/{server → platform}/telemetryIngest.d.ts +0 -0
- /package/dist/{server → platform}/telemetryIngest.js +0 -0
- /package/dist/{server → platform}/tools/index.d.ts +0 -0
- /package/dist/{server → platform}/tools/index.js +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolRunner.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolRunner.js +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/artifactTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/artifactTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/chatTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/chatTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/connectionTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/connectionTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/contextTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/contextTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/discoveryTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/discoveryTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/grantsTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/grantsTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/introductionTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/introductionTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/linkTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/linkTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/marketplaceTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/marketplaceTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/mcpConnectionTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/paymentTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/paymentTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/taskTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/taskTools.js +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { Brain } from './index.js';
|
|
1
2
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
3
|
import type { Workflow } from '../types.js';
|
|
3
4
|
import type { ToolManager } from '../tools/ToolManager.js';
|
|
4
5
|
import { type RawEvent } from '../runtime/buildOutcome.js';
|
|
5
|
-
import type { TickInput, TickOutput } from './
|
|
6
|
+
import type { TickInput, TickOutput } from './ZiggsBrain.js';
|
|
6
7
|
/**
|
|
7
8
|
* status notices must not spend a Claude turn answering
|
|
8
9
|
* nobody. Real work wakes arrive as chat `message`, synthetic `task_result`
|
|
@@ -21,9 +22,15 @@ import type { TickInput, TickOutput } from './Agent.js';
|
|
|
21
22
|
* message still lands in chat history and arrives as context on the next
|
|
22
23
|
* real wake — skipped here is the LLM turn, not the information.
|
|
23
24
|
*/
|
|
25
|
+
/**
|
|
26
|
+
* Store publish chats the agent in a room named `probe_<agentId>_<ts>`.
|
|
27
|
+
* That check is "are you connected and can you reply", not a hired job —
|
|
28
|
+
* spending a model turn on it is how a long skill prompt 504s the gate.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isCapabilityProbeSession(sessionId: string | undefined | null): boolean;
|
|
24
31
|
export declare function isNoticeOnlyWake(event: RawEvent | undefined, ownAgentId?: string | null): boolean;
|
|
25
32
|
export type CognitionKind = 'fsm' | 'claude-sdk';
|
|
26
|
-
export interface
|
|
33
|
+
export interface ClaudeBrainOptions {
|
|
27
34
|
workflow: Workflow;
|
|
28
35
|
toolManager: ToolManager;
|
|
29
36
|
/** Store-facing one-liner. Used as the role block only when `systemPrompt` is absent. */
|
|
@@ -63,15 +70,18 @@ export interface ClaudeSdkAgentOptions {
|
|
|
63
70
|
export declare function isMaxTurnsError(err: unknown): boolean;
|
|
64
71
|
export declare function isMissingSessionError(err: unknown): boolean;
|
|
65
72
|
/**
|
|
66
|
-
* A
|
|
67
|
-
* side channel, same tool surface as
|
|
73
|
+
* A brain driven by the Claude Agent SDK. Same body, same `eff`
|
|
74
|
+
* side channel, same tool surface as `ZiggsBrain` — the ONLY difference is
|
|
68
75
|
* the loop: instead of the FSM + `runTurn`, a wake drives a headless Claude
|
|
69
76
|
* `query()` (see "host is the product; the brain is how it decides").
|
|
70
77
|
*/
|
|
71
|
-
export declare class
|
|
78
|
+
export declare class ClaudeBrain implements Brain {
|
|
79
|
+
readonly kind: "claude";
|
|
72
80
|
workflow: Workflow;
|
|
73
81
|
toolManager: ToolManager;
|
|
74
82
|
private rolePrompt;
|
|
83
|
+
/** Store one-liner — used when the publish probe asks who we are. */
|
|
84
|
+
private listingBlurb;
|
|
75
85
|
private specialization;
|
|
76
86
|
private model;
|
|
77
87
|
private anthropicKey;
|
|
@@ -81,12 +91,14 @@ export declare class ClaudeSdkAgent {
|
|
|
81
91
|
private statelessPerWake;
|
|
82
92
|
private queryFn;
|
|
83
93
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
94
|
+
* Per-lane tool context. One agent object serves every room, and a
|
|
95
|
+
* wake's query can still be running when another lane starts. A single
|
|
96
|
+
* `current` field meant B's room/sender/write keys overwrote A's
|
|
97
|
+
* mid-query. Wakes on one lane stay serialized; one server per lane is
|
|
98
|
+
* enough, and getContext closes over that lane's key.
|
|
87
99
|
*/
|
|
88
|
-
private
|
|
89
|
-
private
|
|
90
|
-
constructor(opts:
|
|
100
|
+
private toolContextByLane;
|
|
101
|
+
private sdkMcpByLane;
|
|
102
|
+
constructor(opts: ClaudeBrainOptions);
|
|
91
103
|
tick({ event, ctx, state, identity, eff, signal }: TickInput): Promise<TickOutput>;
|
|
92
104
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAgreementId } from '@ziggs-ai/api-client';
|
|
1
2
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
3
|
import { isMessageAddressedElsewhere, outcomeFromEvent } from '../runtime/buildOutcome.js';
|
|
3
4
|
import { getBatchEvents } from '../context/batch.js';
|
|
@@ -8,6 +9,7 @@ import { buildZiggsSdkMcpServer } from './toolManagerToSdkMcp.js';
|
|
|
8
9
|
import { claudeQueryOptions, buildClaudeEventPrompt, summarizeContext, extractAssistantText, } from './claudeCognition.js';
|
|
9
10
|
import { assertHostedAgentZiggsTools } from './hostedAgentConnection.js';
|
|
10
11
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
12
|
+
import { resolveOnBehalfAgreementId, } from './onBehalfAgreement.js';
|
|
11
13
|
/**
|
|
12
14
|
* status notices must not spend a Claude turn answering
|
|
13
15
|
* nobody. Real work wakes arrive as chat `message`, synthetic `task_result`
|
|
@@ -26,6 +28,14 @@ import { runtimeLog } from '../shared/runtimeLog.js';
|
|
|
26
28
|
* message still lands in chat history and arrives as context on the next
|
|
27
29
|
* real wake — skipped here is the LLM turn, not the information.
|
|
28
30
|
*/
|
|
31
|
+
/**
|
|
32
|
+
* Store publish chats the agent in a room named `probe_<agentId>_<ts>`.
|
|
33
|
+
* That check is "are you connected and can you reply", not a hired job —
|
|
34
|
+
* spending a model turn on it is how a long skill prompt 504s the gate.
|
|
35
|
+
*/
|
|
36
|
+
export function isCapabilityProbeSession(sessionId) {
|
|
37
|
+
return typeof sessionId === 'string' && sessionId.startsWith('probe_');
|
|
38
|
+
}
|
|
29
39
|
export function isNoticeOnlyWake(event, ownAgentId) {
|
|
30
40
|
const events = getBatchEvents(event);
|
|
31
41
|
if (events.length === 0)
|
|
@@ -164,66 +174,18 @@ function extractMetrics(message) {
|
|
|
164
174
|
};
|
|
165
175
|
}
|
|
166
176
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* `senderId` — which, under a delegation, is the delegating agent (who owns no
|
|
170
|
-
* connection) → a spurious NO_USER_CONNECTION even though the payer IS connected.
|
|
171
|
-
*
|
|
172
|
-
* The id must be found on ANY wake the worker might fetch on, not just
|
|
173
|
-
* `task_result` events. In practice the worker often fetches while reacting to
|
|
174
|
-
* the proposal-approved / agreement-active lifecycle events, which carry the id
|
|
175
|
-
* at the event top level rather than under `result`. Reading only
|
|
176
|
-
* `result.agreementId` (the original wiring) left those wakes with no
|
|
177
|
-
* agreement → the false "authorize your connection" prompt. Resolve from every
|
|
178
|
-
* reliable source instead:
|
|
179
|
-
* 1. task_result events → `result.agreementId`
|
|
180
|
-
* 2. agreement-lifecycle events → top-level `agreementId`
|
|
181
|
-
* 3. task lanes → `sessionId` IS the agreement id
|
|
182
|
-
* 4. fallback → the single active agreement in context this agent provides
|
|
183
|
-
*/
|
|
184
|
-
function resolveOnBehalfAgreementId(rawEvent, contextSnap, agentId, sessionId) {
|
|
185
|
-
const str = (v) => typeof v === 'string' && v ? v : undefined;
|
|
186
|
-
const fromResult = str(rawEvent?.result?.agreementId);
|
|
187
|
-
if (fromResult)
|
|
188
|
-
return fromResult;
|
|
189
|
-
const fromEvent = str(rawEvent?.agreementId);
|
|
190
|
-
if (fromEvent)
|
|
191
|
-
return fromEvent;
|
|
192
|
-
if (typeof sessionId === 'string' && sessionId.startsWith('agr_'))
|
|
193
|
-
return sessionId;
|
|
194
|
-
// Fallback: exactly one active agreement in context where this agent is the
|
|
195
|
-
// provider. Guarded to a single match so we never send the wrong agreement.
|
|
196
|
-
const ags = Array.isArray(contextSnap?.agreements)
|
|
197
|
-
? contextSnap.agreements
|
|
198
|
-
: [];
|
|
199
|
-
const provides = ags.filter((a) => {
|
|
200
|
-
const provider = a?.parties?.provider;
|
|
201
|
-
const providerIds = [provider?.actor, a?.providerId, a?.providerAgentId, a?.executorId];
|
|
202
|
-
const isProvider = providerIds.some((x) => x === agentId);
|
|
203
|
-
const status = a?.status != null ? String(a.status).toLowerCase() : '';
|
|
204
|
-
if (status === 'cancelled' ||
|
|
205
|
-
status === 'refunded' ||
|
|
206
|
-
status === 'fulfilled') {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
const active = status === 'active' || status === 'approved';
|
|
210
|
-
return isProvider && active;
|
|
211
|
-
});
|
|
212
|
-
if (provides.length === 1) {
|
|
213
|
-
return str(provides[0].agreementId) ?? str(provides[0].id) ?? str(provides[0]._id);
|
|
214
|
-
}
|
|
215
|
-
return undefined;
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* A Ziggs agent whose brain is the Claude Agent SDK. Same host, same `eff`
|
|
219
|
-
* side channel, same tool surface as the FSM `Agent` — the ONLY difference is
|
|
177
|
+
* A brain driven by the Claude Agent SDK. Same body, same `eff`
|
|
178
|
+
* side channel, same tool surface as `ZiggsBrain` — the ONLY difference is
|
|
220
179
|
* the loop: instead of the FSM + `runTurn`, a wake drives a headless Claude
|
|
221
180
|
* `query()` (see "host is the product; the brain is how it decides").
|
|
222
181
|
*/
|
|
223
|
-
export class
|
|
182
|
+
export class ClaudeBrain {
|
|
183
|
+
kind = 'claude';
|
|
224
184
|
workflow;
|
|
225
185
|
toolManager;
|
|
226
186
|
rolePrompt;
|
|
187
|
+
/** Store one-liner — used when the publish probe asks who we are. */
|
|
188
|
+
listingBlurb;
|
|
227
189
|
specialization;
|
|
228
190
|
model;
|
|
229
191
|
anthropicKey;
|
|
@@ -233,25 +195,28 @@ export class ClaudeSdkAgent {
|
|
|
233
195
|
statelessPerWake;
|
|
234
196
|
queryFn;
|
|
235
197
|
/**
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
198
|
+
* Per-lane tool context. One agent object serves every room, and a
|
|
199
|
+
* wake's query can still be running when another lane starts. A single
|
|
200
|
+
* `current` field meant B's room/sender/write keys overwrote A's
|
|
201
|
+
* mid-query. Wakes on one lane stay serialized; one server per lane is
|
|
202
|
+
* enough, and getContext closes over that lane's key.
|
|
239
203
|
*/
|
|
240
|
-
|
|
241
|
-
|
|
204
|
+
toolContextByLane = new Map();
|
|
205
|
+
sdkMcpByLane = new Map();
|
|
242
206
|
constructor(opts) {
|
|
243
207
|
if (!opts.workflow?.states)
|
|
244
|
-
throw new Error('
|
|
208
|
+
throw new Error('ClaudeBrain: workflow with states is required');
|
|
245
209
|
if (!opts.toolManager)
|
|
246
|
-
throw new Error('
|
|
210
|
+
throw new Error('ClaudeBrain: toolManager is required');
|
|
247
211
|
if (!opts.anthropicKey)
|
|
248
|
-
throw new Error('
|
|
212
|
+
throw new Error('ClaudeBrain: anthropicKey is required');
|
|
249
213
|
this.workflow = opts.workflow;
|
|
250
214
|
this.toolManager = opts.toolManager;
|
|
251
215
|
// the prompt and the store blurb are two different strings. Agents
|
|
252
216
|
// written before the split still carry the prompt in `description`, so fall
|
|
253
217
|
// back to it rather than silently dropping their role.
|
|
254
218
|
this.rolePrompt = opts.systemPrompt || opts.description;
|
|
219
|
+
this.listingBlurb = opts.description;
|
|
255
220
|
this.specialization = opts.specialization ?? null;
|
|
256
221
|
this.model = opts.model;
|
|
257
222
|
this.anthropicKey = opts.anthropicKey;
|
|
@@ -263,18 +228,15 @@ export class ClaudeSdkAgent {
|
|
|
263
228
|
}
|
|
264
229
|
async tick({ event, ctx, state, identity, eff, signal }) {
|
|
265
230
|
if (!identity?.sessionId)
|
|
266
|
-
throw new Error('
|
|
231
|
+
throw new Error('ClaudeBrain.tick: identity.sessionId is required');
|
|
267
232
|
if (!eff)
|
|
268
|
-
throw new Error('
|
|
233
|
+
throw new Error('ClaudeBrain.tick: eff is required');
|
|
269
234
|
const rawEvent = event;
|
|
270
235
|
const nextCtx = bootstrapCtx(identity, ctx);
|
|
271
236
|
nextCtx.lastOutcome = outcomeFromEvent(rawEvent ?? null, identity.agentId);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
runtimeLog.warn('ClaudeSdkAgent', `wake-rate breaker tripped: ${recentDeliveries.length} wakes in ${WAKE_WINDOW_MS / 60_000}min ` +
|
|
276
|
-
`session=${identity.sessionId} agent=${this.agentId} — skipping LLM for this wake`);
|
|
277
|
-
nextCtx.recentDeliveries = recentDeliveries;
|
|
237
|
+
if (isNoticeOnlyWake(rawEvent, identity.agentId)) {
|
|
238
|
+
runtimeLog.info('ClaudeBrain', `notice-only wake — skipping LLM session=${identity.sessionId} agent=${this.agentId} ` +
|
|
239
|
+
`type=${rawEvent?.type ?? 'unknown'}`);
|
|
278
240
|
return {
|
|
279
241
|
ctx: nextCtx,
|
|
280
242
|
state: state ?? DEFAULT_STATE,
|
|
@@ -283,10 +245,42 @@ export class ClaudeSdkAgent {
|
|
|
283
245
|
metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
|
|
284
246
|
};
|
|
285
247
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
248
|
+
// Store publish must hear a reply even when leftover mail already tripped
|
|
249
|
+
// the wake-rate breaker. The breaker used to sit above this and park the
|
|
250
|
+
// probe with no send-message — Register then died on ALB 504.
|
|
251
|
+
if (nextCtx.lastOutcome?.kind === 'message-received' &&
|
|
252
|
+
isCapabilityProbeSession(identity.sessionId)) {
|
|
253
|
+
const text = `I received your message. ${this.listingBlurb.trim().split(/\r?\n/)[0]?.trim() || 'I am online.'}`;
|
|
254
|
+
const writeKeys = createDeliveryWriteKeys(identity.laneKey, rawEvent);
|
|
255
|
+
const senderId = rawEvent?.senderId != null && String(rawEvent.senderId).trim() !== ''
|
|
256
|
+
? String(rawEvent.senderId)
|
|
257
|
+
: undefined;
|
|
258
|
+
await eff({
|
|
259
|
+
kind: 'send-message',
|
|
260
|
+
sessionId: identity.sessionId,
|
|
261
|
+
...(senderId ? { receiverId: senderId } : {}),
|
|
262
|
+
text,
|
|
263
|
+
messageId: writeKeys.messageKey({
|
|
264
|
+
text,
|
|
265
|
+
sessionId: identity.sessionId,
|
|
266
|
+
receiverId: senderId,
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
269
|
+
runtimeLog.info('ClaudeBrain', `capability-probe reply — skipping LLM session=${identity.sessionId} agent=${this.agentId}`);
|
|
270
|
+
return {
|
|
271
|
+
ctx: nextCtx,
|
|
272
|
+
state: state ?? DEFAULT_STATE,
|
|
273
|
+
parked: true,
|
|
274
|
+
stateTimeoutUnhandled: false,
|
|
275
|
+
metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const now = Date.now();
|
|
279
|
+
const recentDeliveries = (nextCtx.recentDeliveries ?? []).filter((t) => now - t < WAKE_WINDOW_MS);
|
|
280
|
+
if (recentDeliveries.length >= MAX_WAKES_PER_WINDOW) {
|
|
281
|
+
runtimeLog.warn('ClaudeBrain', `wake-rate breaker tripped: ${recentDeliveries.length} wakes in ${WAKE_WINDOW_MS / 60_000}min ` +
|
|
282
|
+
`session=${identity.sessionId} agent=${this.agentId} — skipping LLM for this wake`);
|
|
283
|
+
nextCtx.recentDeliveries = recentDeliveries;
|
|
290
284
|
return {
|
|
291
285
|
ctx: nextCtx,
|
|
292
286
|
state: state ?? DEFAULT_STATE,
|
|
@@ -295,6 +289,7 @@ export class ClaudeSdkAgent {
|
|
|
295
289
|
metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
|
|
296
290
|
};
|
|
297
291
|
}
|
|
292
|
+
nextCtx.recentDeliveries = [...recentDeliveries, now];
|
|
298
293
|
let contextSnap = null;
|
|
299
294
|
try {
|
|
300
295
|
// #5: pre-read is for wake gating + summarizeContext (a count) +
|
|
@@ -307,7 +302,7 @@ export class ClaudeSdkAgent {
|
|
|
307
302
|
});
|
|
308
303
|
}
|
|
309
304
|
catch (err) {
|
|
310
|
-
runtimeLog.warn('
|
|
305
|
+
runtimeLog.warn('ClaudeBrain', `read-context failed session=${identity.sessionId}: ${err.message}`);
|
|
311
306
|
}
|
|
312
307
|
// Same guard as the FSM's runTurn: a message wake for a chat the backend
|
|
313
308
|
// no longer resolves (deleted / not visible) has no world to act in —
|
|
@@ -319,17 +314,15 @@ export class ClaudeSdkAgent {
|
|
|
319
314
|
// signal. Loud-log that mis-key; message wakes still skip below.
|
|
320
315
|
if (contextSnap?.chatMissing === true) {
|
|
321
316
|
const sessionId = identity.sessionId;
|
|
322
|
-
const looksLikeBareAgreement =
|
|
323
|
-
sessionId.startsWith('agr_') &&
|
|
324
|
-
!sessionId.startsWith('agrn-');
|
|
317
|
+
const looksLikeBareAgreement = isAgreementId(sessionId);
|
|
325
318
|
if (looksLikeBareAgreement) {
|
|
326
|
-
runtimeLog.warn('
|
|
319
|
+
runtimeLog.warn('ClaudeBrain', `chatMissing on non-chat sessionId=${sessionId} agent=${this.agentId} ` +
|
|
327
320
|
`outcome=${nextCtx.lastOutcome?.kind ?? 'none'} — lane was keyed by ` +
|
|
328
321
|
`agreementId instead of the agreement's origin/notification chat; ` +
|
|
329
322
|
`wake continues with an empty snapshot`);
|
|
330
323
|
}
|
|
331
324
|
if (nextCtx.lastOutcome?.kind === 'message-received') {
|
|
332
|
-
runtimeLog.warn('
|
|
325
|
+
runtimeLog.warn('ClaudeBrain', `chat ${sessionId} not found on backend — skipping message wake agent=${this.agentId}`);
|
|
333
326
|
return {
|
|
334
327
|
ctx: nextCtx,
|
|
335
328
|
state: state ?? DEFAULT_STATE,
|
|
@@ -364,26 +357,33 @@ export class ClaudeSdkAgent {
|
|
|
364
357
|
// MCP call resolve the originating end-user (payer) rather than the
|
|
365
358
|
// delegating agent (senderId). Resolved from every reliable wire source,
|
|
366
359
|
// not just task_result events — see resolveOnBehalfAgreementId.
|
|
367
|
-
|
|
360
|
+
// Skip payer-only subs named on the worker result; bind to the
|
|
361
|
+
// provider hire (see onBehalfAgreement.ts).
|
|
362
|
+
onBehalfOfAgreementId: resolveOnBehalfAgreementId(rawEvent, Array.isArray(contextSnap?.agreements)
|
|
363
|
+
? contextSnap.agreements
|
|
364
|
+
: null, this.agentId, identity.sessionId),
|
|
368
365
|
runId,
|
|
369
366
|
writeKeys,
|
|
370
367
|
};
|
|
371
|
-
|
|
372
|
-
|
|
368
|
+
const lane = identity.laneKey;
|
|
369
|
+
this.toolContextByLane.set(lane, toolContext);
|
|
370
|
+
let sdkMcpServer = this.sdkMcpByLane.get(lane);
|
|
371
|
+
if (!sdkMcpServer) {
|
|
373
372
|
const availableTools = await this.toolManager.getAvailableTools();
|
|
374
|
-
|
|
375
|
-
const ctx = this.
|
|
373
|
+
sdkMcpServer = buildZiggsSdkMcpServer(availableTools, () => {
|
|
374
|
+
const ctx = this.toolContextByLane.get(lane);
|
|
376
375
|
if (!ctx) {
|
|
377
|
-
throw new Error(
|
|
376
|
+
throw new Error(`ClaudeBrain: tool context missing for lane ${lane}`);
|
|
378
377
|
}
|
|
379
378
|
return ctx;
|
|
380
379
|
}, {
|
|
381
380
|
name: 'ziggs',
|
|
382
381
|
instructions: 'In-process Ziggs tools backed by api-client. No external MCP process.',
|
|
383
382
|
});
|
|
383
|
+
this.sdkMcpByLane.set(lane, sdkMcpServer);
|
|
384
384
|
}
|
|
385
385
|
const prompt = buildClaudeEventPrompt(rawEvent, summarizeContext(contextSnap));
|
|
386
|
-
const mcpServers = { ziggs:
|
|
386
|
+
const mcpServers = { ziggs: sdkMcpServer };
|
|
387
387
|
assertHostedAgentZiggsTools(mcpServers);
|
|
388
388
|
const queryOptions = claudeQueryOptions({
|
|
389
389
|
systemPrompt,
|
|
@@ -394,7 +394,7 @@ export class ClaudeSdkAgent {
|
|
|
394
394
|
stderr: (data) => {
|
|
395
395
|
const line = data.trim();
|
|
396
396
|
if (line)
|
|
397
|
-
runtimeLog.warn('
|
|
397
|
+
runtimeLog.warn('ClaudeBrain', `stderr session=${identity.sessionId}: ${line}`);
|
|
398
398
|
},
|
|
399
399
|
});
|
|
400
400
|
let metrics = { llmCalls: 0, tokens: { total: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 } };
|
|
@@ -429,7 +429,7 @@ export class ClaudeSdkAgent {
|
|
|
429
429
|
try {
|
|
430
430
|
for await (const message of query) {
|
|
431
431
|
if (signal?.aborted) {
|
|
432
|
-
runtimeLog.warn('
|
|
432
|
+
runtimeLog.warn('ClaudeBrain', `host watchdog aborted wake session=${identity.sessionId} agent=${this.agentId} — stopping query`);
|
|
433
433
|
break;
|
|
434
434
|
}
|
|
435
435
|
if ('session_id' in message && typeof message.session_id === 'string') {
|
|
@@ -455,14 +455,14 @@ export class ClaudeSdkAgent {
|
|
|
455
455
|
// reorder key assignment against other writes of this wake.
|
|
456
456
|
const idempotencyKey = writeKeys.artifactKey({ kind: 'thought', text });
|
|
457
457
|
void eff({ kind: 'record-event', sessionId: identity.sessionId, entry: { kind: 'thought', text }, idempotencyKey })
|
|
458
|
-
.catch((err) => runtimeLog.warn('
|
|
458
|
+
.catch((err) => runtimeLog.warn('ClaudeBrain', `record thought failed session=${identity.sessionId}: ${err.message}`));
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
const m = extractMetrics(message);
|
|
462
462
|
if (m)
|
|
463
463
|
metrics = m;
|
|
464
464
|
if (message.type === 'result' && message.subtype !== 'success') {
|
|
465
|
-
runtimeLog.warn('
|
|
465
|
+
runtimeLog.warn('ClaudeBrain', `query ended without success subtype=${message.subtype} session=${identity.sessionId}`);
|
|
466
466
|
}
|
|
467
467
|
// Runaway guard: abort before an unbounded loop drains the
|
|
468
468
|
// key. Breaking the for-await calls the generator's return(),
|
|
@@ -480,7 +480,7 @@ export class ClaudeSdkAgent {
|
|
|
480
480
|
}
|
|
481
481
|
if (spentInput > MAX_TASK_INPUT_TOKENS) {
|
|
482
482
|
abortedBy = 'input-budget';
|
|
483
|
-
runtimeLog.warn('
|
|
483
|
+
runtimeLog.warn('ClaudeBrain', `input-token budget exceeded (~${Math.round(spentInput)} cost-weighted > ${MAX_TASK_INPUT_TOKENS}) session=${identity.sessionId} agent=${this.agentId} — aborting query to cap spend`);
|
|
484
484
|
break;
|
|
485
485
|
}
|
|
486
486
|
}
|
|
@@ -531,9 +531,9 @@ export class ClaudeSdkAgent {
|
|
|
531
531
|
status: 'failed',
|
|
532
532
|
},
|
|
533
533
|
},
|
|
534
|
-
}).catch((err) => runtimeLog.warn('
|
|
534
|
+
}).catch((err) => runtimeLog.warn('ClaudeBrain', `could not fail task ${taskId} after a cut-short wake ` +
|
|
535
535
|
`session=${identity.sessionId}: ${err.message}`));
|
|
536
|
-
runtimeLog.warn('
|
|
536
|
+
runtimeLog.warn('ClaudeBrain', `failed task ${taskId} because the wake was cut short session=${identity.sessionId} agent=${this.agentId}`);
|
|
537
537
|
};
|
|
538
538
|
const noteWakeCutShort = async (reason) => {
|
|
539
539
|
const text = `Wake stopped early: ${reason}. Work in progress was NOT filed — no ` +
|
|
@@ -543,7 +543,7 @@ export class ClaudeSdkAgent {
|
|
|
543
543
|
sessionId: identity.sessionId,
|
|
544
544
|
entry: { kind: 'thought', text },
|
|
545
545
|
idempotencyKey: writeKeys.artifactKey({ kind: 'thought', text }),
|
|
546
|
-
}).catch((err) => runtimeLog.warn('
|
|
546
|
+
}).catch((err) => runtimeLog.warn('ClaudeBrain', `record abort note failed session=${identity.sessionId}: ${err.message}`));
|
|
547
547
|
};
|
|
548
548
|
try {
|
|
549
549
|
await consumeQuery(resumeId);
|
|
@@ -573,10 +573,10 @@ export class ClaudeSdkAgent {
|
|
|
573
573
|
// attempt — a mid-wake failure must keep propagating, since its partial
|
|
574
574
|
// writes are already out.
|
|
575
575
|
if (!isMissingSessionError(err) || !resumeId) {
|
|
576
|
-
runtimeLog.warn('
|
|
576
|
+
runtimeLog.warn('ClaudeBrain', `query failed session=${identity.sessionId}: ${err.message}`);
|
|
577
577
|
throw err;
|
|
578
578
|
}
|
|
579
|
-
runtimeLog.info('
|
|
579
|
+
runtimeLog.info('ClaudeBrain', `resumed Claude session ${resumeId} no longer exists (container replaced?) ` +
|
|
580
580
|
`session=${identity.sessionId} agent=${this.agentId} — retrying once on a fresh session`);
|
|
581
581
|
nextCtx.claudeSessionId = undefined;
|
|
582
582
|
sessionId = undefined;
|
|
@@ -590,7 +590,7 @@ export class ClaudeSdkAgent {
|
|
|
590
590
|
await consumeQuery(undefined);
|
|
591
591
|
}
|
|
592
592
|
catch (retryErr) {
|
|
593
|
-
runtimeLog.warn('
|
|
593
|
+
runtimeLog.warn('ClaudeBrain', `query failed session=${identity.sessionId}: ${retryErr.message}`);
|
|
594
594
|
throw retryErr;
|
|
595
595
|
}
|
|
596
596
|
}
|
|
@@ -612,7 +612,7 @@ export class ClaudeSdkAgent {
|
|
|
612
612
|
// re-reads the world from scratch and runs out of budget at the same step,
|
|
613
613
|
// and a standing job cannot advance.
|
|
614
614
|
if (nextCtx.claudeSessionId && peakTurnInput > MAX_SESSION_RESUME_INPUT_TOKENS) {
|
|
615
|
-
runtimeLog.info('
|
|
615
|
+
runtimeLog.info('ClaudeBrain', `resetting Claude session (transcript ~${peakTurnInput} > ${MAX_SESSION_RESUME_INPUT_TOKENS}) ` +
|
|
616
616
|
`session=${identity.sessionId} agent=${this.agentId} — next wake starts fresh`);
|
|
617
617
|
nextCtx.claudeSessionId = undefined;
|
|
618
618
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { Brain } from './index.js';
|
|
1
2
|
import type { Ctx, Identity, Workflow, EffectHandler } from '../types.js';
|
|
2
3
|
import { type TransitionEvent } from '../runtime/AgentMachine.js';
|
|
3
4
|
import { PromptBuilder } from '../runtime/PromptBuilder.js';
|
|
4
5
|
import type { MemoryStore } from '../memory/MemoryStore.js';
|
|
5
6
|
import type { ToolManager } from '../tools/ToolManager.js';
|
|
6
|
-
export interface
|
|
7
|
+
export interface ZiggsBrainOptions {
|
|
7
8
|
workflow: Workflow;
|
|
8
9
|
toolManager: ToolManager;
|
|
9
10
|
promptBuilder?: PromptBuilder;
|
|
@@ -11,26 +12,27 @@ export interface AgentOptions {
|
|
|
11
12
|
export interface TickInput {
|
|
12
13
|
/** Wire event (already normalized: a `message` or `task_result`-style envelope). */
|
|
13
14
|
event: unknown;
|
|
14
|
-
/**
|
|
15
|
+
/** Agent-provided per-session state. Omit on the first tick — a fresh ctx is constructed. */
|
|
15
16
|
ctx?: Ctx;
|
|
16
|
-
/**
|
|
17
|
+
/** Agent-provided workflow state name. Omit on the first tick — defaults to `workflow.initial`. */
|
|
17
18
|
state?: string;
|
|
18
|
-
/** Who am I and what session am I running in. The
|
|
19
|
+
/** Who am I and what session am I running in. The ZiggsBrain carries this through Ctx. */
|
|
19
20
|
identity: Identity;
|
|
20
21
|
/** The single side channel for I/O — LLM, context, tools, send, record. */
|
|
21
22
|
eff: EffectHandler;
|
|
22
23
|
/**
|
|
23
24
|
* Agent-private long-term memory. Operator-provided (default
|
|
24
|
-
* `InMemoryStore` when omitted by `
|
|
25
|
-
*
|
|
25
|
+
* `InMemoryStore` when omitted by `Agent`). The agent already scopes
|
|
26
|
+
* this to the hire the wake is inside; key shape inside that section is
|
|
27
|
+
* the agent's choice — `counterparty:<id>`, `agreement:<id>:notes`, etc.
|
|
26
28
|
*/
|
|
27
29
|
memory?: MemoryStore;
|
|
28
30
|
/** Optional tap for FSM transition events. */
|
|
29
31
|
transitionTap?: (event: TransitionEvent) => void;
|
|
30
32
|
/**
|
|
31
|
-
* Aborted by the
|
|
32
|
-
* external loop (the Claude
|
|
33
|
-
* abort — the
|
|
33
|
+
* Aborted by the agent when the tick watchdog fires. A brain driving an
|
|
34
|
+
* external loop (the Claude ZiggsBrain SDK query) MUST stop consuming it on
|
|
35
|
+
* abort — the agent's Promise.race only abandons the tick promise, so
|
|
34
36
|
* without this the query keeps running as a zombie: burning tokens and
|
|
35
37
|
* racing its own redelivered retry (observed as duplicate kickoffs and a
|
|
36
38
|
* 363k-token wake, 2026-07-26).
|
|
@@ -38,13 +40,13 @@ export interface TickInput {
|
|
|
38
40
|
signal?: AbortSignal;
|
|
39
41
|
}
|
|
40
42
|
export interface TickOutput {
|
|
41
|
-
/** Updated ctx —
|
|
43
|
+
/** Updated ctx — Agent persists this against the session. */
|
|
42
44
|
ctx: Ctx;
|
|
43
|
-
/** Workflow state name after the tick —
|
|
45
|
+
/** Workflow state name after the tick — Agent persists this against the session. */
|
|
44
46
|
state: string;
|
|
45
47
|
/** Whether the machine settled at a parked state. */
|
|
46
48
|
parked: boolean;
|
|
47
|
-
/** True when a state-timeout outcome matched no transition this tick — the
|
|
49
|
+
/** True when a state-timeout outcome matched no transition this tick — the agent must NOT re-arm. */
|
|
48
50
|
stateTimeoutUnhandled: boolean;
|
|
49
51
|
/**
|
|
50
52
|
* Accumulated LLM call count and token usage for this tick.
|
|
@@ -64,10 +66,10 @@ export interface TickOutput {
|
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
/**
|
|
67
|
-
*
|
|
68
|
-
* no services. The
|
|
69
|
-
* on each `tick`.
|
|
70
|
-
* `
|
|
69
|
+
* Workflow brain: workflow definition + cognition machinery. No I/O, no transport,
|
|
70
|
+
* no services. The brain's only side channel is the `eff` handler it receives
|
|
71
|
+
* on each `tick`. Body state (sessions, lanes, queue, outbox) lives in
|
|
72
|
+
* `Agent` — not here.
|
|
71
73
|
*
|
|
72
74
|
* The contract:
|
|
73
75
|
*
|
|
@@ -75,13 +77,14 @@ export interface TickOutput {
|
|
|
75
77
|
* `{ ctx, state, parked }`
|
|
76
78
|
*
|
|
77
79
|
* Reads top-to-bottom like a synchronous function. Every `await eff(...)`
|
|
78
|
-
* inside the cognition core is a yield to the
|
|
79
|
-
* impure operation and resumes the
|
|
80
|
+
* inside the cognition core is a yield to the body, which performs the
|
|
81
|
+
* impure operation and resumes the ZiggsBrain with the result.
|
|
80
82
|
*/
|
|
81
|
-
export declare class
|
|
83
|
+
export declare class ZiggsBrain implements Brain {
|
|
84
|
+
readonly kind: "ziggs";
|
|
82
85
|
workflow: Workflow;
|
|
83
86
|
toolManager: ToolManager;
|
|
84
87
|
promptBuilder: PromptBuilder;
|
|
85
|
-
constructor({ workflow, toolManager, promptBuilder }:
|
|
88
|
+
constructor({ workflow, toolManager, promptBuilder }: ZiggsBrainOptions);
|
|
86
89
|
tick({ event, ctx, state, identity, eff, memory, transitionTap }: TickInput): Promise<TickOutput>;
|
|
87
90
|
}
|
|
@@ -3,10 +3,10 @@ import { createDeliveryWriteKeys } from '../runtime/deliveryWriteKeys.js';
|
|
|
3
3
|
import { runTurn } from '../runtime/runTurn.js';
|
|
4
4
|
import { PromptBuilder } from '../runtime/PromptBuilder.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* no services. The
|
|
8
|
-
* on each `tick`.
|
|
9
|
-
* `
|
|
6
|
+
* Workflow brain: workflow definition + cognition machinery. No I/O, no transport,
|
|
7
|
+
* no services. The brain's only side channel is the `eff` handler it receives
|
|
8
|
+
* on each `tick`. Body state (sessions, lanes, queue, outbox) lives in
|
|
9
|
+
* `Agent` — not here.
|
|
10
10
|
*
|
|
11
11
|
* The contract:
|
|
12
12
|
*
|
|
@@ -14,27 +14,28 @@ import { PromptBuilder } from '../runtime/PromptBuilder.js';
|
|
|
14
14
|
* `{ ctx, state, parked }`
|
|
15
15
|
*
|
|
16
16
|
* Reads top-to-bottom like a synchronous function. Every `await eff(...)`
|
|
17
|
-
* inside the cognition core is a yield to the
|
|
18
|
-
* impure operation and resumes the
|
|
17
|
+
* inside the cognition core is a yield to the body, which performs the
|
|
18
|
+
* impure operation and resumes the ZiggsBrain with the result.
|
|
19
19
|
*/
|
|
20
|
-
export class
|
|
20
|
+
export class ZiggsBrain {
|
|
21
|
+
kind = 'ziggs';
|
|
21
22
|
workflow;
|
|
22
23
|
toolManager;
|
|
23
24
|
promptBuilder;
|
|
24
25
|
constructor({ workflow, toolManager, promptBuilder }) {
|
|
25
26
|
if (!workflow?.states)
|
|
26
|
-
throw new Error('
|
|
27
|
+
throw new Error('ZiggsBrain: workflow with states is required');
|
|
27
28
|
if (!toolManager)
|
|
28
|
-
throw new Error('
|
|
29
|
+
throw new Error('ZiggsBrain: toolManager is required');
|
|
29
30
|
this.workflow = workflow;
|
|
30
31
|
this.toolManager = toolManager;
|
|
31
32
|
this.promptBuilder = promptBuilder || new PromptBuilder();
|
|
32
33
|
}
|
|
33
34
|
async tick({ event, ctx, state, identity, eff, memory, transitionTap }) {
|
|
34
35
|
if (!identity?.sessionId)
|
|
35
|
-
throw new Error('
|
|
36
|
+
throw new Error('ZiggsBrain.tick: identity.sessionId is required');
|
|
36
37
|
if (!eff)
|
|
37
|
-
throw new Error('
|
|
38
|
+
throw new Error('ZiggsBrain.tick: eff is required');
|
|
38
39
|
const machine = new AgentMachine({
|
|
39
40
|
workflow: this.workflow,
|
|
40
41
|
executionCore: runTurn,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared Claude-Agent-SDK cognition pieces.
|
|
3
3
|
*
|
|
4
4
|
* Everything a Claude-brain agent needs to turn a Ziggs wake into a `query()`
|
|
5
|
-
* call, EXCEPT the loop itself. `
|
|
5
|
+
* call, EXCEPT the loop itself. `ClaudeBrain` owns the loop (drive `query()`,
|
|
6
6
|
* collect metrics); this module owns the periphery so it lives in one place and
|
|
7
7
|
* is unit-testable without spinning up the SDK.
|
|
8
8
|
*
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared Claude-Agent-SDK cognition pieces.
|
|
3
3
|
*
|
|
4
4
|
* Everything a Claude-brain agent needs to turn a Ziggs wake into a `query()`
|
|
5
|
-
* call, EXCEPT the loop itself. `
|
|
5
|
+
* call, EXCEPT the loop itself. `ClaudeBrain` owns the loop (drive `query()`,
|
|
6
6
|
* collect metrics); this module owns the periphery so it lives in one place and
|
|
7
7
|
* is unit-testable without spinning up the SDK.
|
|
8
8
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Operating contract for Claude Agent SDK agents on Ziggs (
|
|
2
|
+
* Operating contract for Claude Agent SDK agents on Ziggs (Agent path).
|
|
3
3
|
*
|
|
4
4
|
* The host reads the agent's inbox for it — a per-agent long-poll loop that
|
|
5
5
|
* pulls each event concerning the agent, hands it to the brain, and owns the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Operating contract for Claude Agent SDK agents on Ziggs (
|
|
2
|
+
* Operating contract for Claude Agent SDK agents on Ziggs (Agent path).
|
|
3
3
|
*
|
|
4
4
|
* The host reads the agent's inbox for it — a per-agent long-poll loop that
|
|
5
5
|
* pulls each event concerning the agent, hands it to the brain, and owns the
|