@ziggs-ai/ziggs-mcp 0.33.1 → 0.34.1

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.
@@ -0,0 +1,5 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { type Creds } from '@ziggs-ai/api-client';
3
+ export declare function installConnectionNotices(server: McpServer, creds: Creds, version: string): void;
4
+ export declare function readConnectionNotices(server: McpServer, fresh?: boolean): Promise<string[]>;
5
+ export declare function appendConnectionNotices(server: McpServer, name: string, value: unknown, providedNotices?: string[]): Promise<unknown>;
@@ -0,0 +1,59 @@
1
+ import { workspaceRouter } from './workspaceRouting.js';
2
+ import { decodeOperatorKeyClaims } from './operatorKey.js';
3
+ import { getBackendUrl } from '@ziggs-ai/api-client';
4
+ const readers = new WeakMap();
5
+ /** A notice changes when the person reconnects or updates; a minute is soon enough. */
6
+ const NOTICE_TTL_MS = 60_000;
7
+ /** The session-start reads always ask the server, so a reconnect shows on the next one. */
8
+ const SESSION_READS = new Set(['ziggs_auth_status', 'ziggs_inbox']);
9
+ export function installConnectionNotices(server, creds, version) {
10
+ if (!decodeOperatorKeyClaims(creds.operatorKey)?.keyId)
11
+ return;
12
+ let cached = null;
13
+ readers.set(server, async (fresh) => {
14
+ if (!fresh && cached && Date.now() - cached.readAt < NOTICE_TTL_MS)
15
+ return cached.notices;
16
+ try {
17
+ const response = await fetch(`${getBackendUrl().replace(/\/$/, '')}/oauth/connection-notices`, {
18
+ headers: { Authorization: `Bearer ${creds.operatorKey}`, 'X-Ziggs-Mcp-Version': version },
19
+ signal: AbortSignal.timeout(5000),
20
+ });
21
+ if (!response.ok)
22
+ return cached?.notices ?? [];
23
+ const body = await response.json();
24
+ if (body.assistantConnection)
25
+ workspaceRouter(server)?.activate();
26
+ const notices = Array.isArray(body.notices) ? body.notices.filter((n) => typeof n === 'string') : [];
27
+ cached = { readAt: Date.now(), notices };
28
+ return notices;
29
+ }
30
+ catch {
31
+ return cached?.notices ?? [];
32
+ } // A notice lookup must never repeat or suppress a tool's write.
33
+ });
34
+ }
35
+ export async function readConnectionNotices(server, fresh = false) {
36
+ return await readers.get(server)?.(fresh) ?? [];
37
+ }
38
+ export async function appendConnectionNotices(server, name, value, providedNotices) {
39
+ const notices = providedNotices ?? await readConnectionNotices(server, SESSION_READS.has(name));
40
+ if (!notices?.length || !value || typeof value !== 'object')
41
+ return value;
42
+ const result = value;
43
+ const content = (result.content ?? []).map(item => ({ ...item }));
44
+ if (name === 'ziggs_auth_status' || name === 'ziggs_inbox') {
45
+ const text = content.find(item => item.type === 'text' && item.text);
46
+ if (text?.text)
47
+ try {
48
+ const payload = JSON.parse(text.text);
49
+ if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
50
+ text.text = JSON.stringify({ ...payload, session: { ...payload.session, connectionNotices: notices } });
51
+ }
52
+ }
53
+ catch { /* Non-JSON content still gets the plain sentence below. */ }
54
+ }
55
+ for (const notice of notices)
56
+ if (!content.some(item => item.text === notice))
57
+ content.push({ type: 'text', text: notice });
58
+ return { ...value, content };
59
+ }
@@ -41,7 +41,7 @@ export declare const PROTOCOL: {
41
41
  * is not work until deps release; withdraw is cancel on the root. A live
42
42
  * hire does not take a second hire or an in-place amend.
43
43
  */
44
- readonly workOrder: "A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.";
44
+ readonly workOrder: "A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. workBrief.work.state listing is that conversation under your listing: answer in this room with the reply action, never another conversation, and when they want something the listing does not cover use the propose_terms action (ziggs_agreement_bid to them, in this room), and describe only the terms of the proposal that went through. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.";
45
45
  /** posted-first: how ANY engagement starts. */
46
46
  readonly engage: "Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the authorized parties approve the final terms.";
47
47
  /**
@@ -41,7 +41,7 @@ export const PROTOCOL = {
41
41
  * is not work until deps release; withdraw is cancel on the root. A live
42
42
  * hire does not take a second hire or an in-place amend.
43
43
  */
44
- workOrder: 'A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger\'s brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.',
44
+ workOrder: 'A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger\'s brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. workBrief.work.state listing is that conversation under your listing: answer in this room with the reply action, never another conversation, and when they want something the listing does not cover use the propose_terms action (ziggs_agreement_bid to them, in this room), and describe only the terms of the proposal that went through. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.',
45
45
  /** posted-first: how ANY engagement starts. */
46
46
  engage: 'Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the authorized parties approve the final terms.',
47
47
  /**
package/dist/server.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { installConnectionNotices } from './connectionNotices.js';
1
2
  import { installWorkspaceRouting } from './workspaceRouting.js';
2
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -24,6 +25,7 @@ export function createZiggsMcpServer(creds, cfg) {
24
25
  // since both build the server through this factory.
25
26
  instructions: renderInstructions(),
26
27
  });
28
+ installConnectionNotices(server, creds, version);
27
29
  const routedCreds = installWorkspaceRouting(server, creds);
28
30
  registerZiggsTools(server, routedCreds, cfg);
29
31
  // Decide what a caller loads natively and what it reaches through
@@ -1,3 +1,4 @@
1
+ import { appendConnectionNotices, readConnectionNotices } from './connectionNotices.js';
1
2
  import { workspaceRouter } from './workspaceRouting.js';
2
3
  import { z } from 'zod';
3
4
  import { recordCatalogEntry } from './toolCatalog.js';
@@ -51,6 +52,11 @@ export function registerStrictTool(server, name, description, shape, annotations
51
52
  shape = { ...shape, orgId: shape.orgId?.optional() ?? z.string().optional().describe('Workspace named by your human. Required for new actions when more than one workspace fits.') };
52
53
  cb = router.wrap(name, annotations.readOnlyHint === true, cb, nativeOrgId);
53
54
  }
55
+ const routed = cb;
56
+ cb = (async (...args) => {
57
+ const notices = await readConnectionNotices(server, name === 'ziggs_auth_status' || name === 'ziggs_inbox');
58
+ return appendConnectionNotices(server, name, await routed(...args), notices);
59
+ });
54
60
  const params = strictParams(name, shape);
55
61
  const handle = server.registerTool(name, {
56
62
  title: annotations.title,
@@ -1,8 +1,8 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { type AssistantWorkspace, type Creds } from '@ziggs-ai/api-client';
3
3
  /**
4
- * One connection, an assistant per workspace, and every tool call acting as
5
- * exactly one of them.
4
+ * One assistant identity across workspace hires, with each tool call choosing
5
+ * the workspace that owns its work.
6
6
  *
7
7
  * The connection's roster (`GET /oauth/workspaces`) is read once a minute and
8
8
  * on every session-start read, so a workspace joined mid-session shows up
@@ -40,12 +40,14 @@ export declare function workspaceRouter(server: McpServer): WorkspaceRouter | un
40
40
  */
41
41
  export declare function installWorkspaceRouting(server: McpServer, creds: Creds): Creds;
42
42
  export declare class WorkspaceRouter {
43
+ private enabled;
43
44
  private readonly active;
44
45
  /** The base credentials with `agentId` answered by the call in progress. */
45
46
  readonly creds: Creds;
46
47
  private readonly client;
47
48
  private roster;
48
- constructor(base: Creds);
49
+ constructor(base: Creds, enabled?: boolean);
50
+ activate(): void;
49
51
  acceptsWorkspace(name: string): boolean;
50
52
  /** The connection's workspaces, re-read after {@link WORKSPACES_TTL_MS} or on demand. */
51
53
  workspaces(fresh?: boolean): Promise<AssistantWorkspace[]>;
@@ -53,6 +55,7 @@ export declare class WorkspaceRouter {
53
55
  /** The existing thing a call is about, from the argument that names it. */
54
56
  private target;
55
57
  wrap(name: string, readOnly: boolean, handler: Handler, nativeOrgId?: boolean): Handler;
58
+ private wrapConnected;
56
59
  /** The connection's own reads and acks: never routed, always live. */
57
60
  private connectionTool;
58
61
  /**
@@ -47,26 +47,30 @@ export function workspaceRouter(server) {
47
47
  * a fleet key with ZIGGS_AGENT_ID) names its agent itself and is left alone.
48
48
  */
49
49
  export function installWorkspaceRouting(server, creds) {
50
- if (!decodeOperatorKeyClaims(creds.operatorKey)?.connectionClientId)
50
+ const claims = decodeOperatorKeyClaims(creds.operatorKey);
51
+ if (!claims?.connectionClientId && !claims?.boundAgentId)
51
52
  return creds;
52
- const router = new WorkspaceRouter(creds);
53
+ const router = new WorkspaceRouter(creds, !!claims.connectionClientId);
53
54
  routers.set(server, router);
54
55
  return router.creds;
55
56
  }
56
57
  export class WorkspaceRouter {
58
+ enabled;
57
59
  active = new AsyncLocalStorage();
58
60
  /** The base credentials with `agentId` answered by the call in progress. */
59
61
  creds;
60
62
  client;
61
63
  roster = null;
62
- constructor(base) {
64
+ constructor(base, enabled = true) {
65
+ this.enabled = enabled;
63
66
  this.client = new AssistantConnectionClient(base);
64
67
  this.creds = new Proxy(base, {
65
- get: (target, key, receiver) => key === 'agentId'
68
+ get: (target, key, receiver) => key === 'agentId' && this.enabled
66
69
  ? (this.active.getStore()?.agentId ?? '')
67
70
  : Reflect.get(target, key, receiver),
68
71
  });
69
72
  }
73
+ activate() { this.enabled = true; }
70
74
  acceptsWorkspace(name) {
71
75
  return !GLOBAL.has(name) && !CONNECTION.has(name);
72
76
  }
@@ -103,6 +107,10 @@ export class WorkspaceRouter {
103
107
  return null;
104
108
  }
105
109
  wrap(name, readOnly, handler, nativeOrgId = false) {
110
+ const routed = this.wrapConnected(name, readOnly, handler, nativeOrgId);
111
+ return (args, ...extra) => this.enabled ? routed(args, ...extra) : handler(args, ...extra);
112
+ }
113
+ wrapConnected(name, readOnly, handler, nativeOrgId = false) {
106
114
  if (GLOBAL.has(name))
107
115
  return handler;
108
116
  // The routing argument stays out of business payloads; a tool with its own
@@ -204,7 +212,7 @@ export class WorkspaceRouter {
204
212
  * already knows, as a fan-out does.
205
213
  */
206
214
  async run(workspace, args, extra, handler, label = true) {
207
- return this.active.run(workspace, async () => {
215
+ return withAssistantRequest({ orgId: workspace.orgId }, () => this.active.run(workspace, async () => {
208
216
  const response = await handler(args, ...extra);
209
217
  if (!label || response.isError)
210
218
  return response;
@@ -225,7 +233,7 @@ export class WorkspaceRouter {
225
233
  };
226
234
  }),
227
235
  };
228
- });
236
+ }));
229
237
  }
230
238
  }
231
239
  function parseJson(text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.33.1",
3
+ "version": "0.34.1",
4
4
  "description": "MCP server for Claude Code, Cursor, and other MCP hosts \u2014 act as your Ziggs delegate agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@modelcontextprotocol/sdk": "^1.29.0",
42
- "@ziggs-ai/api-client": "0.33.1",
42
+ "@ziggs-ai/api-client": "0.34.1",
43
43
  "dotenv": "^16.6.1",
44
44
  "zod": "^3.24.2",
45
45
  "zod-to-json-schema": "^3.25.1"
@@ -7,7 +7,7 @@ You are a delegate agent on a Ziggs team. Read workBrief for the current work an
7
7
  - Flow: inbox → read → act → ack. Use the workspace your human named. If they didn't, ask them. Never guess.
8
8
  - Reading never advances the watermark; once you have handled what an envelope carried, hand it back with ziggs_inbox_ack — pass its `ackTo` VERBATIM (it is opaque — never construct or edit one) together with `handledResourceIds` for every delivery ASSIGNED to you (assigneeId is any of your connection’s agentIds; requests too) in that window. Rows without your stamp are context another window handles — read them, never ack them as yours. Never rewind an ack to an older value.
9
9
  - Honor task.control when opening work or handling a task change. For pause or stop, stop starting actions for that task, settle in-flight work, then call ziggs_task_acknowledge_control with its current requestId and supported=true. If you cannot honor it, report supported=false. Never attest that another runtime stopped. Continue unrelated conversation and tasks. Resume allows this task to run again. Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).
10
- - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
10
+ - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. workBrief.work.state listing is that conversation under your listing: answer in this room with the reply action, never another conversation, and when they want something the listing does not cover use the propose_terms action (ziggs_agreement_bid to them, in this room), and describe only the terms of the proposal that went through. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
11
11
  - Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the authorized parties approve the final terms.
12
12
  - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
13
13
  - Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion's reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.
@@ -26,7 +26,7 @@ _You are a delegate agent on a Ziggs team. Read workBrief for the current work a
26
26
  - Flow: inbox → read → act → ack. Use the workspace your human named. If they didn't, ask them. Never guess.
27
27
  - Reading never advances the watermark; once you have handled what an envelope carried, hand it back with ziggs_inbox_ack — pass its `ackTo` VERBATIM (it is opaque — never construct or edit one) together with `handledResourceIds` for every delivery ASSIGNED to you (assigneeId is any of your connection’s agentIds; requests too) in that window. Rows without your stamp are context another window handles — read them, never ack them as yours. Never rewind an ack to an older value.
28
28
  - Honor task.control when opening work or handling a task change. For pause or stop, stop starting actions for that task, settle in-flight work, then call ziggs_task_acknowledge_control with its current requestId and supported=true. If you cannot honor it, report supported=false. Never attest that another runtime stopped. Continue unrelated conversation and tasks. Resume allows this task to run again. Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).
29
- - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
29
+ - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. workBrief.work.state listing is that conversation under your listing: answer in this room with the reply action, never another conversation, and when they want something the listing does not cover use the propose_terms action (ziggs_agreement_bid to them, in this room), and describe only the terms of the proposal that went through. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
30
30
  - Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the authorized parties approve the final terms.
31
31
  - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
32
32
  - Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion's reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.
@@ -9,7 +9,7 @@ _You are a delegate agent on a Ziggs team. Read workBrief for the current work a
9
9
  - Flow: inbox → read → act → ack. Use the workspace your human named. If they didn't, ask them. Never guess.
10
10
  - Reading never advances the watermark; once you have handled what an envelope carried, hand it back with ziggs_inbox_ack — pass its `ackTo` VERBATIM (it is opaque — never construct or edit one) together with `handledResourceIds` for every delivery ASSIGNED to you (assigneeId is any of your connection’s agentIds; requests too) in that window. Rows without your stamp are context another window handles — read them, never ack them as yours. Never rewind an ack to an older value.
11
11
  - Honor task.control when opening work or handling a task change. For pause or stop, stop starting actions for that task, settle in-flight work, then call ziggs_task_acknowledge_control with its current requestId and supported=true. If you cannot honor it, report supported=false. Never attest that another runtime stopped. Continue unrelated conversation and tasks. Resume allows this task to run again. Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).
12
- - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
12
+ - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. workBrief.work.state listing is that conversation under your listing: answer in this room with the reply action, never another conversation, and when they want something the listing does not cover use the propose_terms action (ziggs_agreement_bid to them, in this room), and describe only the terms of the proposal that went through. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
13
13
  - Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the authorized parties approve the final terms.
14
14
  - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
15
15
  - Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion's reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.
@@ -9,7 +9,7 @@ _You are a delegate agent on a Ziggs team. Read workBrief for the current work a
9
9
  - Flow: inbox → read → act → ack. Use the workspace your human named. If they didn't, ask them. Never guess.
10
10
  - Reading never advances the watermark; once you have handled what an envelope carried, hand it back with ziggs_inbox_ack — pass its `ackTo` VERBATIM (it is opaque — never construct or edit one) together with `handledResourceIds` for every delivery ASSIGNED to you (assigneeId is any of your connection’s agentIds; requests too) in that window. Rows without your stamp are context another window handles — read them, never ack them as yours. Never rewind an ack to an older value.
11
11
  - Honor task.control when opening work or handling a task change. For pause or stop, stop starting actions for that task, settle in-flight work, then call ziggs_task_acknowledge_control with its current requestId and supported=true. If you cannot honor it, report supported=false. Never attest that another runtime stopped. Continue unrelated conversation and tasks. Resume allows this task to run again. Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).
12
- - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
12
+ - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief is not a work order: they hire you through your store listing, or through an agreement you draft when you have no listing, and until then it is conversation. workBrief.work.state listing is that conversation under your listing: answer in this room with the reply action, never another conversation, and when they want something the listing does not cover use the propose_terms action (ziggs_agreement_bid to them, in this room), and describe only the terms of the proposal that went through. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
13
13
  - Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the authorized parties approve the final terms.
14
14
  - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
15
15
  - Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion's reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.