@ziggs-ai/ziggs-mcp 0.9.12 → 0.10.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/README.md CHANGED
@@ -43,7 +43,7 @@ Skill only (no plugin): `skills/ziggs/SKILL.md` ships in the package for org pro
43
43
  |------|------|
44
44
  | List chats / discover reach | `ziggs_chat_list` or `ziggs_grant_list` |
45
45
  | Send message | `ziggs_chat_send` |
46
- | Propose + respond | `ziggs_agreement_propose`, `ziggs_agreement_respond` |
46
+ | Propose + respond | `ziggs_agreement_commission`, `ziggs_agreement_respond` |
47
47
 
48
48
  ---
49
49
 
@@ -157,7 +157,7 @@ Startup validates the key shape, expiry (JWT `exp`), and agent resolution — er
157
157
  | `ziggs_chat_list` | `GET /chats/mine` |
158
158
  | `ziggs_chat_open` | `POST /chats` |
159
159
  | `ziggs_chat_send` | `POST /chats/:id/messages` |
160
- | `ziggs_agreement_propose` | `POST /agreements/proposals` (direct), marketplace publish (broadcast: quest / standing offer), or `POST /agreements` (link) — one propose grammar |
160
+ | `ziggs_agreement_commission` | `POST /agreements/proposals` (direct), marketplace publish (broadcast: quest / standing offer), or `POST /agreements` (link) — one propose grammar |
161
161
  | `ziggs_agreement_respond` | `PUT /agreements/:id/approvals/:partyId` (owner principal; approves direct hire, service, and `link` proposals) |
162
162
  | `ziggs_agreement_claim` | `POST /agreements/:id/claim` — claim any open broadcast (quest / offer / hand-off / link invite) |
163
163
  | `ziggs_agreement_subcontract` | `POST /agreements` delegation under a parent agreement |
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
2
+ import { readOnly, write, destructive } from './toolAnnotations.js';
3
3
  import { registerStrictTool } from './strictParams.js';
4
4
  import { toolError } from './toolError.js';
5
5
  /** One JSON text-content result shape for every MCP tool (was copied 3×). */
@@ -55,10 +55,10 @@ export function toZodShape(params) {
55
55
  }
56
56
  export function registerCapability(server, cap, creds, opts = {}) {
57
57
  const annotations = cap.annotation === 'read-only'
58
- ? READ_ONLY
58
+ ? readOnly(cap.title)
59
59
  : cap.annotation === 'destructive'
60
- ? DESTRUCTIVE
61
- : WRITE;
60
+ ? destructive(cap.title)
61
+ : write(cap.title);
62
62
  registerStrictTool(server, cap.names.mcp, opts.description ?? cap.descriptions.mcp, toZodShape(cap.params), annotations, async (args) => {
63
63
  try {
64
64
  const env = { creds, webUrl: opts.webUrl, surface: 'mcp' };
@@ -23,7 +23,7 @@ export declare const PROTOCOL: {
23
23
  /** Tasks are the unit of work. */
24
24
  readonly task: "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 as plan steps with ziggs_task_replace_plan.";
25
25
  /** posted-first: how ANY engagement starts. */
26
- 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 quest (ziggs_agreement_propose with proposedTo \"everyone\"/\"org\") when nothing listed fits, and supply claims you; (4) PROPOSE directly only for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — 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.";
26
+ 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 quest (ziggs_agreement_quest) 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_commission 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.";
27
27
  /** The reporting rule — the heart of the batch. */
28
28
  readonly reporting: "Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.";
29
29
  /** Pull-only hosts have no push channel. */
@@ -23,7 +23,7 @@ export const PROTOCOL = {
23
23
  /** Tasks are the unit of work. */
24
24
  task: '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 as plan steps with ziggs_task_replace_plan.',
25
25
  /** posted-first: how ANY engagement starts. */
26
- 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 quest (ziggs_agreement_propose with proposedTo "everyone"/"org") when nothing listed fits, and supply claims you; (4) PROPOSE directly only for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — 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.',
26
+ 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 quest (ziggs_agreement_quest) 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_commission 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.',
27
27
  /** The reporting rule — the heart of the batch. */
28
28
  reporting: "Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.",
29
29
  /** Pull-only hosts have no push channel. */
@@ -1,6 +1,6 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
3
2
  import { z, type ZodRawShape, type ZodObject } from 'zod';
3
+ import type { TitledAnnotations } from './toolAnnotations.js';
4
4
  /**
5
5
  * registering a tool with a plain `ZodRawShape` lets the MCP SDK
6
6
  * wrap it in a default `z.object()`, which *strips* unknown keys instead of
@@ -20,5 +20,15 @@ export declare function strictParams<S extends ZodRawShape>(toolName: string, sh
20
20
  * strict. The positional `tool()` overloads only accept a raw shape (a built
21
21
  * ZodObject is parsed as annotations there), so the strict schema has to go
22
22
  * through `registerTool`'s config object.
23
+ *
24
+ * Annotations must carry a title: a titleless tool displays as its wire name
25
+ * (`ziggs_agreement_fulfill`), and the connector directory flags the surface
26
+ * for it. Taking `TitledAnnotations` rather than `ToolAnnotations` is what
27
+ * makes that a compile error instead of a review catch.
28
+ *
29
+ * The title goes out twice from the one source. `title` is where the current
30
+ * spec puts it; `annotations.title` is where clients written against the
31
+ * 2024-11-05 spec still look, and display precedence is title →
32
+ * annotations.title → name, so a client reading either lands on the same copy.
23
33
  */
24
- export declare function registerStrictTool<S extends ZodRawShape>(server: McpServer, name: string, description: string, shape: S, annotations: ToolAnnotations, cb: Parameters<typeof server.registerTool<never, StrictShape<S>>>[2]): void;
34
+ export declare function registerStrictTool<S extends ZodRawShape>(server: McpServer, name: string, description: string, shape: S, annotations: TitledAnnotations, cb: Parameters<typeof server.registerTool<never, StrictShape<S>>>[2]): void;
@@ -26,7 +26,22 @@ export function strictParams(toolName, shape) {
26
26
  * strict. The positional `tool()` overloads only accept a raw shape (a built
27
27
  * ZodObject is parsed as annotations there), so the strict schema has to go
28
28
  * through `registerTool`'s config object.
29
+ *
30
+ * Annotations must carry a title: a titleless tool displays as its wire name
31
+ * (`ziggs_agreement_fulfill`), and the connector directory flags the surface
32
+ * for it. Taking `TitledAnnotations` rather than `ToolAnnotations` is what
33
+ * makes that a compile error instead of a review catch.
34
+ *
35
+ * The title goes out twice from the one source. `title` is where the current
36
+ * spec puts it; `annotations.title` is where clients written against the
37
+ * 2024-11-05 spec still look, and display precedence is title →
38
+ * annotations.title → name, so a client reading either lands on the same copy.
29
39
  */
30
40
  export function registerStrictTool(server, name, description, shape, annotations, cb) {
31
- server.registerTool(name, { description, inputSchema: strictParams(name, shape), annotations }, cb);
41
+ server.registerTool(name, {
42
+ title: annotations.title,
43
+ description,
44
+ inputSchema: strictParams(name, shape),
45
+ annotations,
46
+ }, cb);
32
47
  }
@@ -1,7 +1,11 @@
1
1
  import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
2
+ /** Annotations with a title — the shape `registerStrictTool` demands. */
3
+ export type TitledAnnotations = ToolAnnotations & {
4
+ title: string;
5
+ };
2
6
  /** Reads state, never mutates. */
3
- export declare const READ_ONLY: ToolAnnotations;
7
+ export declare function readOnly(title: string): TitledAnnotations;
4
8
  /** Writes state, but additively/reversibly (create, send, grant). */
5
- export declare const WRITE: ToolAnnotations;
9
+ export declare function write(title: string): TitledAnnotations;
6
10
  /** Mutates state irreversibly (revoke). */
7
- export declare const DESTRUCTIVE: ToolAnnotations;
11
+ export declare function destructive(title: string): TitledAnnotations;
@@ -1,16 +1,12 @@
1
- // MCP annotation hints so connector UIs (Claude, Cursor, …) can bucket Ziggs
2
- // tools into "Read only" vs "Actions" instead of one undefined group.
3
- // `readOnlyHint` drives that split; `destructiveHint` flags the irreversible
4
- // ones so hosts can warn before running them.
5
1
  /** Reads state, never mutates. */
6
- export const READ_ONLY = { readOnlyHint: true };
2
+ export function readOnly(title) {
3
+ return { title, readOnlyHint: true };
4
+ }
7
5
  /** Writes state, but additively/reversibly (create, send, grant). */
8
- export const WRITE = {
9
- readOnlyHint: false,
10
- destructiveHint: false,
11
- };
6
+ export function write(title) {
7
+ return { title, readOnlyHint: false, destructiveHint: false };
8
+ }
12
9
  /** Mutates state irreversibly (revoke). */
13
- export const DESTRUCTIVE = {
14
- readOnlyHint: false,
15
- destructiveHint: true,
16
- };
10
+ export function destructive(title) {
11
+ return { title, readOnlyHint: false, destructiveHint: true };
12
+ }
package/dist/toolError.js CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  const SCOPE_DENIED_HINT = 'You are not authorized for this scope. To get access: ask the counterparty ' +
9
9
  'to issue you a context grant (they run ziggs_context_issue_grant), or propose a ' +
10
- 'bilateral link first (ziggs_agreement_propose with engagementKind "link"). Check what you can already ' +
10
+ 'bilateral link first (ziggs_link_propose). Check what you can already ' +
11
11
  'reach with ziggs_grant_list / ziggs_context_snapshot.';
12
12
  // a human-authority denial is not "try again with more scope": no
13
13
  // retry by this caller can ever pass it, because the guard refuses on being an
package/dist/tools.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
- import { getAgreement, getMyAgreements, listMyChats, proposeUnified, delegateAgreement, provisionRelayWorkers, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateAgentId, AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION, GRANTS_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, marketplaceViewCapability, } from '@ziggs-ai/api-client';
3
+ import { getAgreement, getMyAgreements, listMyChats, delegateAgreement, provisionRelayWorkers, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateAgentId, GRANTS_CAPABILITIES, AGREEMENT_VERB_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, marketplaceViewCapability, } from '@ziggs-ai/api-client';
4
4
  import { decodeOperatorKeyClaims } from './operatorKey.js';
5
5
  import { registerTrustTools } from './trustTools.js';
6
6
  import { registerPaymentTools } from './paymentTools.js';
@@ -17,7 +17,7 @@ function buildRelayCoordinatorTaskBody(opts) {
17
17
  description: `${title}\nrelay:v1\n${JSON.stringify(opts.payload)}`,
18
18
  };
19
19
  }
20
- import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
20
+ import { readOnly, write, destructive } from './toolAnnotations.js';
21
21
  import { registerStrictTool } from './strictParams.js';
22
22
  import { toolError } from './toolError.js';
23
23
  import { registerCapability, registerCapabilities, textResult, } from './capabilityAdapter.js';
@@ -39,7 +39,7 @@ const ZIGGS_PENDING_DECISIONS_DESCRIPTION = 'Session start summary: approve/reje
39
39
  // conversation only; finished work goes to the task result. Reporting rule is
40
40
  // sourced from the shared const so it can't drift.
41
41
  const ZIGGS_SEND_MESSAGE_DESCRIPTION = 'Send a chat message as the acting agent (requires chat membership). ' +
42
- 'Cross-org first contact requires an ACTIVE link first (propose one with ziggs_agreement_propose engagementKind="link", or ziggs_link_create_invite when you lack the agent id; then approved/claimed); without it, messaging an agent outside your org fails with AGENT_NOT_PUBLISHED. ' +
42
+ 'Cross-org first contact requires an ACTIVE link first (propose one with ziggs_link_propose, or ziggs_link_create_invite when you lack the agent id; then approved/claimed); without it, messaging an agent outside your org fails with AGENT_NOT_PUBLISHED. ' +
43
43
  PROTOCOL.reporting;
44
44
  // (revised A4): always-on teaching, not wrong-slot detection. Name the
45
45
  // result slot on the artifact_record description and success path so an agent
@@ -129,8 +129,7 @@ async function loadSessionActionsPayload(creds, cfg, opts) {
129
129
  // #7 — heavy tool groups pulled out of registerZiggsTools so the
130
130
  // lean session-start tier (ZIGGS_MCP_CORE_ONLY) can skip registering them.
131
131
  // killed the dedicated publish tools: publishing IS
132
- // ziggs_agreement_propose with proposedTo "everyone"/"org" (no providerId =
133
- // quest, providerId = own id = standing offer). What remains here is the
132
+ // ziggs_agreement_quest and ziggs_agreement_offer. What remains here is the
134
133
  // browse view and the relay-provisioning composite.
135
134
  function registerMarketplaceTools(server, creds) {
136
135
  registerStrictTool(server, 'ziggs_provision_relay_workers', 'Initiator path: provision per-step worker agreements before relay kickoff. Reuses active delegations under the hire, claims standing offers when available, otherwise proposes delegations (worker must approve — never impersonated). Returns relay:v1 payload and POST /tasks body when all steps are active.', {
@@ -154,7 +153,7 @@ function registerMarketplaceTools(server, creds) {
154
153
  .boolean()
155
154
  .optional()
156
155
  .describe('When true and readyForKickoff, also POST /tasks on the hire for relay-coordinator'),
157
- }, WRITE, async ({ hireAgreementId, chatId, inputArtifactIds, steps, kickoff }) => {
156
+ }, write('Line up workers for a relay'), async ({ hireAgreementId, chatId, inputArtifactIds, steps, kickoff }) => {
158
157
  try {
159
158
  const result = await provisionRelayWorkers({
160
159
  creds,
@@ -192,7 +191,7 @@ function registerConnectionTools(server, creds) {
192
191
  registerStrictTool(server, 'ziggs_connection_list', 'Discover the third-party connections (credentials like GitHub/Jira, and remote MCP servers — NOT agent-to-agent Links; see ziggs_link_list for that) you hold grants for, without the owner sharing connectionId/grantId out of band. ' +
193
192
  'Returns, per connection: connectionId, provider, and the grant(s) you hold — each as the canonical grant shape (grantId, scope, caveats, and grant health active/expired/revoked). ' +
194
193
  'Read-only — never returns credential material. ' +
195
- 'How to use a row: provider "mcp" → ziggs_mcp_tools_list / ziggs_mcp_tool_call; named connectors (github, jira, …) → ziggs_connection_proxy.', {}, READ_ONLY, async () => {
194
+ 'How to use a row: provider "mcp" → ziggs_mcp_tools_list / ziggs_mcp_tool_call; named connectors (github, jira, …) → ziggs_connection_proxy.', {}, readOnly('List connections you can use'), async () => {
196
195
  try {
197
196
  const connections = await new ConnectionsClient(creds.operatorKey, creds.agentId).listForHolder();
198
197
  return textResult({ connections });
@@ -213,7 +212,7 @@ function registerConnectionTools(server, creds) {
213
212
  .string()
214
213
  .optional()
215
214
  .describe('Grant to use. Omit to use the live grant on that connection.'),
216
- }, READ_ONLY, async ({ connectionId, grantId }) => {
215
+ }, readOnly('List tools on a connected server'), async ({ connectionId, grantId }) => {
217
216
  try {
218
217
  const t = await resolveMcpConnectionTarget(creds, connectionId, grantId);
219
218
  const tools = await withMcpGatewayClient(creds, t.connectionId, t.grantId, async (c) => {
@@ -251,7 +250,7 @@ function registerConnectionTools(server, creds) {
251
250
  .string()
252
251
  .optional()
253
252
  .describe('Grant to use. Omit to use the live grant on that connection.'),
254
- }, WRITE, async ({ tool, args, connectionId, grantId }) => {
253
+ }, write('Call a tool on a connected server'), async ({ tool, args, connectionId, grantId }) => {
255
254
  try {
256
255
  if (!tool)
257
256
  throw new Error('tool is required');
@@ -273,7 +272,7 @@ function registerConnectionTools(server, creds) {
273
272
  registerCapability(server, requestConnectionCapability, creds);
274
273
  }
275
274
  export function registerZiggsTools(server, creds, cfg) {
276
- registerStrictTool(server, 'ziggs_auth_status', 'Verify the session binding: acting agent id, owner user id, and org scope. Call after connect before inbox/chats. Includes pendingDecisions summary when approve/reject is waiting. ("Connection" refers only to third-party credential connections, see ziggs_connection_proxy.)', {}, READ_ONLY, async () => {
275
+ registerStrictTool(server, 'ziggs_auth_status', 'Verify the session binding: acting agent id, owner user id, and org scope. Call after connect before inbox/chats. Includes pendingDecisions summary when approve/reject is waiting. ("Connection" refers only to third-party credential connections, see ziggs_connection_proxy.)', {}, readOnly('Check session identity'), async () => {
277
276
  const claims = decodeOperatorKeyClaims(creds.operatorKey);
278
277
  const webOrigin = resolveWebAppOrigin(cfg.ZIGGS_WEB_URL);
279
278
  // #4 — delegate access and session actions are independent.
@@ -361,7 +360,7 @@ export function registerZiggsTools(server, creds, cfg) {
361
360
  : 'Next: ziggs_inbox or ziggs_pending_decisions at session start.',
362
361
  });
363
362
  });
364
- registerStrictTool(server, 'ziggs_org_list', 'List every org you (the operator) belong to — { orgId, name, kind, role }. Unlike ziggs_grant_list (granted scopes only), this is your full membership — useful before OAuth reconnect when the human wants to pick a target org.', {}, READ_ONLY, async () => {
363
+ registerStrictTool(server, 'ziggs_org_list', 'List every org you (the operator) belong to — { orgId, name, kind, role }. Unlike ziggs_grant_list (granted scopes only), this is your full membership — useful before OAuth reconnect when the human wants to pick a target org.', {}, readOnly('List your orgs'), async () => {
365
364
  try {
366
365
  const orgs = await fetchMyOrgs(creds);
367
366
  return textResult({ count: orgs.length, orgs });
@@ -370,7 +369,7 @@ export function registerZiggsTools(server, creds, cfg) {
370
369
  return toolError(e);
371
370
  }
372
371
  });
373
- registerStrictTool(server, 'ziggs_pending_decisions', ZIGGS_PENDING_DECISIONS_DESCRIPTION, {}, READ_ONLY, async () => {
372
+ registerStrictTool(server, 'ziggs_pending_decisions', ZIGGS_PENDING_DECISIONS_DESCRIPTION, {}, readOnly('Check what needs your attention'), async () => {
374
373
  try {
375
374
  const payload = await loadSessionActionsPayload(creds, cfg);
376
375
  const decisions = (payload.decisions ?? []);
@@ -388,7 +387,7 @@ export function registerZiggsTools(server, creds, cfg) {
388
387
  }
389
388
  });
390
389
  if (cfg.debugTools) {
391
- registerStrictTool(server, 'ziggs_smoke_impersonation', '[Internal/debug] Connectivity check for the operator-key impersonation path — lists agreements and snapshots the first chat. Not part of normal delegate workflow; use ziggs_agreement_list / ziggs_context_snapshot instead.', {}, READ_ONLY, async () => {
390
+ registerStrictTool(server, 'ziggs_smoke_impersonation', '[Internal/debug] Connectivity check for the operator-key impersonation path — lists agreements and snapshots the first chat. Not part of normal delegate workflow; use ziggs_agreement_list / ziggs_context_snapshot instead.', {}, readOnly('Debug: check the impersonation path'), async () => {
392
391
  try {
393
392
  const agreements = await getMyAgreements({}, creds);
394
393
  const chats = await listMyChats(creds);
@@ -417,7 +416,7 @@ export function registerZiggsTools(server, creds, cfg) {
417
416
  .string()
418
417
  .optional()
419
418
  .describe('Optional grant id when reading under a context grant'),
420
- }, READ_ONLY, async ({ chatId, maxMessages, contextGrantId }) => {
419
+ }, readOnly('Catch up on a chat'), async ({ chatId, maxMessages, contextGrantId }) => {
421
420
  try {
422
421
  const client = new ContextReadClient(creds.operatorKey, creds.agentId);
423
422
  const result = await client.snapshot(chatId, {
@@ -439,7 +438,7 @@ export function registerZiggsTools(server, creds, cfg) {
439
438
  .string()
440
439
  .optional()
441
440
  .describe('Optional filter: pending, approved, rejected, …'),
442
- }, READ_ONLY, async ({ scope, proposalStatus }) => {
441
+ }, readOnly('List your agreements'), async ({ scope, proposalStatus }) => {
443
442
  try {
444
443
  const agreements = await getMyAgreements({
445
444
  ...(proposalStatus ? { proposalStatus } : {}),
@@ -451,7 +450,7 @@ export function registerZiggsTools(server, creds, cfg) {
451
450
  return toolError(e);
452
451
  }
453
452
  });
454
- registerStrictTool(server, 'ziggs_agreement_get', 'Fetch a single agreement by id.', { agreementId: z.string() }, READ_ONLY, async ({ agreementId }) => {
453
+ registerStrictTool(server, 'ziggs_agreement_get', 'Fetch a single agreement by id.', { agreementId: z.string() }, readOnly('Read one agreement'), async ({ agreementId }) => {
455
454
  try {
456
455
  const agreement = await getAgreement(agreementId, creds);
457
456
  if (!agreement)
@@ -462,7 +461,7 @@ export function registerZiggsTools(server, creds, cfg) {
462
461
  return toolError(e);
463
462
  }
464
463
  });
465
- registerStrictTool(server, 'ziggs_chat_list', 'List chats the acting agent is a member of (GET /chats/mine).', {}, READ_ONLY, async () => {
464
+ registerStrictTool(server, 'ziggs_chat_list', 'List chats the acting agent is a member of (GET /chats/mine).', {}, readOnly('List your chats'), async () => {
466
465
  try {
467
466
  const chats = await listMyChats(creds);
468
467
  return textResult({ count: chats.length, chats });
@@ -487,7 +486,7 @@ export function registerZiggsTools(server, creds, cfg) {
487
486
  .string()
488
487
  .optional()
489
488
  .describe('Retry-safe key. Reuse the SAME key when re-sending the SAME logical message (e.g. after a network error/timeout) so it is stored and delivered exactly once — the backend dedupes on chatId + messageId. Use a fresh key (or omit) for a genuinely new message.'),
490
- }, WRITE, async ({ chatId, receiverId, text, entryType, idempotencyKey }) => {
489
+ }, write('Send a chat message'), async ({ chatId, receiverId, text, entryType, idempotencyKey }) => {
491
490
  try {
492
491
  const result = await sendChatMessage({
493
492
  chatId,
@@ -508,85 +507,10 @@ export function registerZiggsTools(server, creds, cfg) {
508
507
  return toolError(e);
509
508
  }
510
509
  });
511
- registerStrictTool(server, 'ziggs_agreement_propose', 'Propose an agreement — direct, broadcast, hand-off, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (ziggs_marketplace_view → ziggs_agreement_claim; never counter a listing), 3) POST a quest broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. DIRECT: proposedTo = one counterparty id, chatId required. Omit providerId (or set it to proposedTo) to commission the recipient (they work, your side pays); set providerId to your own agent id to offer (you work, proposedTo pays); a third-party providerId brokers (they work, proposedTo pays) and requires that provider to have a matching active offer. BROADCAST: proposedTo "everyone" (fully public) or "org" (your active org only), chatId optional — with no providerId this publishes a QUEST (whoever claims does the work, your side pays); with providerId = your own id it publishes a STANDING OFFER (you work, the claimer pays). HAND-OFF (share an agent you hired): set parentAgreementId = that ACTIVE hire and providerId = its provider; proposedTo may be "everyone"/"org" (claimable) or a specific beneficiary id (they approve directly). The provider stays pinned — whoever claims/approves is the CUSTOMER the work is done for, never the worker, and on a priced hand-off they are also the payer (price omitted/0 = free: nobody is billed for their tasks). Handing off someone else\'s agent leaves that provider\'s approval pending — it must accept once before the hand-off can be claimed. Claiming is ziggs_agreement_claim; browsing is ziggs_marketplace_view. LINK: engagementKind "link" with proposedTo = an agent id proposes bilateral trust (no chat, no money). The server routes parties.proposedTo to that agent\'s owner human (a person decides who their delegate trusts) — the id you pass may differ from parties.proposedTo in the response; when it does, `note` explains the rewrite. Approve via ziggs_agreement_respond. ROLES: proposedTo is the CUSTOMER — the party the work is done for; the payer is only who pays, always derived server-side as the non-providing side — there is no payer input. engagementKind "service" (default) = one deliverable; "hire" = ongoing engagement. Agreements are STANDING by default (lifecycle "open": no expiry, unlimited tasks) — hire once, then keep spawning tasks under the same agreement; set expiresAt (time-bound) or maxExecutions (count-bound) only when the engagement should end on its own. price is recorded on the agreement but does not itself trigger a transfer.', {
512
- proposedTo: z
513
- .string()
514
- .describe('Counterparty id for a direct proposal, or "everyone"/"org" to broadcast'),
515
- chatId: z
516
- .string()
517
- .optional()
518
- .describe('Required on a direct proposal; optional on broadcasts and links'),
519
- description: z.string(),
520
- providerId: z
521
- .string()
522
- .optional()
523
- .describe(`${AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION} With parentAgreementId = an active hire and this set to its provider, the proposal is a HAND-OFF: the provider stays pinned and proposedTo/claimer is the customer.`),
524
- price: z
525
- .number()
526
- .optional()
527
- .describe('Amount in CENTS — 500 means $5.00, and ϟ5.00 in the UI. Optional; does not trigger a ' +
528
- 'transfer by itself. Convert BOTH ways or you are off by 100x: a human who says ' +
529
- '"ϟ2" or "$2 per task" means price 200, not 2; quoting 500 back as "$500" or "ϟ500" ' +
530
- 'is the same mistake inverted. ϟ is the currency symbol the UI shows — it is not cents.'),
531
- engagementKind: z
532
- .enum(['hire', 'service', 'link'])
533
- .optional()
534
- .describe("'service' (default) = one-off deliverable; 'hire' = ongoing engagement; 'link' = bilateral trust link (no work, no money)"),
535
- expiresAt: z
536
- .string()
537
- .optional()
538
- .describe('ISO date: the agreement ends (is cancelled, tasks and all) at this time. Omit for a standing agreement.'),
539
- maxExecutions: z
540
- .number()
541
- .int()
542
- .positive()
543
- .optional()
544
- .describe('The agreement auto-fulfills after this many completed tasks. Omit for unlimited tasks.'),
545
- lifecycle: z
546
- .enum(['open', 'time-bound', 'count-bound'])
547
- .optional()
548
- .describe("Usually inferred: expiresAt → 'time-bound', maxExecutions → 'count-bound', neither → 'open' (standing)."),
549
- billing: z
550
- .enum(['total', 'per_task'])
551
- .optional()
552
- .describe("How price reads. 'total' (default) = one price for the whole engagement, escrowed now and paid at the end. 'per_task' = a RATE charged for each completed task, paid as work lands — requires a standing (open) agreement, and is the default for a hire. Never send 'per_task' for a one-off price or the payer is charged it once per task."),
553
- }, WRITE, async ({ proposedTo, chatId, description, providerId, price, engagementKind, expiresAt, maxExecutions, lifecycle, billing, }) => {
554
- try {
555
- const { agreement, shape } = await proposeUnified({
556
- proposedTo,
557
- chatId,
558
- description,
559
- providerId: providerId?.trim() || undefined,
560
- price,
561
- engagementKind,
562
- expiresAt,
563
- maxExecutions,
564
- lifecycle,
565
- billing,
566
- }, creds);
567
- // surface the owner-routing rewrite so agents do not think
568
- // the id they passed was ignored silently.
569
- const routedProposedTo = shape === 'link' &&
570
- agreement?.parties?.proposedTo &&
571
- proposedTo &&
572
- proposedTo !== agreement.parties.proposedTo
573
- ? `Routed approval to the target agent's owner (${agreement.parties.proposedTo}): a person decides who their delegate trusts. You passed proposedTo=${proposedTo}.`
574
- : undefined;
575
- return textResult({
576
- shape,
577
- agreement,
578
- ...(routedProposedTo ? { note: routedProposedTo } : {}),
579
- ...(shape === 'quest' || shape === 'offer'
580
- ? {
581
- nextSteps: 'Published to the marketplace — claimable via ziggs_agreement_claim; it also appears in ziggs_marketplace_view.',
582
- }
583
- : {}),
584
- });
585
- }
586
- catch (e) {
587
- return toolError(e);
588
- }
589
- });
510
+ // One verb per shape, replacing the propose dispatch table. The direction
511
+ // of work is in the verb name, so no caller reconstructs a providerId
512
+ // permutation to reach the shape it already knew it wanted.
513
+ registerCapabilities(server, AGREEMENT_VERB_CAPABILITIES, creds);
590
514
  registerCapability(server, agreementClaimCapability, creds);
591
515
  registerStrictTool(server, 'ziggs_agreement_subcontract', 'Delegate part of an engagement to another agent under an existing parent agreement (a sub-agreement; the worker must approve — never impersonated). Use when you hold an active agreement and want a third agent to do a slice of it. Requires parentAgreementId and the chat you are coordinating in. Spawn tasks for the worker under the sub-agreement once it is active.', {
592
516
  parentAgreementId: z.string().describe('The active agreement you are delegating under'),
@@ -604,7 +528,7 @@ export function registerZiggsTools(server, creds, cfg) {
604
528
  .optional()
605
529
  .describe("Usually inferred: expiresAt → 'time-bound', maxExecutions → 'count-bound', neither → 'open' (standing)."),
606
530
  agreementDescription: z.string().optional(),
607
- }, WRITE, async ({ parentAgreementId, executorId, chatId, description, price, expiresAt, maxExecutions, lifecycle, agreementDescription, }) => {
531
+ }, write('Subcontract part of your work'), async ({ parentAgreementId, executorId, chatId, description, price, expiresAt, maxExecutions, lifecycle, agreementDescription, }) => {
608
532
  try {
609
533
  const agreement = await delegateAgreement({
610
534
  parentAgreementId,
@@ -634,7 +558,7 @@ export function registerZiggsTools(server, creds, cfg) {
634
558
  registerStrictTool(server, 'ziggs_agreement_respond', 'Approve or reject a pending DIRECT agreement proposal addressed to YOU — your own party slot (PUT /approvals/:partyId, which takes a decision only from the party itself). A proposal bound to your PRINCIPAL instead is not yours to answer and this tool refuses it: consent is deliberately withheld from delegates, so no retry and no other tool changes it — the human decides it in the Ziggs app, under Agreements. ziggs_pending_decisions marks which is which with respondableBy (agent | human), so check there before calling. Open broadcasts (quests, standing offers, link invites) have no personal approval slot — claim those with ziggs_agreement_claim instead, or ignore them to pass. ONE exception: a hand-off that pins YOU (or your agent) as provider carries your pending approval even as an open broadcast — approving it consents to serving whoever claims it (the row stays open for claims); rejecting cancels the hand-off.', {
635
559
  agreementId: z.string(),
636
560
  action: z.enum(['approve', 'reject']),
637
- }, WRITE, async ({ agreementId, action }) => {
561
+ }, write('Approve or reject a proposal'), async ({ agreementId, action }) => {
638
562
  try {
639
563
  const claims = decodeOperatorKeyClaims(creds.operatorKey);
640
564
  const ownerId = claims?.ownerId ?? cfg.ZIGGS_OWNER_USER_ID;
@@ -651,7 +575,7 @@ export function registerZiggsTools(server, creds, cfg) {
651
575
  });
652
576
  registerStrictTool(server, 'ziggs_agreement_revoke', 'Revoke any agreement you are a party to — hire, service, quest, standing offer, or link (DELETE /agreements/:id). Either party may revoke; this ends the engagement immediately. Revoking a link ends cross-org reach to that peer; revoking an open broadcast takes it off the marketplace.', {
653
577
  agreementId: z.string().describe('Agreement to revoke'),
654
- }, DESTRUCTIVE, async ({ agreementId }) => {
578
+ }, destructive('Revoke an agreement'), async ({ agreementId }) => {
655
579
  try {
656
580
  const result = await revokeAgreement(agreementId, creds);
657
581
  const isLink = result.agreement?.engagementKind === 'link';
@@ -673,7 +597,7 @@ export function registerZiggsTools(server, creds, cfg) {
673
597
  price: z
674
598
  .number()
675
599
  .optional()
676
- .describe('Revised price, in CENTS — 500 means $5.00 / ϟ5.00 (see ziggs_agreement_propose).'),
600
+ .describe('Revised price, in CENTS — 500 means $5.00 / ϟ5.00 (see ziggs_agreement_commission).'),
677
601
  agreementDescription: z
678
602
  .string()
679
603
  .optional()
@@ -688,7 +612,7 @@ export function registerZiggsTools(server, creds, cfg) {
688
612
  .string()
689
613
  .optional()
690
614
  .describe('Revised task description for the spawned work'),
691
- }, WRITE, async ({ agreementId, ...counter }) => {
615
+ }, write('Counter a proposal'), async ({ agreementId, ...counter }) => {
692
616
  try {
693
617
  const agreement = await counterAgreement(agreementId, counter, creds);
694
618
  return textResult({ status: 'countered', agreementId, agreement });
@@ -699,7 +623,7 @@ export function registerZiggsTools(server, creds, cfg) {
699
623
  });
700
624
  registerStrictTool(server, 'ziggs_agreement_fulfill', 'END an agreement you PROVIDE — permanently (POST /agreements/:id/fulfill). Fulfilling terminates the whole relationship, not one deliverable: every grant the agreement conferred (context, connection, payment) is revoked, its shared space is torn down, and it cannot be reopened — the counterparty would have to re-hire you from scratch. Finished WORK is reported with ziggs_task_set_result, which closes the task and leaves the agreement standing for the next one. Only fulfill a count/time-bound engagement whose full scope is delivered and where nothing more is expected — never a standing hire that just finished a task. Party-gated server-side: only the providing side can fulfill.', {
701
625
  agreementId: z.string().describe('The agreement you provide, to mark fulfilled'),
702
- }, WRITE, async ({ agreementId }) => {
626
+ }, write('Close an agreement you provide'), async ({ agreementId }) => {
703
627
  try {
704
628
  const result = await fulfillAgreement(agreementId, creds);
705
629
  return textResult({ status: 'fulfilled', agreementId, agreement: result.agreement });
@@ -721,7 +645,7 @@ export function registerZiggsTools(server, creds, cfg) {
721
645
  .number()
722
646
  .optional()
723
647
  .describe('Long-poll: hold up to this many seconds (server-clamped, ~25 max) and return as soon as something new arrives — same response shape, no busy re-polling. Omit for an immediate snapshot.'),
724
- }, READ_ONLY, async ({ ack, handledResourceIds, waitSeconds }) => {
648
+ }, readOnly('Check your inbox'), async ({ ack, handledResourceIds, waitSeconds }) => {
725
649
  try {
726
650
  const client = new InboxClient(creds.operatorKey, creds.agentId);
727
651
  // Ack-before-fetch is load-bearing; everything else is independent of
@@ -836,7 +760,7 @@ export function registerZiggsTools(server, creds, cfg) {
836
760
  .boolean()
837
761
  .optional()
838
762
  .describe('When true, restructuring the plan mid-task parks it for a fresh acknowledgement instead of applying silently.'),
839
- }, WRITE, async ({ agreementId, description, parentTaskId, assigneeId, inputArtifactIds, plan, planReviewTiming, requireMidWorkPlanAck, }) => {
763
+ }, write('Create a task'), async ({ agreementId, description, parentTaskId, assigneeId, inputArtifactIds, plan, planReviewTiming, requireMidWorkPlanAck, }) => {
840
764
  try {
841
765
  const task = await createTask({
842
766
  agreementId,
@@ -871,7 +795,7 @@ export function registerZiggsTools(server, creds, cfg) {
871
795
  .string()
872
796
  .optional()
873
797
  .describe('Optional dedup key: a redelivered transition with the same key no-ops (returns the task) instead of erroring on an already-terminal task. Derive it deterministically (e.g. from taskId + target state) so a crash-replay reproduces it.'),
874
- }, WRITE, async ({ taskId, state, result, errorMessage, idempotencyKey }) => {
798
+ }, write('File a task result'), async ({ taskId, state, result, errorMessage, idempotencyKey }) => {
875
799
  try {
876
800
  const task = await updateTaskState(taskId, state, { result, errorMessage, idempotencyKey }, creds);
877
801
  return textResult({ ok: true, task });
@@ -906,7 +830,7 @@ export function registerZiggsTools(server, creds, cfg) {
906
830
  .describe('Optional step output stored with this replace.'),
907
831
  }))
908
832
  .describe('Full replacement step list (ordered)'),
909
- }, WRITE, async ({ taskId, steps }) => {
833
+ }, write('Update a task plan'), async ({ taskId, steps }) => {
910
834
  try {
911
835
  const task = await replaceTaskPlan(taskId, steps, creds);
912
836
  return textResult({ ok: true, task });
@@ -930,7 +854,7 @@ export function registerZiggsTools(server, creds, cfg) {
930
854
  .boolean()
931
855
  .optional()
932
856
  .describe('Shorthand for assignedTo=<this delegate\'s own agent id>. Takes precedence over assignedTo if both are set.'),
933
- }, READ_ONLY, async ({ state, cursor, limit, assignedTo, assignedToMe }) => {
857
+ }, readOnly('List tasks'), async ({ state, cursor, limit, assignedTo, assignedToMe }) => {
934
858
  try {
935
859
  const effectiveAssignedTo = assignedToMe ? creds.agentId : assignedTo;
936
860
  const result = await listTasks({ state, cursor, limit, assignedTo: effectiveAssignedTo }, creds);
@@ -940,7 +864,7 @@ export function registerZiggsTools(server, creds, cfg) {
940
864
  return toolError(e);
941
865
  }
942
866
  });
943
- registerStrictTool(server, 'ziggs_task_get', 'Fetch a single task by id (GET /tasks/:id). Use this when a human hands you a taskId directly (e.g. "work on task_…") so you can read the work-order — its description, plan, assignee, state, and result — before acting. Same operator-key scope as ziggs_task_list; pairs with ziggs_task_set_result to close the task.', { taskId: z.string() }, READ_ONLY, async ({ taskId }) => {
867
+ registerStrictTool(server, 'ziggs_task_get', 'Fetch a single task by id (GET /tasks/:id). Use this when a human hands you a taskId directly (e.g. "work on task_…") so you can read the work-order — its description, plan, assignee, state, and result — before acting. Same operator-key scope as ziggs_task_list; pairs with ziggs_task_set_result to close the task.', { taskId: z.string() }, readOnly('Read one task'), async ({ taskId }) => {
944
868
  try {
945
869
  const task = await getTask(taskId, creds);
946
870
  if (!task)
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { ContextGrantsClient, addChatMember, contextBounds, resolveOrgScopeId, LINK_CAPABILITIES, DISCOVERY_CAPABILITIES, contextDelegateCapability, } from '@ziggs-ai/api-client';
3
- import { WRITE, DESTRUCTIVE } from './toolAnnotations.js';
3
+ import { write, destructive } from './toolAnnotations.js';
4
4
  import { registerStrictTool } from './strictParams.js';
5
5
  import { toolError } from './toolError.js';
6
6
  import { registerCapabilities, registerCapability, textResult } from './capabilityAdapter.js';
@@ -29,7 +29,7 @@ export function registerTrustTools(server, creds, cfg) {
29
29
  .optional()
30
30
  .nullable()
31
31
  .describe('ISO-8601 expiry; omit for platform default TTL'),
32
- }, WRITE, async ({ holderId, scopeKind, scopeId, temporal, expiresAt }) => {
32
+ }, write('Give another agent access'), async ({ holderId, scopeKind, scopeId, temporal, expiresAt }) => {
33
33
  // An artifact predates any watermark you could set, so from-now would
34
34
  // validate and then read empty — the server refuses it outright. Defaulting
35
35
  // artifact scope to from-now here made the tool's own default invocation
@@ -98,7 +98,7 @@ export function registerTrustTools(server, creds, cfg) {
98
98
  }
99
99
  registerStrictTool(server, 'ziggs_context_revoke_grant', 'Revoke a context grant and its descendants (DELETE /context/grants/:id). You can revoke (narrow) any grant you hold — this needs no special scope. Revoking a grant you do NOT hold (one you issued, or on a scope you own) is a human-authority action: as a delegate you are limited to grants you hold; the human/owner does the rest.', {
100
100
  grantId: z.string(),
101
- }, DESTRUCTIVE, async ({ grantId }) => {
101
+ }, destructive('Revoke a context grant'), async ({ grantId }) => {
102
102
  try {
103
103
  const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
104
104
  const result = await client.revokeGrant(grantId);
@@ -53,7 +53,7 @@ Ask Claude to call tools in order:
53
53
 
54
54
  1. `ziggs_chat_list` or `ziggs_grant_list`
55
55
  2. `ziggs_chat_send` (chat you belong to)
56
- 3. `ziggs_agreement_propose` + `ziggs_agreement_respond` (optional)
56
+ 3. `ziggs_agreement_commission` + `ziggs_agreement_respond` (optional)
57
57
 
58
58
  ## Fleet key alternative
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.9.12",
3
+ "version": "0.10.0",
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": {
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/sdk": "^1.29.0",
41
- "@ziggs-ai/api-client": "0.9.12",
41
+ "@ziggs-ai/api-client": "0.10.0",
42
42
  "dotenv": "^16.6.1",
43
43
  "zod": "^3.24.2"
44
44
  },
@@ -6,7 +6,7 @@ You are a delegate agent on a Ziggs team. The MCP tools are the connection; oper
6
6
  - Flow: inbox → read → act → ack.
7
7
  - Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack together with `handledResourceIds` for every delivery (and quest) in that window — an ack that would bury unlisted deliveries is refused. Never rewind an ack to an older timestamp.
8
8
  - 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 as plan steps with ziggs_task_replace_plan.
9
- - 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 quest (ziggs_agreement_propose with proposedTo "everyone"/"org") when nothing listed fits, and supply claims you; (4) PROPOSE directly only for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — 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.
9
+ - 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 quest (ziggs_agreement_quest) 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_commission 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.
10
10
  - Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
11
11
  - When humanAttention is present, tell the human immediately (pull-only MCP has no push).
12
12
  - At session start call ziggs_pending_decisions; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks). ziggs_inbox and ziggs_auth_status report the same counts and point back to it for the card.
@@ -25,7 +25,7 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
25
25
  - Flow: inbox → read → act → ack.
26
26
  - Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack together with `handledResourceIds` for every delivery (and quest) in that window — an ack that would bury unlisted deliveries is refused. Never rewind an ack to an older timestamp.
27
27
  - 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 as plan steps with ziggs_task_replace_plan.
28
- - 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 quest (ziggs_agreement_propose with proposedTo "everyone"/"org") when nothing listed fits, and supply claims you; (4) PROPOSE directly only for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — 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.
28
+ - 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 quest (ziggs_agreement_quest) 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_commission 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.
29
29
  - Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
30
30
  - When humanAttention is present, tell the human immediately (pull-only MCP has no push).
31
31
  - At session start call ziggs_pending_decisions; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks). ziggs_inbox and ziggs_auth_status report the same counts and point back to it for the card.
@@ -95,7 +95,7 @@ See [references/untrusted-input.md](references/untrusted-input.md).
95
95
  When coordinating with another org’s delegate:
96
96
 
97
97
  1. Inbox → read new messages in the shared chat. Counterparties may appear as opaque `rpb_*` / `psn_*` presentation refs (plus a `presentation` face) — not as raw account ids.
98
- 2. Reply with **`ziggs_chat_send`** (echo an `rpb_*` receiverId as-is; do not look it up, wake, or pay against it) or drive **`ziggs_agreement_propose`** / **`ziggs_agreement_respond`** as appropriate.
98
+ 2. Reply with **`ziggs_chat_send`** (echo an `rpb_*` receiverId as-is; do not look it up, wake, or pay against it) or drive **`ziggs_agreement_commission`** / **`ziggs_agreement_respond`** as appropriate.
99
99
  3. If trust is missing, **`ziggs_agent_search`** → human picks counterparty → **`ziggs_context_issue_grant`** (with approval) before reading their context.
100
100
  4. Ack the handled envelope (`ackTo`) before ending the turn.
101
101
 
@@ -41,7 +41,7 @@ connection) **is just an agreement** (`engagementKind: "link"`). Create it, the
41
41
  counterparty owner approves it, and unpublished delegates can then reach each other.
42
42
 
43
43
  1. Human describes goal and counterparty.
44
- 2. Propose the link with **`ziggs_agreement_propose`** (`engagementKind: "link"`, `proposedTo` =
44
+ 2. Propose the link with **`ziggs_agreement_commission`** (`engagementKind: "link"`, `proposedTo` =
45
45
  the target delegate agent id; use `ziggs_agent_search` to find agents — do not guess ids).
46
46
  No agent id? Mint a shareable invite with **`ziggs_link_create_invite`** instead; the
47
47
  recipient claims it with **`ziggs_agreement_claim`**.
@@ -8,7 +8,7 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
8
8
  - Flow: inbox → read → act → ack.
9
9
  - Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack together with `handledResourceIds` for every delivery (and quest) in that window — an ack that would bury unlisted deliveries is refused. Never rewind an ack to an older timestamp.
10
10
  - 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 as plan steps with ziggs_task_replace_plan.
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 quest (ziggs_agreement_propose with proposedTo "everyone"/"org") when nothing listed fits, and supply claims you; (4) PROPOSE directly only for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — 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.
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 quest (ziggs_agreement_quest) 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_commission 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.
12
12
  - Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
13
13
  - When humanAttention is present, tell the human immediately (pull-only MCP has no push).
14
14
  - At session start call ziggs_pending_decisions; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks). ziggs_inbox and ziggs_auth_status report the same counts and point back to it for the card.
@@ -8,7 +8,7 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
8
8
  - Flow: inbox → read → act → ack.
9
9
  - Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack together with `handledResourceIds` for every delivery (and quest) in that window — an ack that would bury unlisted deliveries is refused. Never rewind an ack to an older timestamp.
10
10
  - 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 as plan steps with ziggs_task_replace_plan.
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 quest (ziggs_agreement_propose with proposedTo "everyone"/"org") when nothing listed fits, and supply claims you; (4) PROPOSE directly only for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — 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.
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 quest (ziggs_agreement_quest) 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_commission 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.
12
12
  - Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
13
13
  - When humanAttention is present, tell the human immediately (pull-only MCP has no push).
14
14
  - At session start call ziggs_pending_decisions; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks). ziggs_inbox and ziggs_auth_status report the same counts and point back to it for the card.