@axonity-ai/mcp 0.17.1 → 0.20.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 +105 -37
- package/dist/errors.js +45 -11
- package/dist/errors.js.map +1 -1
- package/dist/generated/listFilters.js +23 -1
- package/dist/generated/listFilters.js.map +1 -1
- package/dist/index.js +208 -26
- package/dist/index.js.map +1 -1
- package/dist/tools/access.d.ts +19 -0
- package/dist/tools/access.js +71 -0
- package/dist/tools/access.js.map +1 -0
- package/dist/tools/applications.d.ts +24 -0
- package/dist/tools/applications.js +145 -0
- package/dist/tools/applications.js.map +1 -0
- package/dist/tools/concepts.js +68 -3
- package/dist/tools/concepts.js.map +1 -1
- package/dist/tools/conventions.d.ts +2 -2
- package/dist/tools/conventions.js +105 -38
- package/dist/tools/conventions.js.map +1 -1
- package/dist/tools/dataSources.d.ts +5 -5
- package/dist/tools/dataSources.js +10 -9
- package/dist/tools/dataSources.js.map +1 -1
- package/dist/tools/dataTables.js +7 -5
- package/dist/tools/dataTables.js.map +1 -1
- package/dist/tools/entityContract.d.ts +44 -0
- package/dist/tools/entityContract.js +90 -0
- package/dist/tools/entityContract.js.map +1 -0
- package/dist/tools/extras.js +40 -2
- package/dist/tools/extras.js.map +1 -1
- package/dist/tools/knowledgeGraph.d.ts +28 -0
- package/dist/tools/knowledgeGraph.js +126 -0
- package/dist/tools/knowledgeGraph.js.map +1 -0
- package/dist/tools/queries.d.ts +18 -0
- package/dist/tools/queries.js +70 -0
- package/dist/tools/queries.js.map +1 -0
- package/dist/tools/triggers.js +69 -79
- package/dist/tools/triggers.js.map +1 -1
- package/dist/tools/validation.js +21 -3
- package/dist/tools/validation.js.map +1 -1
- package/dist/tools/workflowMutations.d.ts +18 -0
- package/dist/tools/workflowMutations.js +61 -5
- package/dist/tools/workflowMutations.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applications beyond the entity verbs (axonity-mcp#86, the connector half of
|
|
3
|
+
* axonity-flow#1901): which executors an application runs on, the executors
|
|
4
|
+
* themselves, and what the application has learned about itself.
|
|
5
|
+
*
|
|
6
|
+
* The plain verbs — list/read/create/update/delete/restore, the versions and
|
|
7
|
+
* request_publish_application — come from the registrars (see `ENTITIES` in
|
|
8
|
+
* index.ts). Publishing is the human approval queue, as everywhere: the limits
|
|
9
|
+
* are what an executor on a customer's machine is allowed to do.
|
|
10
|
+
*
|
|
11
|
+
* **Executors are read-only here.** Registering one mints its token and
|
|
12
|
+
* revoking one cuts a machine off; both are deny-listed. Nothing here returns a
|
|
13
|
+
* token: an executor's answer carries only its prefix and expiry.
|
|
14
|
+
*
|
|
15
|
+
* **Who may write.** Linking executors and writing, forgetting or restoring
|
|
16
|
+
* knowledge are `require_admin` on the platform, and a service token always
|
|
17
|
+
* resolves as a member — so today these answer 403 for every token, and
|
|
18
|
+
* `ForbiddenError` says so in words. The tools are here so the refusal names
|
|
19
|
+
* its reason rather than the tool being missing, and so they work the day the
|
|
20
|
+
* platform opens them.
|
|
21
|
+
*/
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
import { guard, jsonResult } from "./result.js";
|
|
24
|
+
const APPLICATIONS = "/api/v1/applications";
|
|
25
|
+
const EXECUTORS = "/api/v1/executors";
|
|
26
|
+
const APPLICATION_ID = z.string().describe("The application's id.");
|
|
27
|
+
const EXECUTOR_ID = z.string().describe("The executor's id.");
|
|
28
|
+
const ADMIN_ONLY = "ADMINISTRATOR ONLY on the platform, and a service token always counts as " +
|
|
29
|
+
"a member: today this is refused for every token with a 403 that says so. " +
|
|
30
|
+
"Do not retry — tell your human; an administrator does it in Axonity " +
|
|
31
|
+
"(Digital Twin → Applications).";
|
|
32
|
+
const PAGE_NOTE = "THE RESPONSE IS ONE PAGE: { items, nextCursor, pageSize, hasMore }. While " +
|
|
33
|
+
"hasMore is true you have not seen every entry — pass nextCursor back as " +
|
|
34
|
+
"cursor until it is null. Never build a cursor.";
|
|
35
|
+
const PAGING = {
|
|
36
|
+
limit: z
|
|
37
|
+
.number()
|
|
38
|
+
.int()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Page size. The platform clamps it to its maximum."),
|
|
41
|
+
cursor: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Opaque continuation cursor from the previous response's nextCursor. " +
|
|
45
|
+
"Omit for the first page."),
|
|
46
|
+
};
|
|
47
|
+
const KIND = z.enum(["element", "instruction", "lesson"]);
|
|
48
|
+
export function registerApplicationTools(server, client) {
|
|
49
|
+
server.tool("read_application_executors", "Which executors (machines) an application runs on: `{ executorIds }`. " +
|
|
50
|
+
"Read it before set_application_executors, which replaces the whole " +
|
|
51
|
+
`set.\n\n${ADMIN_ONLY}`, { id: APPLICATION_ID }, async ({ id }) => guard(async () => jsonResult(await client.get(`${APPLICATIONS}/${id}/executors`))));
|
|
52
|
+
server.tool("set_application_executors", "Choose the executors (machines) an application runs on. REPLACES THE " +
|
|
53
|
+
"WHOLE SET: send every executor it should run on, not only the one you " +
|
|
54
|
+
"add — read_application_executors first. An empty list runs it on none. " +
|
|
55
|
+
"Executor ids come from list_executors. An executor receives the " +
|
|
56
|
+
"application's PUBLISHED limits, so a never-published application " +
|
|
57
|
+
"gives it nothing yet." +
|
|
58
|
+
`\n\n${ADMIN_ONLY}`, {
|
|
59
|
+
id: APPLICATION_ID,
|
|
60
|
+
executorIds: z
|
|
61
|
+
.array(z.string())
|
|
62
|
+
.max(50)
|
|
63
|
+
.describe("Every executor this application should run on, by id."),
|
|
64
|
+
}, async ({ id, executorIds }) => guard(async () => jsonResult(await client.put(`${APPLICATIONS}/${id}/executors`, { executorIds }))));
|
|
65
|
+
server.tool("list_executors", "List the workspace's executors — the programs on a customer's machine " +
|
|
66
|
+
"that carry out what an agent asks of an application. Read-only. Each " +
|
|
67
|
+
"has `status` (online/offline), `lastSeenAt`, what the machine reported " +
|
|
68
|
+
"(`reported`) and `limitsConfirmed`, the published limits version it " +
|
|
69
|
+
"confirmed per application. Never a token: `tokenPrefix` only " +
|
|
70
|
+
"recognises it. Registering an executor or revoking one is a human's " +
|
|
71
|
+
"job in Axonity, not a tool here.", {}, async () => guard(async () => jsonResult(await client.get(EXECUTORS))));
|
|
72
|
+
server.tool("read_executor", "Read one executor: its status, when it was last seen, what it reported " +
|
|
73
|
+
"and which published limits it confirmed per application. Read-only, " +
|
|
74
|
+
"and never a token.", { id: EXECUTOR_ID }, async ({ id }) => guard(async () => jsonResult(await client.get(`${EXECUTORS}/${id}`))));
|
|
75
|
+
server.tool("read_executor_applications", "The other side of read_application_executors: which applications one " +
|
|
76
|
+
"executor may operate, `{ applicationIds }`. To change the link, use " +
|
|
77
|
+
`set_application_executors on the application.\n\n${ADMIN_ONLY}`, { id: EXECUTOR_ID }, async ({ id }) => guard(async () => jsonResult(await client.get(`${EXECUTORS}/${id}/applications`))));
|
|
78
|
+
server.tool("list_executor_commands", "Every command an executor can carry out — click, type, read a screen, " +
|
|
79
|
+
"open an address, … — with its `kind` (`read` or `navigate`), its " +
|
|
80
|
+
"arguments (`inputSchema`) and its default timeout. Read-only; the " +
|
|
81
|
+
"platform's fixed catalogue, the same for every workspace. Use it to " +
|
|
82
|
+
"write an instruction's steps with actions that exist.", {}, async () => guard(async () => jsonResult(await client.get(`${EXECUTORS}/commands`))));
|
|
83
|
+
server.tool("list_application_knowledge", "What an application has learned about itself and uses now: `element` " +
|
|
84
|
+
"(a control on screen and what it is for), `instruction` (how to do " +
|
|
85
|
+
"something, as steps) and `lesson` (a short rule learned from a run). " +
|
|
86
|
+
"Each entry has `id`, `kind`, `key`, `version`, `content`, `source`, " +
|
|
87
|
+
"`status` and how often it `worked`. Read it before forget or write, " +
|
|
88
|
+
"and when an agent keeps making the same mistake in an application." +
|
|
89
|
+
`\n\n${PAGE_NOTE}`, {
|
|
90
|
+
id: APPLICATION_ID,
|
|
91
|
+
kind: KIND.optional().describe("Only entries of this kind. Omit for all three."),
|
|
92
|
+
...PAGING,
|
|
93
|
+
}, async ({ id, kind, limit, cursor }) => guard(async () => jsonResult(await client.get(`${APPLICATIONS}/${id}/knowledge`, { kind, limit, cursor }))));
|
|
94
|
+
server.tool("list_application_knowledge_versions", "Every version of one knowledge entry, newest first — what it said " +
|
|
95
|
+
"before it was corrected or forgotten. Pick the one to bring back with " +
|
|
96
|
+
`restore_application_knowledge.\n\n${PAGE_NOTE}`, {
|
|
97
|
+
id: APPLICATION_ID,
|
|
98
|
+
knowledgeId: z.string().describe("The entry's id, from list_application_knowledge."),
|
|
99
|
+
...PAGING,
|
|
100
|
+
}, async ({ id, knowledgeId, limit, cursor }) => guard(async () => jsonResult(await client.get(`${APPLICATIONS}/${id}/knowledge/${knowledgeId}/versions`, {
|
|
101
|
+
limit,
|
|
102
|
+
cursor,
|
|
103
|
+
}))));
|
|
104
|
+
server.tool("write_application_knowledge", "Write or correct one entry of what an application knows, as a designer " +
|
|
105
|
+
"would. Always a NEW version; the old one is kept and can be restored. " +
|
|
106
|
+
"What the entry is about is its element's `label`, its `instruction` " +
|
|
107
|
+
"or its lesson's `text` (case and spacing ignored): write the same one " +
|
|
108
|
+
"again to correct it, a different one to add an entry. `entry` " +
|
|
109
|
+
"takes the shape of its kind (camelCase): " +
|
|
110
|
+
"\n- `element`: `{ label, purpose, kind?, place? }`; " +
|
|
111
|
+
"\n- `instruction`: `{ instruction, steps, proof?, startsFrom? }`, each " +
|
|
112
|
+
"step `{ action, target?, value?, toTarget?, options? }` with an action " +
|
|
113
|
+
"from list_executor_commands, and `proof` `{ window?, text? }` — what " +
|
|
114
|
+
"is on screen when it worked; " +
|
|
115
|
+
"\n- `lesson`: `{ text }`, at most 500 characters. " +
|
|
116
|
+
"\nNever put a secret's value in an entry: a step types a secret with " +
|
|
117
|
+
"`type_secret` and the secret's NAME." +
|
|
118
|
+
`\n\n${ADMIN_ONLY}`, {
|
|
119
|
+
id: APPLICATION_ID,
|
|
120
|
+
kind: KIND.describe("What kind of entry this is."),
|
|
121
|
+
entry: z
|
|
122
|
+
.record(z.unknown())
|
|
123
|
+
.describe("The entry itself, in the shape of its kind (see above)."),
|
|
124
|
+
}, async ({ id, kind, entry }) => guard(async () => jsonResult(await client.post(`${APPLICATIONS}/${id}/knowledge`, { kind, [kind]: entry }))));
|
|
125
|
+
server.tool("forget_application_knowledge", "Stop the application using one entry — a wrong element, instruction or " +
|
|
126
|
+
"lesson. Not a delete: it is kept, marked forgotten, with its versions, " +
|
|
127
|
+
"and restore_application_knowledge brings it back. Only the current " +
|
|
128
|
+
"version of an entry can be forgotten." +
|
|
129
|
+
`\n\n${ADMIN_ONLY}`, {
|
|
130
|
+
id: APPLICATION_ID,
|
|
131
|
+
knowledgeId: z.string().describe("The entry's id, from list_application_knowledge."),
|
|
132
|
+
}, async ({ id, knowledgeId }) => guard(async () => jsonResult(await client.post(`${APPLICATIONS}/${id}/knowledge/${knowledgeId}/forget`))));
|
|
133
|
+
server.tool("restore_application_knowledge", "Make a forgotten entry, or an older version of one, current again — as " +
|
|
134
|
+
"a new version, so nothing is overwritten. Pass the id of the version " +
|
|
135
|
+
"you want back (from list_application_knowledge_versions), or the " +
|
|
136
|
+
"forgotten entry's id to undo forget_application_knowledge. The " +
|
|
137
|
+
"current version cannot be restored — it already is current." +
|
|
138
|
+
`\n\n${ADMIN_ONLY}`, {
|
|
139
|
+
id: APPLICATION_ID,
|
|
140
|
+
knowledgeId: z
|
|
141
|
+
.string()
|
|
142
|
+
.describe("The id of the version to bring back, or of the forgotten entry."),
|
|
143
|
+
}, async ({ id, knowledgeId }) => guard(async () => jsonResult(await client.post(`${APPLICATIONS}/${id}/knowledge/${knowledgeId}/restore`))));
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=applications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applications.js","sourceRoot":"","sources":["../../src/tools/applications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,YAAY,GAAG,sBAAsB,CAAC;AAC5C,MAAM,SAAS,GAAG,mBAAmB,CAAC;AAEtC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC;AACpE,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;AAE9D,MAAM,UAAU,GACd,2EAA2E;IAC3E,2EAA2E;IAC3E,sEAAsE;IACtE,gCAAgC,CAAC;AAEnC,MAAM,SAAS,GACb,4EAA4E;IAC5E,0EAA0E;IAC1E,gDAAgD,CAAC;AAEnD,MAAM,MAAM,GAAG;IACb,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,QAAQ,CAAC,mDAAmD,CAAC;IAChE,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,sEAAsE;QACpE,0BAA0B,CAC7B;CACJ,CAAC;AAEF,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE1D,MAAM,UAAU,wBAAwB,CACtC,MAAiB,EACjB,MAAqB;IAErB,MAAM,CAAC,IAAI,CACT,4BAA4B,EAC5B,wEAAwE;QACtE,qEAAqE;QACrE,WAAW,UAAU,EAAE,EACzB,EAAE,EAAE,EAAE,cAAc,EAAE,EACtB,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CACf,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CACrF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,2BAA2B,EAC3B,uEAAuE;QACrE,wEAAwE;QACxE,yEAAyE;QACzE,kEAAkE;QAClE,mEAAmE;QACnE,uBAAuB;QACvB,OAAO,UAAU,EAAE,EACrB;QACE,EAAE,EAAE,cAAc;QAClB,WAAW,EAAE,CAAC;aACX,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,GAAG,CAAC,EAAE,CAAC;aACP,QAAQ,CAAC,uDAAuD,CAAC;KACrE,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAC5B,KAAK,CAAC,KAAK,IAAI,EAAE,CACf,UAAU,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,IAAI,EAAE,YAAY,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,CACjF,CACJ,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,gBAAgB,EAChB,wEAAwE;QACtE,uEAAuE;QACvE,yEAAyE;QACzE,sEAAsE;QACtE,+DAA+D;QAC/D,sEAAsE;QACtE,kCAAkC,EACpC,EAAE,EACF,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CACvE,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,eAAe,EACf,yEAAyE;QACvE,sEAAsE;QACtE,oBAAoB,EACtB,EAAE,EAAE,EAAE,WAAW,EAAE,EACnB,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CACxF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,4BAA4B,EAC5B,uEAAuE;QACrE,sEAAsE;QACtE,oDAAoD,UAAU,EAAE,EAClE,EAAE,EAAE,EAAE,WAAW,EAAE,EACnB,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CACf,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,CACrF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,wEAAwE;QACtE,mEAAmE;QACnE,oEAAoE;QACpE,sEAAsE;QACtE,uDAAuD,EACzD,EAAE,EACF,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,WAAW,CAAC,CAAC,CAAC,CACrF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,4BAA4B,EAC5B,uEAAuE;QACrE,qEAAqE;QACrE,uEAAuE;QACvE,sEAAsE;QACtE,sEAAsE;QACtE,oEAAoE;QACpE,OAAO,SAAS,EAAE,EACpB;QACE,EAAE,EAAE,cAAc;QAClB,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;QAChF,GAAG,MAAM;KACV,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CACpC,KAAK,CAAC,KAAK,IAAI,EAAE,CACf,UAAU,CACR,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,IAAI,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAC7E,CACF,CACJ,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,qCAAqC,EACrC,oEAAoE;QAClE,wEAAwE;QACxE,qCAAqC,SAAS,EAAE,EAClD;QACE,EAAE,EAAE,cAAc;QAClB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kDAAkD,CAAC;QACpF,GAAG,MAAM;KACV,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAC3C,KAAK,CAAC,KAAK,IAAI,EAAE,CACf,UAAU,CACR,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,IAAI,EAAE,cAAc,WAAW,WAAW,EAAE;QAC1E,KAAK;QACL,MAAM;KACP,CAAC,CACH,CACF,CACJ,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,6BAA6B,EAC7B,yEAAyE;QACvE,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,gEAAgE;QAChE,2CAA2C;QAC3C,sDAAsD;QACtD,yEAAyE;QACzE,yEAAyE;QACzE,uEAAuE;QACvE,+BAA+B;QAC/B,oDAAoD;QACpD,uEAAuE;QACvE,sCAAsC;QACtC,OAAO,UAAU,EAAE,EACrB;QACE,EAAE,EAAE,cAAc;QAClB,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,6BAA6B,CAAC;QAClD,KAAK,EAAE,CAAC;aACL,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;aACnB,QAAQ,CAAC,yDAAyD,CAAC;KACvE,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAC5B,KAAK,CAAC,KAAK,IAAI,EAAE,CACf,UAAU,CACR,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,IAAI,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAC9E,CACF,CACJ,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,8BAA8B,EAC9B,yEAAyE;QACvE,yEAAyE;QACzE,qEAAqE;QACrE,uCAAuC;QACvC,OAAO,UAAU,EAAE,EACrB;QACE,EAAE,EAAE,cAAc;QAClB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kDAAkD,CAAC;KACrF,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAC5B,KAAK,CAAC,KAAK,IAAI,EAAE,CACf,UAAU,CACR,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,IAAI,EAAE,cAAc,WAAW,SAAS,CAAC,CAC3E,CACF,CACJ,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,+BAA+B,EAC/B,yEAAyE;QACvE,uEAAuE;QACvE,mEAAmE;QACnE,iEAAiE;QACjE,6DAA6D;QAC7D,OAAO,UAAU,EAAE,EACrB;QACE,EAAE,EAAE,cAAc;QAClB,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,CAAC,iEAAiE,CAAC;KAC/E,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAC5B,KAAK,CAAC,KAAK,IAAI,EAAE,CACf,UAAU,CACR,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,IAAI,EAAE,cAAc,WAAW,UAAU,CAAC,CAC5E,CACF,CACJ,CAAC;AACJ,CAAC"}
|
package/dist/tools/concepts.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { z } from "zod";
|
|
26
26
|
import { guard, jsonResult } from "./result.js";
|
|
27
27
|
const BASE = "/api/v1/concepts";
|
|
28
|
+
const LINKS = "/api/v1/concept-links";
|
|
28
29
|
const KIND = z
|
|
29
30
|
.enum(["concept", "state", "metric", "rule"])
|
|
30
31
|
.describe("`concept`: one of the company's own words and what it means here. " +
|
|
@@ -48,10 +49,13 @@ const SYNONYMS = z
|
|
|
48
49
|
"the workspace.");
|
|
49
50
|
const REFERS_TO = z
|
|
50
51
|
.string()
|
|
51
|
-
.describe("What in the model this is about, in one of
|
|
52
|
+
.describe("What in the model this is about, in one of five forms: " +
|
|
52
53
|
"`measure:active_clients`, `table:orders_monthly`, " +
|
|
53
|
-
"`column:orders_monthly.country`, `value:orders_monthly.status=lost
|
|
54
|
-
"
|
|
54
|
+
"`column:orders_monthly.country`, `value:orders_monthly.status=lost`, " +
|
|
55
|
+
"`query:plan_revenue` (or one of its columns, " +
|
|
56
|
+
"`query:plan_revenue.amount`). It must exist. Empty for a word that is " +
|
|
57
|
+
"not in the model yet. ONE thing only — to hang the word on more, " +
|
|
58
|
+
"use link_concept.");
|
|
55
59
|
const OWNER = z
|
|
56
60
|
.string()
|
|
57
61
|
.describe('Who to ask when this definition is disputed — a role or a person, "CGO". ' +
|
|
@@ -118,5 +122,66 @@ export function registerConceptTools(server, client) {
|
|
|
118
122
|
.literal(true)
|
|
119
123
|
.describe("Must be true. Acknowledges the definition is removed from the draft."),
|
|
120
124
|
}, async ({ id }) => guard(async () => jsonResult(await client.del(`${BASE}/${id}`))));
|
|
125
|
+
// ---- links: one word, everything it means (axonity-mcp#92) ------------
|
|
126
|
+
// A concept's `refersTo` points at ONE thing. A word like "net revenue"
|
|
127
|
+
// lives in the revenue column of three tables; the links are what let it
|
|
128
|
+
// reach all three, so the word is made once instead of three times that
|
|
129
|
+
// drift apart. They are rows of a second table the platform keeps, with the
|
|
130
|
+
// same lifecycle as the concepts' own.
|
|
131
|
+
server.tool("list_concept_links", "Which things a word is hung on, or which words are hung on a thing. " +
|
|
132
|
+
"Read-only. Pass `concept` for everything one word reaches, " +
|
|
133
|
+
"`refersTo` for every word on one table, column, measure, value or " +
|
|
134
|
+
"query — or neither for every link in the workspace. A thing with no " +
|
|
135
|
+
"word answers an empty list.", {
|
|
136
|
+
concept: z
|
|
137
|
+
.string()
|
|
138
|
+
.optional()
|
|
139
|
+
.describe("Only the links of this word — the concept's `name`."),
|
|
140
|
+
refersTo: z
|
|
141
|
+
.string()
|
|
142
|
+
.optional()
|
|
143
|
+
.describe("Only the words hung on this, in the same forms as a concept's " +
|
|
144
|
+
"refersTo (`column:orders_monthly.net_revenue`)."),
|
|
145
|
+
}, async ({ concept, refersTo }) => guard(async () => jsonResult(await client.get(LINKS, { concept, refersTo }))));
|
|
146
|
+
server.tool("link_concept", "Hang a word on one more thing it means: a column, a table, a measure, " +
|
|
147
|
+
"a value or a query. A concept's own `refersTo` points at one thing; " +
|
|
148
|
+
"a link is how one word reaches EVERY column that means it, so a " +
|
|
149
|
+
"question in the workspace's own words finds the right data wherever " +
|
|
150
|
+
"it lives. Make the word once and link it, never a second concept " +
|
|
151
|
+
"for the same word. " +
|
|
152
|
+
"\n\nThe word must be a concept of this workspace and the thing must " +
|
|
153
|
+
"exist; each is refused by name otherwise. Linking the same word to " +
|
|
154
|
+
"the same thing twice is one link — the second call changes nothing. " +
|
|
155
|
+
"The links table is created with the first link. " +
|
|
156
|
+
"\n\nNOT LIVE UNTIL PUBLISHED. The links live in a table of their own " +
|
|
157
|
+
"named `concept_links`: find its id with list_data_tables({ name: " +
|
|
158
|
+
"\"concept_links\" }) and request_publish_data_table on it — one " +
|
|
159
|
+
"approval for every link you wrote.", {
|
|
160
|
+
concept: z
|
|
161
|
+
.string()
|
|
162
|
+
.describe("The word — the concept's `name`, as list_concepts gives it."),
|
|
163
|
+
refersTo: z
|
|
164
|
+
.string()
|
|
165
|
+
.describe("What it means here, in one of five forms: " +
|
|
166
|
+
"`column:orders_monthly.net_revenue`, `table:orders_monthly`, " +
|
|
167
|
+
"`measure:net_revenue`, `value:orders_monthly.status=lost`, " +
|
|
168
|
+
"`query:plan_revenue` or `query:plan_revenue.amount`."),
|
|
169
|
+
note: z
|
|
170
|
+
.string()
|
|
171
|
+
.optional()
|
|
172
|
+
.describe("Why THIS column is that word, when it is not obvious. Empty is " +
|
|
173
|
+
"normal: the meaning lives on the concept."),
|
|
174
|
+
}, async ({ concept, refersTo, note }) => guard(async () => jsonResult(await client.post(LINKS, { concept, refersTo, ...(note ? { note } : {}) }))));
|
|
175
|
+
server.tool("unlink_concept", "Take one link back: the word no longer reaches that thing. Answers " +
|
|
176
|
+
"with the links the word has left. Taking away a link that is not " +
|
|
177
|
+
"there is a success, not an error. The concept itself stays; " +
|
|
178
|
+
"delete_concept removes the word. Like a new link, the removal counts " +
|
|
179
|
+
"once the `concept_links` table is published again " +
|
|
180
|
+
"(request_publish_data_table).", {
|
|
181
|
+
concept: z.string().describe("The word — the concept's `name`."),
|
|
182
|
+
refersTo: z
|
|
183
|
+
.string()
|
|
184
|
+
.describe("What it was hung on, exactly as list_concept_links shows it."),
|
|
185
|
+
}, async ({ concept, refersTo }) => guard(async () => jsonResult(await client.del(LINKS, { concept, refersTo }))));
|
|
121
186
|
}
|
|
122
187
|
//# sourceMappingURL=concepts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"concepts.js","sourceRoot":"","sources":["../../src/tools/concepts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,IAAI,GAAG,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"concepts.js","sourceRoot":"","sources":["../../src/tools/concepts.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
*/
|
|
49
49
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
50
50
|
import type { AxonityClient } from "../client.js";
|
|
51
|
-
export declare const CONVENTIONS = "# Authoring conventions for Axonity entities\n\nRead this before creating or updating anything.\n\n## What this connector is for\n- AUTHORING: composing an entity from intent \u2014 drafting a tool, an agent, a\n workflow, and wiring them together so the result is CORRECT and COMPLETE\n (every required field set, every reference pointing at something real).\n- REPRODUCING a setup is just authoring it again, from scratch. To recreate an\n agent/workflow/tool, CREATE NEW entities and wire them by their NEW ids \u2014\n never reuse an id you read. There is no \"migration\" and no \"import an id\":\n this connector only ever acts in the ONE tenant its token points at (see\n Tenant), and ids are tenant-local, so an id from anywhere else points at\n nothing (or the wrong thing) here. Read the source for its SHAPE and intent \u2014\n names, field values, which tools an agent uses, which agents a workflow's\n steps run \u2014 then build fresh and capture the ids the create calls return.\n Recreating into a DIFFERENT tenant means running that build while the\n connector is configured with THAT tenant's token \u2014 a separate session. See\n \"Reproducing a setup\" below.\n\n## The authoring loop\n1. `read_*` the entity (and `read_*_published` if you need to see what is live).\n2. Edit the DRAFT: `update_*`, or workflow-edit tools for workflow structure.\n3. Validate before publish: `validate_workflow` / `analyze_workflow_reachable_outputs` / \n `validate_tool_code` / `format_tool_code`. These four are stateless and work with\n read-only tokens.\n4. Re-read and verify with a saved tool command: `execute_tool` /\n `execute_stored_connector`.\n5. Fix any issues and re-read again.\n6. For a code tool, PROVE it: `dry_run_tool` \u2014 see \"Proving a tool\" below. Skip\n this and `request_publish_tool` will sit blocked forever.\n7. `request_publish_*` \u2014 this only QUEUES a human approval. Publishing many\n entities at once (taking a tenant live) is `request_publish_bulk`, up to 200\n in one call; it is not transactional, so read the per-entry results.\n8. `list_publish_approvals` / `get_publish_approval` to see whether it was\n approved or rejected. Readiness there is recomputed as you read it\n (`readiness` now, `readinessAtRequest` then, `readinessChanged` if it\n moved), so a blocker you saw earlier may already be gone.\n\n9. If something went wrong:\n - Bad edit, entity still exists \u2192 `list_*_versions` then `restore_*_version`\n to roll the draft back to an earlier version.\n - Never published, want a clean slate \u2192 `discard_*_draft` resets the draft to\n the last published state (fails if it was never published \u2014 there's nothing\n to fall back to).\n - Deleted by mistake \u2192 `restore_*` (see \"Delete is recoverable\" below).\n\n## Proving a tool (the publish gate)\nBefore a code tool may be published, the platform requires that its stored code\nhas run cleanly once, at its current version.\n- `dry_run_tool(toolId)` is the run that counts. It executes the tool's OWN\n saved implementation; you supply only sample input. A clean pass clears the\n `dry_run_required` blocker.\n- `execute_tool` does NOT count, however green it comes back. It runs the\n functions YOU pass, which need not be what is stored \u2014 proving nothing about\n what would ship. Use it while drafting code you have not saved yet.\n- Editing the tool afterwards bumps its version and invalidates the proof, so\n dry-run AFTER your last edit.\n- Connectors, builtin-backed validators and platform-shipped tools have no code\n of their own to prove and will refuse (422/403). That is expected, not a\n failure to work around.\n\n## Drafts, not live\n- `create_*` and `update_*` produce a DRAFT. The live runtime is unaffected\n until a human publishes it in Axonity. `flow` has the same rule: runtime uses\n the published snapshot from `read_flow_published` until `request_publish_flow`\n is approved.\n- You cannot publish directly \u2014 the backend refuses a direct publish from a service\n token with a 403, so there is no tool for it and no way around it.\n- A read-only token can only read: any write returns 403. If you see that, ask\n your human for a write-scoped token rather than retrying.\n- `request_publish_*` does NOT publish \u2014 it creates a pending approval a human\n approves in Axonity. Use it when a draft is ready; then tell the user to\n approve it in their Axonity approvals inbox.\n\n## Optimistic locking (always read-then-write)\n- Before `update_*`, call `read_*` and use the `version` you get as\n `expectedVersion` \u2014 in the request BODY. If the update returns a 409\n conflict, someone else changed it \u2014 read again and re-apply your change onto\n the new version.\n- `delete_*` also takes `expectedVersion`, but as an argument on the tool call\n only \u2014 the connector maps it to whatever query-parameter spelling that\n entity's route actually expects (it differs per entity on the wire; you never\n need to know which).\n- `restore_*` and `restore_deleted_*_version` take NO version at all \u2014 a\n restore must never lose to a stale-version guard.\n\n## Errors: branch on the failure, not the words\n- Every error names what went wrong as plainly as the backend allows. For a 409\n specifically, two different problems share that status code and need\n different responses:\n - **Version conflict** \u2014 you had a stale read. Re-read, re-apply, retry.\n - **Reference conflict** \u2014 the entity is referenced by something published or\n live. Retrying will never help. Detach the reference or unpublish the\n referencing entity first.\n The error message tells you which one you hit \u2014 read it, don't assume 409\n always means \"retry.\"\n- A 403 on any write means the token is read-only, or (for a direct publish\n attempt, which no tool makes) that publishing must go through\n `request_publish_*`. Either way: stop, don't retry, tell your human.\n- A 401 means the service token is dead \u2014 missing, wrong, expired, or revoked.\n **The backend deliberately returns the identical message for all four\n causes** so it doesn't leak which applies; you have no way to tell them\n apart and no way to check a token's remaining lifetime in advance (every\n token now expires \u2014 7, 15, 30, 60 or 90 days, chosen when it was minted;\n there is no \"never\"). On any 401: stop, do not retry, and tell your human\n plainly that the configured token is no longer valid and a new one must be\n minted from Axonity \u2192 Settings \u2192 API tokens. Do not guess or imply the cause.\n\n## Field shape\n- All fields are camelCase JSON (e.g. `capabilityTier`, not `capability_tier`).\n ONE documented exception: validator specs (`permissions.outputValidators`,\n `permissions.exposedValidatorTools`) use snake_case keys (`function`,\n `target`, `args`, `when_present`, `inputs`, `error_message`,\n `tool_name`, `validator_name`) \u2014 they are the same shape at every level and\n are stored verbatim, so they are NOT camel-aliased. Send them in snake_case.\n What those keys MEAN, and how a validator is bound to what it judges, is the\n Validators section.\n- `update_*` is a partial change for agents/tools and a partial patch for\n workflows: send only the fields you are changing (plus `expectedVersion`).\n- The backend validates fields; a 422 error names the offending field and why\n (e.g. `body.capabilityTier: Input should be 'economy', 'standard', 'smart' or\n 'reasoning'`). Fix that field and retry \u2014 do not guess at the whole shape.\n- **create/update for tool and agent are STRICT** (`extra=\"forbid\"`): an\n undeclared OR MISSPELLED key is a 422 that names it, not a silent drop. So\n spelling matters and you get told when it is wrong \u2014 good. Other entities are\n more lenient and may drop an unknown key silently; never rely on that.\n- `agent.tags` is a free label for grouping and reporting. It GATES NOTHING \u2014\n what reaches an agent's prompt is a LINK (`attach_policy_to_agent`), visible\n in a table a human can read and edit. A role-tag gate on a policy used to\n decide that invisibly and is refused outright now: sending\n `activation.tags` to `create_policy`/`update_policy` is a 422. The two\n surviving gates, `activation.tools` and `activation.delegation`, each say\n something a link cannot \u2014 \"only on turns where this agent holds this tool\",\n and \"only when (not) delegated\".\n\n## After you write, verify\n- A 200 means the request was accepted, NOT that it did what you meant. Re-read\n the entity after a mutation and diff it against your intent \u2014 a partial update\n only changes what you sent, and on the lenient (non-tool/agent) entities an\n unknown key can be dropped silently rather than rejected.\n- Re-reading the ENTITY is not enough for an `attach_*`: those links are stored\n outside the entity body and never appear in `read_agent`. Read them back with\n `list_agent_skills` / `list_agent_policies` / `list_agent_reference_docs`\n (see Wiring, mechanism B).\n- \"Complete\" is your job, not the backend's. A create succeeds with only the\n required fields set; it does not tell you the entity is USABLE. An agent with\n no `toolIds` runs with no tools; a connector tool with an empty\n `implementation` has no endpoint to call; a workflow with steps but no\n trigger never fires. After creating, check the entity has everything its\n purpose needs \u2014 see the per-entity and wiring sections.\n- To test a whole workflow end to end, `start_workflow_run` then read the result\n (`read_run` / `read_run_trace` / `read_run_cost`). Note it runs the PUBLISHED\n workflow and really executes (cost, connector side effects), so publish first,\n and only test-run when a human is expecting it.\n - Read `read_workflow_trigger_parameters` first and pass `triggerId`: a\n workflow can have several starts, each with different fields, and omitting\n it fires whichever comes first. Never send a parameter marked `pinned` \u2014\n the author owns that value and it is overwritten anyway.\n - **A run can park instead of finishing.** A step that asks a question waits\n for an answer, so \"it never came back\" usually means \"it is waiting on you\".\n `read_run_waiting_on` says what for; `answer_run_question` (or\n `send_run_message` on a conversation run) resumes it. Answer your own test\n runs; on a run a human is depending on, the answer is theirs to give.\n - A FOR EACH launch is not one result. `read_run_items_summary` is the\n roll-up (\"4,415 processed \u00B7 12 failed\") and `list_run_items` the rows \u2014\n with `outcome: \"failed\"`, because the failures are scattered and filtering\n page one finds none of them.\n- A LIST is not the whole list. `list_workflow_runs` returns a page \u2014\n `{ items, nextCursor, pageSize, hasMore }`, 20 by default \u2014 so counting or\n concluding from page one while `hasMore` is true is how you answer confidently\n and wrongly. Follow `nextCursor` until it is null. Two further traps: that\n route lists LAUNCHES (a 4,415-item FOR EACH is ONE entry carrying\n `forEachProgress`, so rows are not runs), and the offset-style lists\n (`list_runs`, `list_publish_approvals`) cap silently \u2014 a full page means\n there may be more, and nothing in the response says so.\n\n## Delete is recoverable \u2014 but not everything is\n- `delete_*` soft-deletes: the entity disappears from `list_*` but is not gone.\n `restore_*` brings it back; `list_deleted_*` shows what's waiting. This\n applies uniformly to workflow, agent, tool, skill, policy, reference_doc,\n persona, output_schema, prompt_snippet, and flow.\n- The same applies one level down, to version HISTORY:\n `delete_*_version` removes one entry from history (the current draft and\n published version are protected and can't be targeted this way);\n `list_deleted_*_versions` / `restore_deleted_*_version` undo it. Deleted\n version numbers stay reserved rather than reused, so gaps in the numbering\n are normal, not a sign of corruption.\n- `restore_*` can itself 409 if a live entity now holds the same name/slot \u2014\n the error names the conflicting one.\n- There is no \"archive\" state for these ten entities \u2014 delete + restore IS the\n lifecycle. Runs are the one exception with a true archive/unarchive (see below).\n\n## Per entity \u2014 what to create, and what \"complete\" means\n\nBuild in dependency order: a thing you will REFERENCE must exist before the\nthing that references it. So: tools and output-schemas first, then agents (which\nlist tool ids), then workflows (whose steps name agents and tools), then\ntriggers (which make a workflow fire). Skills/policies/reference-docs can be\nmade any time before you attach them.\n\n### tool (`create_tool`, strict body)\n- Required: `name`. Also: `description`, `type`, `inputSchema`,\n `outputSchema`, `implementation`, `authConfig`, `surfacing`.\n- `type`: `\"function\"` (default) | `\"connector\"` | `\"validator\"` |\n `\"evaluator\"`. `surfacing`: `\"always\"` (in the agent's tool list) |\n `\"on_demand\"`.\n- `inputSchema` / `outputSchema` are JSON-Schema objects describing the tool's\n params and result. Set them \u2014 an agent binds to a tool by its declared I/O; a\n tool with no inputSchema is hard to call correctly.\n- `implementation` is what makes the tool DO something, and its shape depends on\n `type`:\n - **function** \u2014 carries the Python. Author and check the code with\n `validate_tool_code` and `format_tool_code`, and prove it runs with\n `execute_tool`, BEFORE `create_tool`. A function tool whose\n `implementation` is empty is an inert stub.\n - **connector** \u2014 carries the HTTP call: typically `connectorType`\n (\"rest\"/\"graphql\"), `baseUrl`, `method`, `path`, `headers`,\n `queryParams`, `body`, `responseMapping`, `pagination`. Prefer the\n `create_connector` tool, which sets `type:\"connector\"` for you. NEVER put\n a real credential in `authConfig` \u2014 placeholders only (\"{{ MY_SECRET }}\"\n or \"\"); a human fills the secret in Axonity. Test with\n `execute_stored_connector` (uses the real, server-side-decrypted secret;\n you see only the result).\n - **validator / evaluator** \u2014 a constrained kind, and `implementation` takes\n ONE OF TWO shapes. Either `{\"builtin\": \"validators.<name>\"}`, naming a\n house predicate \u2014 look at the tenant's existing validator/evaluator tools\n (`list_tools`) for the keys in use rather than inventing one. Or authored\n Python, like a function tool, whose `run` returns a VERDICT: `True` /\n `False` / a fail-detail object. A `run` that returns a string, a number\n or nothing at all declares no verdict and is REFUSED on save, so that a\n misconfigured gate cannot silently pass everything at run time. Authored\n code takes the standard call \u2014 see Validators.\n- Complete check: the tool has the I/O schemas its callers need AND a non-empty\n implementation of the right shape for its type.\n- `toolboxId` files the tool in a toolbox on creation. Read `list_toolboxes`\n BEFORE you create: a tool made without one is ungrouped, which is valid and\n is also how nobody finds it. See Toolboxes below.\n\n### agent (`create_agent`, strict body)\n- Required: `name`. Enumerated fields, with their ONLY valid values:\n - `capabilityTier`: `economy` | `standard` (default) | `smart` | `reasoning`\n - `creativityTier`: `low` | `medium` (default) | `high`\n - `learningMode`: `none` (default) | `adaptive` | `strict`\n- `avatar`, `tags` (free labels for grouping \u2014 they gate nothing; see Field\n shape), `canOwnProcess` (bool; must be true for an agent to own a\n workflow step's flow), `episodicMemoryEnabled` (bool).\n- `permissions` (object): `planning`, `delegation`, `verboseOutput` (bools);\n `outputValidators` / `exposedValidatorTools` (snake_case specs \u2014 see Field\n shape for the casing, Validators for what they say);\n `expectedOutputSchemaId` (an output_schema id \u2014 MANDATORY whenever you\n attach validators here).\n- Wiring fields \u2014 see the Wiring section: `toolIds`, `systemToolIds`,\n `delegationTargetIds`.\n- Complete check: an agent with no `toolIds`/`systemToolIds` has no tools and\n can only reason/talk. If it is meant to DO something, give it the tools.\n\n### workflow (`create_workflow`, then edit the document)\n- `create_workflow` makes a STUB (name, description) with an empty document.\n You then build the graph with either\n - `apply_workflow_mutations` \u2014 a LIST of command mutations applied in order\n (preferred: the backend validates each and threads the version), or\n - `replace_workflow_document` \u2014 a full-document atomic PUT.\n- The document is a graph: `steps.all[]` (each `{ id, type, name, ... }`) and\n `steps.edges[]` (each `{ id, from, to, type? }`) plus `triggers`.\n- WHICH VALUES a step's `type` and a trigger's `typeId` may hold is answered\n by `get_workflow_authoring_spec`, read live from this deploy. This guide\n does not list them, on purpose: it used to name nine step types where seven\n validate, and a list nobody could read is a list that goes wrong. The spec\n also lists the types you may NOT author, each with the reason and how to\n express that intention instead \u2014 so reaching for one gets you an answer\n rather than a `step_invalid_type`.\n- Referential integrity is enforced: step and trigger ids must be unique, and\n every edge `from`/`to` must reference an id that exists in the document\n (`from` may be a trigger or step; `to` must be a step). A dangling edge is a\n hard error.\n- Complete check: run `validate_workflow` on the document \u2014 it returns\n `launchable` plus an `issues` list. A workflow is not done until it is\n launchable AND has a trigger (see Triggers).\n\n### persona\n- An agent's character \u2014 1:1 with an agent. Created only through its agent\n (`create_agent_persona`: `name`, `characterText`); then a normal entity\n (`read_persona`, `update_persona`, delete/restore/versions).\n\n### output_schema\n- A reusable output contract, versioned and publishable. Referenced BY id from a\n tool's validators or an agent's `permissions.expectedOutputSchemaId` \u2014 so\n create it first, then point things at the id the create call returns.\n- `schemaBody` is a JSON Schema document (`type`, `properties`,\n `required`) PLUS one key that is not JSON Schema: `validators`, the gates\n run against a submitted output. This is the place a validator can be STEERED\n at part of the output \u2014 see Validators.\n\n### skill / policy / reference_doc\n- Memory entities attached to agents (and skills also to workflows). Create them,\n then wire with the `attach_*` tools \u2014 see Wiring.\n- A reference doc has NO kind any more. What used to be a semantic note lives\n in the model itself: a word is a concept (`create_concept`), a table card\n is the table's description and grain, a source query is the table's\n `provenance`, an example question is a measure's `questions`. The\n platform refuses `semanticKind`, `semanticDetails`, `subjectTableId`\n and `subjectSourceId` on a reference doc, and says where each one went.\n Building the SEMANTIC MODEL? Read `axonity_semantic_conventions` first \u2014\n those rules are served by the platform; this guide does not restate them.\n\n### prompt_snippet\n- Reusable prompt fragments; a normal entity. Wire-level oddity:\n `list_deleted_prompt_snippets` returns the backend `items` envelope.\n\n### flow\n- A reusable workflow fragment in the authored-entity lifecycle. `create_flow` /\n `update_flow` write draft state; runtime uses `read_flow_published` until\n approval. `request_publish_flow` to publish, `discard_flow_draft` to reset.\n\n### data_table\n- An authored table of rows: the tenant's own reference data, designed and\n maintained by a person, read by agents and decisions. Same lifecycle as an\n output schema \u2014 draft, version, request publish. See \"Tables\" below, which is\n where the rules that are NOT the generic ones live.\n\n### company\n- The tenant's single company document (mission, value streams, org structure).\n A SINGLETON \u2014 one per tenant, no id, so there is no list/create/delete:\n `read_company`, `update_company` (a WHOLE-document save with\n `expectedVersion`, like `replace_workflow_document`), `list_company_versions`,\n `read_company_version`, `restore_company_version`, `read_company_published`.\n Publish it like anything else with `request_publish_company` \u2014 but because it\n is a singleton this takes NO id (the server resolves your tenant's one\n company). Direct company publish is closed to machine tokens, so the approval\n queue is the only path.\n\n## Validators \u2014 one shape, one call\nA validator is a GATE on a submitted output: it runs after an agent produces a\nresult and can refuse it. Specs are written in two places and carry the same\nkey names in both \u2014 `permissions.outputValidators` on an agent, and\n`validators` inside an output_schema's `schemaBody`. The backend's OpenAPI\ndescription of `schemaBody` is the source for the full contract; what follows\nis the part you cannot author without.\n\n- A spec names its check in ONE of two ways, and each brings its own binding:\n - `function` \u2014 a shipped predicate (e.g. `validators.min_length`), bound\n by `target` (the field it judges; omit it to judge the whole output) and\n `args`.\n - `tool` \u2014 a validator TOOL in this tenant's library, by name or id.\n Exactly one of the two. Naming both leaves it unsaid which one runs, naming\n neither leaves nothing to run, and a gate does not get to be ambiguous \u2014 both\n are refused. The binding keys do not mix either: `target`/`args` belong to\n the `function` form and are refused on a `tool` spec, because a key the\n runtime ignores is worse stored than rejected.\n- `error_message` is what the agent is told on refusal, and the one key both\n forms share. `{value}` in it is replaced by what the validator reported.\n- `when_present` runs the check only when the target field is actually in the\n payload. Off by default, deliberately: a blanket skip turns a mistyped\n `target` into a gate that passes everything. Set it where a field is\n genuinely optional.\n\n### The standard call\nEVERY authored validator is invoked the same way. The signature does not vary\nper tool, and that is the whole point: a screen cannot offer inputs it does not\nknow, and a spec served to an agent cannot describe a shape that differs per\ntool. Bespoke parameters push the contract into prose that goes stale.\n\n```\nrun(data, slots, binding)\n```\n\n- `data` \u2014 what the validator is pointed at, UNTOUCHED. The whole submitted\n output, or the field named by `field`.\n- `slots` \u2014 every declared session-memory reference found in `data`, with its\n text, indexed by the reference itself. The platform resolves them because code\n in the sandbox is never handed a lookup callable \u2014 a validator may be foreign\n code.\n- `binding` \u2014 what you declared, verbatim, so the validator knows which values\n in `data` are references to look up rather than text to read. Without it it\n would have to guess, and guessing is how a check reads a key as though it were\n the body.\n\nTwo keys on the spec steer that call: `field` names the part of the output to\nhand over (leave it out for the whole output), and `slots` lists the fields\nthat hold references.\n\n```json\n{ \"tool\": \"my.freshness.check\",\n \"field\": \"units\",\n \"slots\": [\"key\", \"source_key\"],\n \"error_message\": \"REJECTED: {value}\" }\n```\n\nBOTH ARE CHECKED WHEN YOU SAVE the output schema, and a refusal names the\nfields that do exist: a `field` that is not a property, or a `slots` name the\nrows do not carry. That check is there because either mistake fails SILENTLY at\nrun time \u2014 a name that is not there is never looked up, so the gate runs on\nunresolved values and nothing says so. (Rows whose schema declares no shape are\nleft alone: there is nothing to check against, and guessing would refuse correct\nconfigurations.)\n\nWHERE `field` AND `slots` ARE ACCEPTED: inside an output_schema's\n`schemaBody`, which is stored verbatim. An agent's\n`permissions.outputValidators` is a STRICT body \u2014 an undeclared key is a 422 \u2014\nso a spec written THERE takes neither, and its validator gets the whole output\nand no slots. Steer a validator from the output_schema.\n\n### The older, explicit binding\nA tool spec MAY declare `inputs` instead, and a spec with `inputs` does NOT\nget the standard call. That is the way for a validator tool backed by a shipped\npredicate, which receives `value` plus its own arguments \u2014 its `run` would\nnot accept `data`/`slots`/`binding`. Each entry is\n`{\"source\": \"field\"|\"const\", \"field\": <name>, \"as\": \"slot\"?}`; with\n`as: \"slot\"` the field's value is read as a reference and its TEXT is passed.\n`field` takes a path, so a binding can point INTO a list: `units[].key` is,\nfrom every row in `units`, the `key` field.\n\nWriting a NEW validator: take the standard call and leave `inputs` out.\n\n## Wiring \u2014 how the pieces connect\nThere are THREE mechanisms, and using the wrong one silently no-ops. Know which\neach link uses.\n\n**A) Set a field on the entity (via create/update)** \u2014 an id or id-array that\nlives in the entity body:\n- tool \u2192 agent: add the tool's id to the agent's `toolIds`. This is how an\n agent gets a (non-system) tool. There is no attach_tool_to_agent route.\n- system tool \u2192 agent: add the catalog id to `systemToolIds`. Get ids from\n `list_system_tools`. No grant/revoke tool \u2014 you set the array via\n `update_agent`. `toolIds` and `systemToolIds` are DIFFERENT lists; a\n system-tool id does not go in `toolIds`.\n- agent \u2192 agent (delegation): add the callee agent's id to the caller's\n `delegationTargetIds` (and set `permissions.delegation: true`).\n- output_schema \u2192 agent: `permissions.expectedOutputSchemaId` (a single id).\n- Changing any of these is read-then-write under optimistic locking: read the\n agent, edit the field \u2014 an id-array is a FULL replace, so include the ids you\n are keeping \u2014 then `update_agent` with `expectedVersion`.\n\n**B) Call an attach route (a link, not a field).** `attach_*`/`detach_*` POST/\nDELETE a link and do NOT touch the entity body:\n- skill \u2192 agent: `attach_skill_to_agent`; skill \u2192 workflow:\n `attach_skill_to_workflow`.\n- policy \u2192 agent: `attach_policy_to_agent`.\n- reference_doc \u2192 agent: `attach_reference_to_agent` (agents only \u2014 there is no\n workflow-scoped reference-doc route).\n- VERIFY every one of these with the read-back before `request_publish_*`:\n `list_agent_skills`, `list_agent_policies`, `list_agent_reference_docs`,\n and `list_workflow_skills` for the workflow-scoped variant. This is not\n optional politeness \u2014 because a link lives OUTSIDE the entity body,\n `read_agent` / `read_workflow` will not show it, so the read-back is the\n only way to tell an attach that landed from one that silently did nothing.\n (Mechanism A is different: those ids ARE in the body, so re-reading the agent\n proves them.)\n- The three agent read-backs also take `workflowId`. With it you get the\n COMPOSED runtime view \u2014 what this agent's prompt actually assembles inside\n that workflow, with a `linkSource` per row saying WHY each item is there (an\n explicit link, tenant-wide, or scoped to the workflow). Without it you see the\n agent on its own, which is not what a run assembles.\n\n**C) Reference an id INSIDE the workflow document** (this is how agents and tools\nget \"into\" a workflow \u2014 there is no attach route for it):\n- agent \u2192 workflow: an `agent` step names its agent in the step's\n `contract.owners[]` as `{ \"type\": \"agent\", \"id\": \"<agentId>\" }`.\n- tool \u2192 workflow: on an `agent` step, list tool ids under\n `contract.tools.toolIds[]` (and/or per plan-step `contract.activities[]`\n entries of `{ \"kind\": \"tool\", \"toolId\": \"<id>\" }`). On an `automation` step,\n set `config.automationType: \"tool\"` and `config.toolId`. A `connector` step\n references a connector tool the same way.\n- Every id you place in a document must be a REAL id in THIS tenant. The workflow\n validator flags a step whose owner agent or referenced tool no longer exists \u2014\n `validate_workflow` is how you catch a bad reference before publishing.\n- Prefer `apply_workflow_mutations` (e.g. `add_step`, `update_step`,\n `add_edge`) over hand-building this JSON: the backend fills defaults and\n validates each edit. Read the returned document to see the exact shape it\n produced, then adjust.\n\n### Which way you are asking\n- `list_workflow_components` \u2014 what does THIS workflow depend on. Two hops:\n what the document names, and what each agent then actually receives.\n- `list_workflows_using` / `list_dependent_agents` \u2014 who depends on THIS\n entity. Ask before you edit or delete anything shared.\n- A component's `reach` is how many workflows name it, this one included. Above\n 1, an edit reaches processes you did not open; `duplicate_workflow_component`\n gives this workflow its own copy and leaves the others on the original.\n\n## Prompt elements & placement (Memory V2)\nA prompt snippet is a LIBRARY item \u2014 creating it changes nothing on its own. It\nonly shapes a run once it is PLACED into a flow step's prompt stack. Placement is\nper flow step, in one of two channels, at an order:\n- `read_workflow_prompt_stacks(workflowId)` (or `read_flow_prompt_stacks(flowId)`)\n resolves a workflow/flow to its steps and each step's system/user stacks \u2014 this\n is how you DISCOVER the `flowStepId`s you place against. Start here.\n- `attach_prompt_snippet_to_flow_step(flowStepId, snippetId, target, displayOrder?)`\n \u2014 `target` is `\"system\"` (instructions the model is steered by) or `\"user\"`\n (content presented as the user turn).\n- `list_flow_step_prompts` shows a step's current system/user stacks (with each\n attachment's `linkId`); `update_flow_step_prompt(linkId, \u2026)` moves/reorders one;\n `reorder_flow_step_prompts(flowStepId, snippetIds)` sets the whole order;\n `detach_prompt_snippet_from_flow_step(linkId)` unlinks (the snippet survives).\n- A WILDCARD snippet applies to EVERY step (top of every stack), with no\n per-step link. `list_wildcard_prompts` shows them.\n\nWhich prompt element an instruction goes in \u2014 persona, policy, skill, reference\ndoc or a step's activities \u2014 and how large each may be, is not decided here: the\nplacement rules are served by the platform and appended to this guide, read live\nfrom this deploy each time you call it.\n\nLong-term recall for an agent is the agent's **`episodicMemoryEnabled`** flag\n(Memory V2, opt-in). Session/workflow memory are RUNTIME, not authored: they are\nwritten by a run, never set here. This connector does not expose them \u2014 the\nbackend serves them per run, but no tool wraps those reads yet, so do not\npromise a user you can show them.\n\n## Reproducing a setup (recreate, never copy ids)\nTo rebuild an agent/workflow/tool graph as NEW entities \u2014 in this tenant, or in\nanother tenant by pointing the connector at THAT tenant's token in a separate\nsession \u2014 author it fresh and wire by the NEW ids. An id from a read is\nmeaningful only in the tenant it came from; sending it back in a create/update\nelsewhere points at nothing. Do this instead:\n1. Read the source graph (in the session pointed at it) for its SHAPE: names,\n field values, which tools each agent lists, which agents/tools each workflow\n step references. START WITH `list_workflow_components`: it answers what the\n workflow is made of in one call \u2014 agents and flows, the workflows it calls,\n the tools those agents reach, the tables behind those tools, the knowledge\n attached \u2014 each row naming the STEPS that are the reason it is there. Walking\n that outward by hand is how a rebuild quietly loses a tool nobody noticed was\n attached. Then read each entity for its field values. For each source agent\n also read its LINKS \u2014 `list_agent_skills`, `list_agent_policies`,\n `list_agent_reference_docs` \u2014 they are not in the agent body, so a plain\n `read_agent` misses them and you would rebuild an agent that quietly lost\n its skills and guardrails.\n2. Create the leaf dependencies first (tools, output_schemas). Keep a map from\n each OLD id to the NEW id the create call returns.\n3. Create agents, translating every referenced id through your map:\n old toolId \u2192 new toolId in `toolIds`, etc. Never send an old id. If the\n source agent has a persona, recreate it with `create_agent_persona` on the\n new agent.\n4. Create workflows; when you add agent/tool references into the document,\n translate through the map the same way.\n5. Attach skills/policies/reference-docs by the new ids, then read them back\n (`list_agent_*`) and diff against what you read in step 1.\n6. `validate_workflow` and re-read each entity to confirm every reference\n resolved, then `request_publish_*`.\nThe connector never carries a secret across: `authConfig` stays placeholders in\nthe new tenant too, and a human fills the real value there.\n\n## Structural workflow edits\n- **Start by reading the spec.** `get_workflow_authoring_spec` returns FOUR\n lists this deploy generates from its own registries: the mutation commands,\n the trigger types, the step types (including the ones you may not author, with\n the reason), and the schedule-rule shapes with a working example each. Neither\n this guide nor the tool descriptions restate any of those lists: a list kept\n in the connector is right only by maintenance, and it has been wrong twice\n already, both times in the direction that costs an agent a 422 on something it\n was told to use. Pass `types` for the live payload schema of a command you\n are about to write.\n- **The payload schemas describe themselves.** What may go inside\n `parameters`, `inputs`, `outputs` and `contract` is IN those schemas \u2014\n including the fact that a bound input DERIVES its contract from the output it\n reads. Ask the spec for the command's `types` rather than looking for prose\n about the shape here; there deliberately is none.\n- **One thing the schemas do not make obvious: which KEY carries the value's\n type, because it is not the same key everywhere.**\n - A TRIGGER PARAMETER and a WORKFLOW CONSTANT use `type`.\n - A step INPUT or OUTPUT uses `kind`.\n - Where a field carries a nested `schema` object, `schema.kind` is what\n wins.\n\n This asymmetry is real, not a typo to normalise: the shapes came from\n different surfaces, and normalising them would make one reader wrong. The\n VALUES differ too \u2014 `get_workflow_authoring_spec` returns one list per key\n (`parameterTypes`, `outputKinds`, `schemaFieldKinds`), and they are not\n interchangeable: only one knows `constant`, and they disagree on how to\n spell a yes/no. Read the list that matches the shape you are writing; this\n guide does not restate them, for the same reason it restates no other list.\n\n Writing `kind` on a trigger parameter is not a 422; it is a key nothing\n reads, so the value silently falls back to text and the workflow runs with\n the wrong thing. Check which of the three you are writing before you write it.\n- `rulesVersion` in that response is a content hash over all four lists. While\n it is unchanged there is nothing to re-fetch; re-read the spec when it changes,\n or when a mutation fails with a 422 suggesting your idea of a command is stale.\n- `apply_workflow_mutations` takes a LIST of commands and applies them in order,\n threading the version for you \u2014 pass the version you read, not one per command.\n- It is NOT transactional. If command N fails, the ones before it stay applied;\n the error tells you how many landed and which index failed. Re-read and resume\n from that index rather than replaying the whole list.\n- `replace_workflow_document` stores the document's `name` and\n `description` \u2014 they sync onto the workflow's own columns, an omitted key\n leaves the stored value alone, and an explicit `\"\"` clears it. `stageId`,\n `capabilityId`, `valueStreamId` and `onFailure` are PATCH-only: changing\n one through this route is REFUSED with a 422 naming the field, and\n round-tripping it unchanged passes. Read, edit, write is therefore safe;\n re-linking a workflow to another stage or capability is its own call.\n- The response is a SUMMARY (version, appliedCount, launchable, validationIssues,\n stepCount, edgeCount), not the document. That is deliberate: a real workflow\n document overflows the response limit, and a successful call would come back\n looking like a failure \u2014 retrying that is how you end up with duplicate steps.\n Ask for the document with `returnDocument: true` or `read_workflow`.\n- Command payloads are STRICT and complete:\n - `add_step` builds a whole step in ONE call \u2014 `name` and `position` are\n required, and `id`, `type`, `config`, `contract`, `inputs` and\n `outputs` are accepted next to them. No follow-up `update_step` is needed\n just to fill the step in.\n - `add_trigger` does the same for a trigger: `parameters` and `outputs`\n go in that call. This is what makes a callable workflow declarable in one\n mutation, since a trigger's parameter list IS its signature.\n - `add_edge` takes the document's own `from`/`to` as well as\n `fromStepId`/`toStepId`, and keeps an `id` you supply.\n - An unknown or misspelled key is a 422 naming it, not a silent drop \u2014 so\n spelling matters and you are told when it is wrong.\n\n## Inputs and constants \u2014 the two lists that belong to the PROCESS\nA workflow declares two whole-list things of its own, both edited the same way\n(read the list, change it, write it back \u2014 there is no per-entry command,\nbecause the identity is the `name` and renaming is the common edit):\n- `set_workflow_inputs` \u2014 what the process NEEDS to start. Carried by every\n start, and the list a caller fills in whichever trigger fired. A start's own\n `parameters` are what that start declares on top; where a name is in both,\n the process-level one wins.\n- `set_workflow_constants` \u2014 what the AUTHOR pins, described below.\nRead both back with `read_workflow_trigger_parameters`, which answers\n`{ inputs, triggers, constants }`.\n\n## Constants \u2014 values the AUTHOR owns\nA constant is applied on EVERY run whichever trigger fired, and it OVERWRITES\nwhatever a caller sent. That is deliberate: a field the operator's form does not\nshow must not become an injection point for anyone who learns its name.\n- Set them with the `set_workflow_constants` command. It replaces the WHOLE\n list, the way `advanced_edit` replaces steps and edges \u2014 read the list, edit\n it, write it back. There is no per-constant command, because a constant's\n identity is its name and renaming is the common edit.\n- Every entry needs a `name`: it is what a step binds to and how the run\n payload keys it. A nameless entry is refused rather than stored, because at\n run time it would simply be skipped.\n- **A constant with no value blocks publishing.** `constant_without_value` is\n an error, not a warning, and it names the constant. It has to be: an entry\n with no value was never written into the run at all, so the workflow validated\n clean, published, and ran without it.\n- ABSENCE, not falsiness. `0`, `\"\"`, `false` and `[]` are values and pass.\n- The value lives in `defaultValue`. On a constant that is not a fallback \u2014\n it IS the value, written on every run. A constant carries no `required`\n (nobody is being asked to fill it in) and needs no flag saying it is one.\n- **A trigger parameter can be pinned the same way**, and the platform reads\n TWO spellings: `constant: true`, or the older `type: \"constant\"`. Both\n mean the author owns it and the caller must not send it, so treat a parameter\n carrying either as off-limits when you build `triggerInput`.\n `read_workflow_trigger_parameters` collapses both into `pinned`, on the\n workflow's constants and on each start's parameters \u2014 read that rather than\n checking the two spellings yourself.\n\n## Two platform invariants that look like bugs\nBoth are deliberate, both run after EVERY edit, and both now report themselves in\nthe mutation response under `systemAdjustments` \u2014 so you no longer have to diff\na document to find out what the platform did.\n- **A bound input's contract is derived, not authored.** An input that reads an\n upstream output takes its contract \u2014 description included \u2014 FROM that output.\n Write the text on the producing step's output; a downstream description changing\n when you edit an output is this rule at work, not the platform overwriting you.\n The inputs it re-derived come back in `rederivedInputs`.\n- **Every step reaches the END point.** The platform guarantees it and wires that\n edge itself; the edges it added or removed come back in `addedEdges` /\n `removedEdges`. An edge you did not ask for is this, not corruption.\n\n## Validating: two questions, two calls\n- `validate_workflow({ workflowId })` checks the STORED workflow against the\n tenant catalog \u2014 it confirms the agents, tools and flows the steps reference\n actually exist. This is the \"can this run?\" answer, and it comes back with\n `catalogChecked: true`.\n- `validate_workflow({ document })` is stateless and read-only-token safe, but\n without a catalog it can only judge STRUCTURE: `launchable` there means the\n graph is sound, not that it will run. It says so with `catalogChecked: false`.\n- Pass exactly one. Before telling a human a workflow is ready, check\n `catalogChecked` \u2014 a green structural verdict is the easiest thing to\n over-read.\n- Read the `issues` rather than only `launchable`: the validator now reports\n a sub-process call with no target, one pointing at itself, one whose target\n is deleted or was never published or has no invocation trigger, a constant\n with no value, and a trigger drawn on the canvas that nothing is wired to.\n Some of those are warnings and still let a workflow publish \u2014 read the\n `severity`, and read the `code`, which is stable where the message is not.\n\n## Triggers\n- A published workflow still does nothing until it has a trigger. A trigger has\n TWO halves, and drawing one on the canvas is only the first.\n- **A schedule is reconciled for you.** Draw it in the document, publish, and\n the firing row is brought into line. That is the exception.\n- **A webhook or conditional trigger is NOT.** Its row must be created\n separately \u2014 `create_webhook_trigger` / `create_conditional_trigger` \u2014\n and publishing will not create one. A webhook drawn and published with no row\n is an endpoint that does not exist; the validator says so with\n `trigger_not_wired`, a WARNING, so the publish still goes through. Take the\n warning seriously: nothing else will tell you.\n- Trigger deletes are HARD deletes \u2014 no restore. Webhook tokens are shown once\n at create/rotate and are never retrievable; hand them straight to your human\n and never store one in an entity field.\n- **A schedule is a claim \u2014 check it.** `run_cron_schedule_now` fires one\n immediately without moving `nextFireAt`, so you can prove a schedule starts\n something instead of waiting until tomorrow to find out it does not. It runs\n for real, with real cost and side effects.\n- **To PAUSE a schedule, disarm it** (`set_cron_schedule_enabled`), never\n delete it. Deleting throws away the rules the author wrote and makes \"stop\n this for a week\" indistinguishable from \"we do not do this any more\".\n- `create_cron_schedule` takes EITHER `cronExpr` OR `rules`, and the\n trigger must exist in the PUBLISHED document. For the rule shapes, read\n `scheduleRuleKinds` from `get_workflow_authoring_spec` \u2014 do not invent one.\n- `list_all_cron_schedules` answers \"what runs tonight?\" across the tenant;\n `reconcile_cron_schedules` answers \"is that actually what runs?\". The second\n reports rather than tidying silently and never arms something switched off.\n- **A conditional trigger without a CHECK starts the workflow every beat.** It\n wakes on its interval and, with nothing set, runs regardless of whether there\n was anything to do \u2014 a mailbox on fifteen minutes then makes ninety-six empty\n runs a day. Set `checkKind` on `create_conditional_trigger`, or on an\n existing one with `update_conditional_trigger`; every trigger authored\n before checks existed has none, so read `checkKind` in\n `list_conditional_triggers` before assuming otherwise.\n- `checkKind: \"automatic\"` is ONE tool call and one rule on its answer \u2014 no\n model, so it is the cheap way to ask \"is there anything there?\". Its\n `checkConfig` is `{ toolId, inputs?, rule: { field, op, value } }`, where\n `field` names a key OF THE TOOL'S ANSWER. A rule pointing at a field the\n tool never returns is reported as a broken setup, not as \"condition not met\".\n `\"agent\"` is reserved for a model judging the condition and is refused\n today. Do not pre-validate a check here \u2014 the backend judges it with the same\n function the dispatcher runs when the trigger fires, and its refusal says\n what to change.\n- **To take a check off again, send `checkKind: \"\"`** \u2014 the empty string. It\n clears the kind and its config together and returns the trigger to starting\n every beat. Omitting the field leaves the check standing, which is why the\n empty string exists at all.\n\n## Evaluating what you built\n- There is no findings endpoint and no evaluator entity. To judge a run, read it:\n `read_run` (its `validatorVerdicts` and `agentInvocations`) and\n `read_run_trace` (the step-by-step tool calls).\n- **Start with `read_run_outline`, not `read_run`.** The outline is the run's\n table of contents and carries no bodies, so it costs what the run's SHAPE\n costs rather than its content \u2014 a fan-out over four thousand items reads about\n as cheaply as one over four. Then open only what you need:\n `read_run_value` for one large step value (by the digest in `stepStates`),\n `read_run_invocation_messages` for one agent's transcript (by the id in\n `agentInvocations`). Reading a whole run to find one message is the habit\n this replaces.\n- `itemCap` on the outline bounds items listed per fan-out step; the remainder\n is counted in `counts.truncated`, never dropped in silence.\n- **What an agent wrote to itself is readable.** `list_run_session_memory`\n lists the files an agent left during a run (metadata only) and\n `read_run_session_memory_file` opens one. When the trace shows a decision but\n not what it was reading, this is usually where the reason is. Workflow-bound\n reference material is NOT here \u2014 that lives in `reference_docs`.\n- **`list_todo_steps`** is every step in the tenant waiting on a human, across\n all runs; `read_run_waiting_on` is the same question for one run you already\n have. It is PAGED over the waiting RUNS, so `items` can be longer than\n `pageSize` \u2014 one run may park several steps. Follow `nextCursor` until it\n is null before concluding anything about how much is waiting.\n- Runs are the only thing with a real archive state \u2014 `archive_run` is\n reversible, `bulk_delete_runs` is not.\n\n## Subworkflows \u2014 one workflow calling another\nBoth halves live in the workflow DOCUMENT, so both are ordinary\n`apply_workflow_mutations` work. There is no separate \"link\" route.\n- **To make a workflow callable**, it needs TWO things: a trigger with\n `typeId: \"subprocess-invocation\"` (`add_trigger`), which is what makes it\n callable at all, and a declared SIGNATURE.\n- **The signature belongs to the PROCESS, not to the trigger.** Declare it with\n `set_workflow_inputs` \u2014 one list, carried by every start, and the same list\n a caller of any kind fills in. A trigger's own `parameters` are what THAT\n start declares on top; `parameters` goes in the `add_trigger` call itself,\n so a start that does add fields needs no follow-up `update_trigger`.\n- Older documents declared everything on the invocation trigger instead, and\n that still works: both are read, and where a name appears in both the\n PROCESS-level one wins. When you are editing a workflow you did not author,\n read `read_workflow_trigger_parameters` and see which list actually holds\n its fields before adding to either.\n- `set_workflow_inputs` REPLACES the whole list, like\n `set_workflow_constants`: read, edit, write back. An entry with no `name`\n is refused rather than stored \u2014 the name is how a caller passes the value and\n how the run payload keys it, so a nameless one would be skipped silently at\n run time.\n- For what an input or a parameter may contain, ask\n `get_workflow_authoring_spec` for the payload schema of the command you are\n about to write, rather than a shape written down here.\n- **It must also be PUBLISHED.** The catalogue reads the published document, so\n a draft-only workflow comes back `callable: false` with \"Never published\".\n Authoring the caller first and publishing the callee later is a run-time\n failure waiting to happen.\n- **To call it**, add a step with `type: \"subprocess\"` and\n `config: { targetWorkflowId, ownerAgentId }` \u2014 both required, both workflow/\n agent ids. Bind the step's `inputs` by NAME to the callee's `parameters`;\n that is the whole mapping, there is no separate argument block.\n- `config.waitForCompletion` is **IGNORED** if you write it. Drawing a step\n after this one already means \"after\", so waiting is implied. The one thing the\n graph cannot say is fire-and-forget: `config.fireAndForget: true`, and then\n nothing downstream may depend on the result.\n- `config.errorStrategy` (\"fail\" | \"skip\") is only honoured when the step\n ITERATES (`config.iteration.enabled`). On a single call \"skip\" would report\n a failed child as success, so it is forced to fail-fast.\n- **A workflow cannot call itself** \u2014 every run would spawn a child that reaches\n the same step again. Pass `exclude: <the workflow you are editing>` to\n `list_callable_workflows` and it is kept out of the list.\n- **What comes back** is the callee's `outcomes`: its end steps, each with the\n fields bound into it. WHICH end was reached is itself part of the answer.\n- **`validate_workflow` checks this now.** A missing target, a workflow\n calling itself, a target that is deleted, never published, or has no\n invocation trigger \u2014 each has its own issue code, and the ones that resolve to\n nothing are errors that block publishing. Still run\n `list_callable_workflows` BEFORE you author the step: it is how you pick a\n target and see the interface you are binding to, where the validator only\n tells you afterwards that the one you picked was wrong.\n\n## Secrets\n- Never handle a real credential. A connector's `authConfig` takes placeholders\n only (\"{{ MY_SECRET }}\" or \"\"); a human fills the real value in Axonity. This\n is enforced \u2014 a write carrying something that looks like a real secret is\n rejected before it leaves this connector.\n- You CAN read the catalogue: `list_secrets` (id, name, authType) and\n `read_secret`. That is how you get the id for a connector's\n `authConfig.secretId` \u2014 do not ask a human to copy one out of the UI.\n- Before you wire a connector to a secret, check `valueKeys` on\n `read_secret`. An empty list means nobody has filled the secret in yet: the\n connector will author fine and fail at RUN time. Say so to your human, with the\n secret's name, rather than reporting the wiring as done.\n- You cannot create, change or delete a secret. That is a human act in Axonity \u2192\n Settings \u2192 Secrets, and the backend refuses it from this connector's token.\n- A secret's `metadata` (connection config, e.g. a session_cookie handshake)\n comes back with credential-shaped entries withheld, listed under\n `metadataRedacted`. If you need one of those values, a human reads it in\n Axonity \u2014 do not try another route to it.\n\n## Tables \u2014 the tenant's own reference data\nA table is an authored library element that HOLDS ROWS: the pricing bands, the\nregion-to-owner map, the list of accepted reason codes. A person designs the\ncolumns and maintains the content; agents and decisions read it.\n\n**When a table is the right element.** Reach for one when the CONTENT is the\npoint and it changes without the process changing.\n- Not an `output_schema`: that describes a SHAPE and holds no data. If you\n find yourself putting example values in a schema, you wanted a table.\n- Not a workflow constant: a constant belongs to ONE workflow and is edited in\n its document. A table is shared, and three workflows reading the same rows is\n the case it exists for.\n- Not a function tool with a list inside it: that makes changing a row an act of\n editing code, reviewing it and publishing a tool. A business user maintains a\n table; nobody should need you to change a postcode.\n\n**Static or dynamic \u2014 who owns the rows.**\n- `isDynamic: false` (the default) is reference data the AUTHOR maintains. A\n workflow reads it and cannot change it. `allowedOperations` must be empty:\n a permission nothing honours is refused, not stored.\n- `isDynamic: true` lets a workflow change the rows, limited to\n `allowedOperations` \u2014 any of `add` / `update` / `delete`. READING IS\n NEVER LISTED there: it is what a table is for and is always allowed.\n- You edit rows through `add_data_table_row` / `update_data_table_row` /\n `delete_data_table_row` whatever the table's mode says \u2014 those grants are\n about what a RUN may do, not about authoring.\n\n**Never put rows in `update_data_table`.** `rows` and `columns` are\nwhole-collection fields: sending one row REPLACES the table's content and\ndeletes everything else. The row tools address a single row by a match\n(`matchColumn`/`matchValue`) and cannot touch the rest \u2014 use them. Reach for\n`update_data_table({ fields: { rows } })` only when you genuinely mean to\nreplace the whole content, and never as a way to append.\n\n**The rule that bites hardest: derived tools follow the PUBLISHED table.** A\npublished table mints its own CRUD tools, so granting an agent access to one is\nordinary tool granting \u2014 take a `toolId` from `list_data_table_tools` and put\nit in the agent's `toolIds`. But those tools follow the table's PUBLISHED\nversion. An author who ticked \"may add rows\" and has not published has granted\nnothing, and the tool that would let a run act on it does not exist yet.\n`list_data_table_tools` reports `offered` (what the draft would yield),\n`toolId` (whether a row exists) and `isLive` (whether a run can reach it)\nseparately for exactly this reason. Read all three before concluding a grant\nlanded. Same rule as everywhere else here \u2014 a draft changes nothing until it is\npublished \u2014 and it is the first thing an agent gets wrong about tables.\n\n**Routing a decision on a table.** A decision can branch by looking a value up\nin one column and taking the branch named in another, with\n`builtin: \"evaluators.table_lookup\"`. The behaviour worth knowing, because no\ncatalogue states it: THE CELL'S VALUE IS THE BRANCH'S OUTCOME KEY \u2014 not a new\nfield, and not something you map afterwards. Every value in the result column\nmust be the outcome key of an outgoing branch, or publishing the workflow is\nrefused. The table must be PUBLISHED: a run reads the published snapshot, so a\ntable that is only saved routes nothing. For the argument names it reads, and\nfor every other selectable evaluator, read `decisionEvaluators` from\n`get_workflow_authoring_spec` \u2014 generated from the registry that dispatches\nthem, so it cannot advertise one the platform would not run.\n\n**Column kinds** come from the same place: `tableColumnKinds` in\n`get_workflow_authoring_spec`. It is a FOURTH vocabulary beside the three for\nparameters, outputs and schema fields, with its own spellings \u2014 do not carry a\nvalue across from one of the others, and do not guess. Every column needs a\n`description`: an agent reads it to know what the column is for, and a save\nwithout one is refused.\n\n**`list_data_tables` is PAGED**, alone among the library lists \u2014 a tenant's\nreference data has no ceiling. Page one is not the library. Follow\n`nextCursor` while `hasMore` is true, or better, narrow in the query:\n`name` is exact and unique per workspace, so \"does one called X already\nexist\" is a single call rather than a walk. The page carries `rowCount`, never\nthe rows; the rows come from `read_data_table`.\n\n## Toolboxes \u2014 where a tool is filed\n- A toolbox is a per-tenant grouping label over tools. Read `list_toolboxes`\n before `create_tool` and pass the box's id as `toolboxId`; a tool created\n without one is ungrouped. Move an existing tool with\n `assign_tool_toolbox` (`toolboxId: null` takes it out of its box).\n- `set_toolbox_tools` DECLARES the membership \u2014 it does not add to it. A tool\n in the box and absent from your list is evicted. To add one tool, read the\n box's contents first, or use `assign_tool_toolbox`, which cannot evict\n anything by accident. A tool is in at most one box.\n- A toolbox NEVER changes what an agent may call. An agent is linked to\n individual tools (`agent.toolIds`), never to a box; there is no route that\n grants a whole box, and \"give this agent the Carerix toolbox\" is a bulk write\n of individual links. What the box decides is how a tool is ADVERTISED: a boxed\n tool with `surfacing: \"inherit\"` is found via discover_tools instead of\n riding in every prompt. Read `effectiveSurfacing` on the tool to answer \"is\n it in the prompt?\"; `surfacing` is the raw stored choice.\n- Deleting a toolbox does NOT delete its tools \u2014 they survive, ungrouped. The\n box itself has no restore.\n- Platform-shipped tools cannot be moved. The `Axonity`, `Checks` and\n `Branching` boxes hold locked tools; the API refuses and NAMES the tool it\n refused. Read that name to your human instead of bisecting the selection.\n- `set_toolbox_auth` sets the credential a box's tools SHARE \u2014 ten Microsoft\n Mail tools, one mailbox, said once. A tool with its own auth keeps it; only a\n tool with none inherits. Same rule as anywhere else: point `secretId` at a\n stored secret, never a real value. Before you clear or replace it, or delete\n the box, call `list_toolbox_dependent_tools` \u2014 it names the tools that would\n start failing at run time with a bare 401.\n\n## Tenant\n- Your token fixes the tenant. You cannot act across tenants; never send a\n tenant id in a body.\n- `read_model_tier_map` says what `capabilityTier` actually RESOLVES TO on\n this tenant. The tier is the field you write; the model is what you are\n choosing. Read it before you pick one.\n- When a run does not start, look outward before blaming the workflow:\n `read_concurrency_status` (is the tenant at its cap?),\n `read_queue_overview` (is dispatch alive?), `list_task_queue` (did\n something fail in the layer beneath the run?). `read_for_each_rate` explains\n a fan-out that is pacing itself rather than stalling.\n- You can READ those and not change them. Caps, rate ceilings and the tier map\n are operator decisions \u2014 report what you found, and to whom, rather than\n looking for another route to it.\n- `read_deploy_contract` says what THIS deploy speaks: build, environment and\n the routes it mounts. Read it when a call fails in a way that suggests the\n backend is older or newer than you expect.\n- `list_users` is where an `ownerId` comes from. Look one up instead of\n asking a human to copy it out of the UI.\n- `list_audit_events({ actorKind: \"service_token\" })` reads back what external\n agents \u2014 including you \u2014 actually changed.\n";
|
|
51
|
+
export declare const CONVENTIONS = "# Authoring conventions for Axonity entities\n\nRead this before creating or updating anything.\n\n## What this connector is for\n- AUTHORING: composing an entity from intent \u2014 drafting a tool, an agent, a\n workflow, and wiring them together so the result is CORRECT and COMPLETE\n (every required field set, every reference pointing at something real).\n- REPRODUCING a setup is just authoring it again, from scratch. To recreate an\n agent/workflow/tool, CREATE NEW entities and wire them by their NEW ids \u2014\n never reuse an id you read. There is no \"migration\" and no \"import an id\":\n this connector only ever acts in the ONE tenant its token points at (see\n Tenant), and ids are tenant-local, so an id from anywhere else points at\n nothing (or the wrong thing) here. Read the source for its SHAPE and intent \u2014\n names, field values, which tools an agent uses, which agents a workflow's\n steps run \u2014 then build fresh and capture the ids the create calls return.\n Recreating into a DIFFERENT tenant means running that build while the\n connector is configured with THAT tenant's token \u2014 a separate session. See\n \"Reproducing a setup\" below.\n\n## The authoring loop\n1. `read_*` the entity (and `read_*_published` if you need to see what is live).\n2. Edit the DRAFT: `update_*`, or workflow-edit tools for workflow structure.\n3. Validate before publish: `validate_workflow` / `analyze_workflow_reachable_outputs` / \n `validate_tool_code` / `format_tool_code`. These four are stateless and work with\n read-only tokens.\n4. Re-read and verify with a saved tool command: `execute_tool` /\n `execute_stored_connector`.\n5. Fix any issues and re-read again.\n6. For a code tool, PROVE it: `dry_run_tool` \u2014 see \"Proving a tool\" below. Skip\n this and `request_publish_tool` will sit blocked forever.\n7. `request_publish_*` \u2014 this only QUEUES a human approval. Publishing many\n entities at once (taking a tenant live) is `request_publish_bulk`, up to 200\n in one call; it is not transactional, so read the per-entry results.\n8. `list_publish_approvals` / `get_publish_approval` to see whether it was\n approved or rejected. Readiness there is recomputed as you read it\n (`readiness` now, `readinessAtRequest` then, `readinessChanged` if it\n moved), so a blocker you saw earlier may already be gone.\n\n9. If something went wrong:\n - Bad edit, entity still exists \u2192 `list_*_versions` then `restore_*_version`\n to roll the draft back to an earlier version.\n - Never published, want a clean slate \u2192 `discard_*_draft` resets the draft to\n the last published state (fails if it was never published \u2014 there's nothing\n to fall back to).\n - Deleted by mistake \u2192 `restore_*` (see \"Delete is recoverable\" below).\n\n## Proving a tool (the publish gate)\nBefore a code tool may be published, the platform requires that its stored code\nhas run cleanly once, at its current version.\n- `dry_run_tool(toolId)` is the run that counts. It executes the tool's OWN\n saved implementation; you supply only sample input. A clean pass clears the\n `dry_run_required` blocker.\n- `execute_tool` does NOT count, however green it comes back. It runs the\n functions YOU pass, which need not be what is stored \u2014 proving nothing about\n what would ship. Use it while drafting code you have not saved yet.\n- Editing the tool afterwards bumps its version and invalidates the proof, so\n dry-run AFTER your last edit.\n- Connectors, builtin-backed validators and platform-shipped tools have no code\n of their own to prove and will refuse (422/403). That is expected, not a\n failure to work around.\n\n## Drafts, not live\n- `create_*` and `update_*` produce a DRAFT. The live runtime is unaffected\n until a human publishes it in Axonity. `flow` has the same rule: runtime uses\n the published snapshot from `read_flow_published` until `request_publish_flow`\n is approved.\n- You cannot publish directly \u2014 the backend refuses a direct publish from a service\n token with a 403, so there is no tool for it and no way around it.\n- A read-only token can only read: any write returns 403. If you see that, ask\n your human for a write-scoped token rather than retrying.\n- `request_publish_*` does NOT publish \u2014 it creates a pending approval a human\n approves in Axonity. Use it when a draft is ready; then tell the user to\n approve it in their Axonity approvals inbox.\n\n## Optimistic locking (always read-then-write)\n- Before `update_*`, call `read_*` and use the `version` you get as\n `expectedVersion` \u2014 in the request BODY. If the update returns a 409\n conflict, someone else changed it \u2014 read again and re-apply your change onto\n the new version.\n- `delete_*` also takes `expectedVersion`, but as an argument on the tool call\n only \u2014 the connector maps it to whatever query-parameter spelling that\n entity's route actually expects (it differs per entity on the wire; you never\n need to know which).\n- `restore_*` and `restore_deleted_*_version` take NO version at all \u2014 a\n restore must never lose to a stale-version guard.\n\n## Errors: branch on the failure, not the words\n- Every error names what went wrong as plainly as the backend allows. For a 409\n specifically, two different problems share that status code and need\n different responses:\n - **Version conflict** \u2014 you had a stale read. Re-read, re-apply, retry.\n - **Reference conflict** \u2014 the entity is referenced by something published or\n live. Retrying will never help. Detach the reference or unpublish the\n referencing entity first.\n The error message tells you which one you hit \u2014 read it, don't assume 409\n always means \"retry.\"\n- A 403 on any write means the token is read-only, or (for a direct publish\n attempt, which no tool makes) that publishing must go through\n `request_publish_*`. Either way: stop, don't retry, tell your human.\n- A 401 means the service token is dead \u2014 missing, wrong, expired, or revoked.\n **The backend deliberately returns the identical message for all four\n causes** so it doesn't leak which applies; you have no way to tell them\n apart and no way to check a token's remaining lifetime in advance (every\n token now expires \u2014 7, 15, 30, 60 or 90 days, chosen when it was minted;\n there is no \"never\"). On any 401: stop, do not retry, and tell your human\n plainly that the configured token is no longer valid and a new one must be\n minted from Axonity \u2192 Settings \u2192 API tokens. Do not guess or imply the cause.\n\n## Field shape\n- All fields are camelCase JSON (e.g. `capabilityTier`, not `capability_tier`).\n ONE documented exception: validator specs (`permissions.outputValidators`,\n `permissions.exposedValidatorTools`) use snake_case keys (`function`,\n `target`, `args`, `when_present`, `inputs`, `error_message`,\n `tool_name`, `validator_name`) \u2014 they are the same shape at every level and\n are stored verbatim, so they are NOT camel-aliased. Send them in snake_case.\n What those keys MEAN, and how a validator is bound to what it judges, is the\n Validators section.\n- `update_*` is a partial change for agents/tools and a partial patch for\n workflows: send only the fields you are changing (plus `expectedVersion`).\n- The backend validates fields; a 422 error names the offending field and why\n (e.g. `body.capabilityTier: Input should be 'economy', 'standard', 'smart' or\n 'reasoning'`). Fix that field and retry \u2014 do not guess at the whole shape.\n- **create/update for tool and agent are STRICT** (`extra=\"forbid\"`): an\n undeclared OR MISSPELLED key is a 422 that names it, not a silent drop. So\n spelling matters and you get told when it is wrong \u2014 good. Other entities are\n more lenient and may drop an unknown key silently; never rely on that.\n- `agent.tags` is a free label for grouping and reporting. It GATES NOTHING \u2014\n what reaches an agent's prompt is a LINK (`attach_policy_to_agent`), visible\n in a table a human can read and edit. A role-tag gate on a policy used to\n decide that invisibly and is refused outright now: sending\n `activation.tags` to `create_policy`/`update_policy` is a 422. The two\n surviving gates, `activation.tools` and `activation.delegation`, each say\n something a link cannot \u2014 \"only on turns where this agent holds this tool\",\n and \"only when (not) delegated\".\n\n## After you write, verify\n- A 200 means the request was accepted, NOT that it did what you meant. Re-read\n the entity after a mutation and diff it against your intent \u2014 a partial update\n only changes what you sent, and on the lenient (non-tool/agent) entities an\n unknown key can be dropped silently rather than rejected.\n- Re-reading the ENTITY is not enough for an `attach_*`: those links are stored\n outside the entity body and never appear in `read_agent`. Read them back with\n `list_agent_skills` / `list_agent_policies` / `list_agent_reference_docs`\n (see Wiring, mechanism B).\n- \"Complete\" is your job, not the backend's. A create succeeds with only the\n required fields set; it does not tell you the entity is USABLE. An agent with\n no `toolIds` runs with no tools; a connector tool with an empty\n `implementation` has no endpoint to call; a workflow with steps but no\n trigger never fires. After creating, check the entity has everything its\n purpose needs \u2014 see the per-entity and wiring sections.\n- To test a whole workflow end to end, `start_workflow_run` then read the result\n (`read_run` / `read_run_trace` / `read_run_cost`). Note it runs the PUBLISHED\n workflow and really executes (cost, connector side effects), so publish first,\n and only test-run when a human is expecting it.\n - Read `read_workflow_trigger_parameters` first and pass `triggerId`: a\n workflow can have several starts, each with different fields, and omitting\n it fires whichever comes first. Never send a parameter marked `pinned` \u2014\n the author owns that value and it is overwritten anyway.\n - **A run can park instead of finishing.** A step that asks a question waits\n for an answer, so \"it never came back\" usually means \"it is waiting on you\".\n `read_run_waiting_on` says what for; `answer_run_question` (or\n `send_run_message` on a conversation run) resumes it. Answer your own test\n runs; on a run a human is depending on, the answer is theirs to give.\n - A FOR EACH launch is not one result. `read_run_items_summary` is the\n roll-up (\"4,415 processed \u00B7 12 failed\") and `list_run_items` the rows \u2014\n with `outcome: \"failed\"`, because the failures are scattered and filtering\n page one finds none of them.\n- A LIST is not the whole list. `list_workflow_runs` returns a page \u2014\n `{ items, nextCursor, pageSize, hasMore }`, 20 by default \u2014 so counting or\n concluding from page one while `hasMore` is true is how you answer confidently\n and wrongly. Follow `nextCursor` until it is null. Two further traps: that\n route lists LAUNCHES (a 4,415-item FOR EACH is ONE entry carrying\n `forEachProgress`, so rows are not runs), and the offset-style lists\n (`list_runs`, `list_publish_approvals`) cap silently \u2014 a full page means\n there may be more, and nothing in the response says so.\n\n## Delete is recoverable \u2014 but not everything is\n- `delete_*` soft-deletes: the entity disappears from `list_*` but is not gone.\n `restore_*` brings it back; `list_deleted_*` shows what's waiting. This\n applies uniformly to workflow, agent, tool, skill, policy, reference_doc,\n persona, output_schema, prompt_snippet, and flow.\n- The same applies one level down, to version HISTORY:\n `delete_*_version` removes one entry from history (the current draft and\n published version are protected and can't be targeted this way);\n `list_deleted_*_versions` / `restore_deleted_*_version` undo it. Deleted\n version numbers stay reserved rather than reused, so gaps in the numbering\n are normal, not a sign of corruption.\n- `restore_*` can itself 409 if a live entity now holds the same name/slot \u2014\n the error names the conflicting one.\n- There is no \"archive\" state for these ten entities \u2014 delete + restore IS the\n lifecycle. Runs are the one exception with a true archive/unarchive (see below).\n\n## Per entity \u2014 what to create, and what \"complete\" means\n\n**The platform's live create contract wins.** `get_entity_authoring_contract`\nreads this deploy's own contract per entity \u2014 required fields, allowed values,\nand the rules a publication checks \u2014 generated from the code that enforces\nthem, with a `rulesVersion`. The notes below are kept by hand in this\nconnector: guidance on order and on what \"complete\" means, SUBORDINATE to that\ncontract. Where they disagree, the contract is right and this text is behind.\nOn a deploy that does not serve it yet the tool says so; then the platform's\nrefusal on save or publish (and `get_publish_release`'s readiness) is the\nauthority, not these notes.\n\nBuild in dependency order: a thing you will REFERENCE must exist before the\nthing that references it. So: tools and output-schemas first, then agents (which\nlist tool ids), then workflows (whose steps name agents and tools), then\ntriggers (which make a workflow fire). Skills/policies/reference-docs can be\nmade any time before you attach them.\n\n### tool (`create_tool`, strict body)\n- Required: `name`. Also: `description`, `type`, `inputSchema`,\n `outputSchema`, `implementation`, `authConfig`, `surfacing`.\n- `type`: `\"function\"` (default) | `\"connector\"` | `\"validator\"` |\n `\"evaluator\"` | `\"executor\"`. `surfacing`: `\"always\"` (in the agent's tool list) |\n `\"on_demand\"`.\n- `inputSchema` / `outputSchema` are JSON-Schema objects describing the tool's\n params and result. Set them \u2014 an agent binds to a tool by its declared I/O; a\n tool with no inputSchema is hard to call correctly.\n- `implementation` is what makes the tool DO something, and its shape depends on\n `type`:\n - **function** \u2014 carries the Python. Author and check the code with\n `validate_tool_code` and `format_tool_code`, and prove it runs with\n `execute_tool`, BEFORE `create_tool`. A function tool whose\n `implementation` is empty is an inert stub.\n - **connector** \u2014 carries the HTTP call: typically `connectorType`\n (\"rest\"/\"graphql\"), `baseUrl`, `method`, `path`, `headers`,\n `queryParams`, `body`, `responseMapping`, `pagination`. Prefer the\n `create_connector` tool, which sets `type:\"connector\"` for you. NEVER put\n a real credential in `authConfig` \u2014 placeholders only (\"{{ MY_SECRET }}\"\n or \"\"); a human fills the secret in Axonity. Test with\n `execute_stored_connector` (uses the real, server-side-decrypted secret;\n you see only the result).\n - **`implementation.effect` is required to PUBLISH**: `\"reads\"` (fetches,\n looks something up) or `\"acts\"` (sends, writes, starts something). A\n draft saves without it; the publish approval refuses it (\"Set the\n connector's side effects \u2026 before publishing\"). Set it at create time,\n so the human approving is not the first to find out.\n - **An answer that is a file**: `responseMapping: { \"type\": \"file\",\n \"filename\": \"{{filename}}\" }` keeps the bytes as a file of the run and\n returns its reference `{ fileId, filename, contentType, sizeBytes }` \u2014\n the shape an upload has, so Digitize documents reads it and a\n sub-workflow's file parameter takes it. `filename` is optional (the\n far end's `Content-Disposition` name, else `document`) and may use\n the inputs. No `responseTransform` with a file answer. Outside a run\n (`execute_stored_connector`) nothing is stored: you see name and size.\n - **executor** \u2014 runs a command on a registered executor (a program on\n the customer's side). The save refuses a tool that names no executor or a\n command that executor does not know; `list_executors` and\n `list_executor_commands` say what exists.\n - **validator / evaluator** \u2014 a constrained kind, and `implementation` takes\n ONE OF TWO shapes. Either `{\"builtin\": \"validators.<name>\"}`, naming a\n house predicate \u2014 look at the tenant's existing validator/evaluator tools\n (`list_tools`) for the keys in use rather than inventing one. Or authored\n Python, like a function tool, whose `run` returns a VERDICT: `True` /\n `False` / a fail-detail object. A `run` that returns a string, a number\n or nothing at all declares no verdict and is REFUSED on save, so that a\n misconfigured gate cannot silently pass everything at run time. Authored\n code takes the standard call \u2014 see Validators.\n- Complete check: the tool has the I/O schemas its callers need AND a non-empty\n implementation of the right shape for its type.\n- `toolboxId` files the tool in a toolbox on creation. Read `list_toolboxes`\n BEFORE you create: a tool made without one is ungrouped, which is valid and\n is also how nobody finds it. See Toolboxes below.\n\n### agent (`create_agent`, strict body)\n- Required: `name`. Enumerated fields, with their ONLY valid values:\n - `capabilityTier`: `economy` | `standard` (default) | `smart` | `reasoning`\n - `creativityTier`: `low` | `medium` (default) | `high`\n - `learningMode`: `none` (default) | `adaptive` | `strict`\n- `avatar`, `tags` (free labels for grouping \u2014 they gate nothing; see Field\n shape), `canOwnProcess` (bool; must be true for an agent to own a\n workflow step's flow), `episodicMemoryEnabled` (bool).\n- `permissions` (object): `planning`, `delegation`, `verboseOutput` (bools);\n `outputValidators` / `exposedValidatorTools` (snake_case specs \u2014 see Field\n shape for the casing, Validators for what they say);\n `expectedOutputSchemaId` (an output_schema id \u2014 MANDATORY whenever you\n attach validators here).\n- Wiring fields \u2014 see the Wiring section: `toolIds`, `systemToolIds`,\n `delegationTargetIds`.\n- Complete check: an agent with no `toolIds`/`systemToolIds` has no tools and\n can only reason/talk. If it is meant to DO something, give it the tools.\n\n### workflow (`create_workflow`, then edit the document)\n- `create_workflow` makes a STUB (name, description) with an empty document.\n You then build the graph with either\n - `apply_workflow_mutations` \u2014 a LIST of command mutations applied in order\n (preferred: the backend validates each and threads the version), or\n - `replace_workflow_document` \u2014 a full-document atomic PUT.\n- The document is a graph: `steps.all[]` (each `{ id, type, name, ... }`) and\n `steps.edges[]` (each `{ id, from, to, type? }`) plus `triggers`.\n- A step's GOAL is read from a different place per step type: an `agent`\n step's `contract.goal`, a `manual` or `automation` step's\n `config.goal`. Written in the other place it is stored and read by nobody,\n and the validator keeps reporting `step_no_goal`.\n- After every `apply_workflow_mutations` / `replace_workflow_document` the\n connector runs `validate_workflow` on the stored workflow and returns its\n verdict as `validation`. Report those issues as they are. Green there means\n every PLATFORM check passed \u2014 none of them asks whether the editor can draw\n the document, so open it in the editor before you call it done.\n- WHICH VALUES a step's `type` and a trigger's `typeId` may hold is answered\n by `get_workflow_authoring_spec`, read live from this deploy. This guide\n does not list them, on purpose: it used to name nine step types where seven\n validate, and a list nobody could read is a list that goes wrong. The spec\n also lists the types you may NOT author, each with the reason and how to\n express that intention instead \u2014 so reaching for one gets you an answer\n rather than a `step_invalid_type`.\n- Referential integrity is enforced: step and trigger ids must be unique, and\n every edge `from`/`to` must reference an id that exists in the document\n (`from` may be a trigger or step; `to` must be a step). A dangling edge is a\n hard error.\n- Complete check: run `validate_workflow` on the document \u2014 it returns\n `launchable` plus an `issues` list. A workflow is not done until it is\n launchable AND has a trigger (see Triggers).\n\n### persona\n- An agent's character \u2014 1:1 with an agent. Created only through its agent\n (`create_agent_persona`: `name`, `characterText`); then a normal entity\n (`read_persona`, `update_persona`, delete/restore/versions).\n\n### output_schema\n- A reusable output contract, versioned and publishable. Referenced BY id from a\n tool's validators or an agent's `permissions.expectedOutputSchemaId` \u2014 so\n create it first, then point things at the id the create call returns.\n- `schemaBody` is a JSON Schema document (`type`, `properties`,\n `required`) PLUS one key that is not JSON Schema: `validators`, the gates\n run against a submitted output. This is the place a validator can be STEERED\n at part of the output \u2014 see Validators.\n\n### skill / policy / reference_doc\n- Memory entities attached to agents (and skills also to workflows). Create them,\n then wire with the `attach_*` tools \u2014 see Wiring.\n- A reference doc has NO kind any more. What used to be a semantic note lives\n in the model itself: a word is a concept (`create_concept`), a table card\n is the table's description and grain, a source query is a query of its\n own (`create_query`, the table's `fillingQueryId`), an example question\n is a measure's `questions`. The\n platform refuses `semanticKind`, `semanticDetails`, `subjectTableId`\n and `subjectSourceId` on a reference doc, and says where each one went.\n Building the SEMANTIC MODEL? Read `axonity_semantic_conventions` first \u2014\n those rules are served by the platform; this guide does not restate them.\n\n### prompt_snippet\n- Reusable prompt fragments; a normal entity. Wire-level oddity:\n `list_deleted_prompt_snippets` returns the backend `items` envelope.\n\n### flow\n- A reusable workflow fragment in the authored-entity lifecycle. `create_flow` /\n `update_flow` write draft state; runtime uses `read_flow_published` until\n approval. `request_publish_flow` to publish, `discard_flow_draft` to reset.\n\n### data_table\n- An authored table of rows: the tenant's own reference data, designed and\n maintained by a person, read by agents and decisions. Same lifecycle as an\n output schema \u2014 draft, version, request publish. See \"Tables\" below, which is\n where the rules that are NOT the generic ones live.\n\n### company\n- The tenant's single company document (mission, value streams, org structure).\n A SINGLETON \u2014 one per tenant, no id, so there is no list/create/delete:\n `read_company`, `update_company` (a WHOLE-document save with\n `expectedVersion`, like `replace_workflow_document`), `list_company_versions`,\n `read_company_version`, `restore_company_version`, `read_company_published`.\n Publish it like anything else with `request_publish_company` \u2014 but because it\n is a singleton this takes NO id (the server resolves your tenant's one\n company). Direct company publish is closed to machine tokens, so the approval\n queue is the only path.\n\n## Validators \u2014 one shape, one call\nA validator is a GATE on a submitted output: it runs after an agent produces a\nresult and can refuse it. Specs are written in two places and carry the same\nkey names in both \u2014 `permissions.outputValidators` on an agent, and\n`validators` inside an output_schema's `schemaBody`. The backend's OpenAPI\ndescription of `schemaBody` is the source for the full contract, and the\nplatform's live contract (`get_entity_authoring_contract`, output_schema)\nwins over this section where this deploy serves it. What follows is the part\nyou cannot author without, checked against the platform and SUBORDINATE to both.\n\n- A spec names its check in ONE of two ways, and each brings its own binding:\n - `function` \u2014 a shipped predicate (e.g. `validators.min_length`), bound\n by `target` (the field it judges; omit it to judge the whole output) and\n `args`.\n - `tool` \u2014 a validator TOOL in this tenant's library, by name or id.\n Exactly one of the two. Naming both leaves it unsaid which one runs, naming\n neither leaves nothing to run, and a gate does not get to be ambiguous \u2014 both\n are refused. The binding keys do not mix either: `target`/`args` belong to\n the `function` form and are refused on a `tool` spec, because a key the\n runtime ignores is worse stored than rejected.\n- `error_message` is what the agent is told on refusal, and the one key both\n forms share. `{value}` in it is replaced by what the validator reported.\n- `when_present` runs the check only when the target field is actually in the\n payload. Off by default, deliberately: a blanket skip turns a mistyped\n `target` into a gate that passes everything. Set it where a field is\n genuinely optional.\n\n### The standard call\nEVERY authored validator is invoked the same way. The signature does not vary\nper tool, and that is the whole point: a screen cannot offer inputs it does not\nknow, and a spec served to an agent cannot describe a shape that differs per\ntool. Bespoke parameters push the contract into prose that goes stale.\n\n```\nrun(data, slots, binding)\n```\n\n- `data` \u2014 what the validator is pointed at, UNTOUCHED. The whole submitted\n output, or the field named by `field`.\n- `slots` \u2014 every declared session-memory reference found in `data`, with its\n text, indexed by the reference itself. The platform resolves them because code\n in the sandbox is never handed a lookup callable \u2014 a validator may be foreign\n code.\n- `binding` \u2014 what you declared, verbatim, so the validator knows which values\n in `data` are references to look up rather than text to read. Without it it\n would have to guess, and guessing is how a check reads a key as though it were\n the body.\n\nTwo keys on the spec steer that call: `field` names the part of the output to\nhand over (leave it out for the whole output), and `slots` lists the fields\nthat hold references.\n\n```json\n{ \"tool\": \"my.freshness.check\",\n \"field\": \"units\",\n \"slots\": [\"key\", \"source_key\"],\n \"error_message\": \"REJECTED: {value}\" }\n```\n\nBOTH ARE CHECKED WHEN YOU SAVE the output schema, and a refusal names the\nfields that do exist: a `field` that is not a property, or a `slots` name the\nrows do not carry. That check is there because either mistake fails SILENTLY at\nrun time \u2014 a name that is not there is never looked up, so the gate runs on\nunresolved values and nothing says so. (Rows whose schema declares no shape are\nleft alone: there is nothing to check against, and guessing would refuse correct\nconfigurations.)\n\nWHERE `field` AND `slots` ARE ACCEPTED: inside an output_schema's\n`schemaBody`, which is stored verbatim. An agent's\n`permissions.outputValidators` is a STRICT body \u2014 an undeclared key is a 422 \u2014\nso a spec written THERE takes neither, and its validator gets the whole output\nand no slots. Steer a validator from the output_schema.\n\n### The older, explicit binding\nA tool spec MAY declare `inputs` instead, and a spec with `inputs` does NOT\nget the standard call. That is the way for a validator tool backed by a shipped\npredicate, which receives `value` plus its own arguments \u2014 its `run` would\nnot accept `data`/`slots`/`binding`. Each entry is\n`{\"source\": \"field\"|\"const\", \"field\": <name>, \"as\": \"slot\"?}`; with\n`as: \"slot\"` the field's value is read as a reference and its TEXT is passed.\n`field` takes a path, so a binding can point INTO a list: `units[].key` is,\nfrom every row in `units`, the `key` field.\n\nWriting a NEW validator: take the standard call and leave `inputs` out.\n\n## Wiring \u2014 how the pieces connect\nThere are THREE mechanisms, and using the wrong one silently no-ops. Know which\neach link uses.\n\n**A) Set a field on the entity (via create/update)** \u2014 an id or id-array that\nlives in the entity body:\n- tool \u2192 agent: add the tool's id to the agent's `toolIds`. This is how an\n agent gets a (non-system) tool. There is no attach_tool_to_agent route.\n- system tool \u2192 agent: add the catalog id to `systemToolIds`. Get ids from\n `list_system_tools`. No grant/revoke tool \u2014 you set the array via\n `update_agent`. `toolIds` and `systemToolIds` are DIFFERENT lists; a\n system-tool id does not go in `toolIds`.\n- agent \u2192 agent (delegation): add the callee agent's id to the caller's\n `delegationTargetIds` (and set `permissions.delegation: true`).\n- output_schema \u2192 agent: `permissions.expectedOutputSchemaId` (a single id).\n- Changing any of these is read-then-write under optimistic locking: read the\n agent, edit the field \u2014 an id-array is a FULL replace, so include the ids you\n are keeping \u2014 then `update_agent` with `expectedVersion`.\n\n**B) Call an attach route (a link, not a field).** `attach_*`/`detach_*` POST/\nDELETE a link and do NOT touch the entity body:\n- skill \u2192 agent: `attach_skill_to_agent`; skill \u2192 workflow:\n `attach_skill_to_workflow`.\n- policy \u2192 agent: `attach_policy_to_agent`.\n- reference_doc \u2192 agent: `attach_reference_to_agent` (agents only \u2014 there is no\n workflow-scoped reference-doc route).\n- VERIFY every one of these with the read-back before `request_publish_*`:\n `list_agent_skills`, `list_agent_policies`, `list_agent_reference_docs`,\n and `list_workflow_skills` for the workflow-scoped variant. This is not\n optional politeness \u2014 because a link lives OUTSIDE the entity body,\n `read_agent` / `read_workflow` will not show it, so the read-back is the\n only way to tell an attach that landed from one that silently did nothing.\n (Mechanism A is different: those ids ARE in the body, so re-reading the agent\n proves them.)\n- The three agent read-backs also take `workflowId`. With it you get the\n COMPOSED runtime view \u2014 what this agent's prompt actually assembles inside\n that workflow, with a `linkSource` per row saying WHY each item is there (an\n explicit link, tenant-wide, or scoped to the workflow). Without it you see the\n agent on its own, which is not what a run assembles.\n\n**C) Reference an id INSIDE the workflow document** (this is how agents and tools\nget \"into\" a workflow \u2014 there is no attach route for it):\n- agent \u2192 workflow: an `agent` step names its agent in the step's\n `contract.owners[]` as `{ \"type\": \"agent\", \"id\": \"<agentId>\" }`.\n- tool \u2192 workflow: on an `agent` step, list tool ids under\n `contract.tools.toolIds[]` (and/or per plan-step `contract.activities[]`\n entries of `{ \"kind\": \"tool\", \"toolId\": \"<id>\" }`). On an `automation` step,\n set `config.automationType: \"tool\"` and `config.toolId`. A `connector` step\n references a connector tool the same way.\n- Every id you place in a document must be a REAL id in THIS tenant. The workflow\n validator flags a step whose owner agent or referenced tool no longer exists \u2014\n `validate_workflow` is how you catch a bad reference before publishing.\n- Prefer `apply_workflow_mutations` (e.g. `add_step`, `update_step`,\n `add_edge`) over hand-building this JSON: the backend fills defaults and\n validates each edit. Read the returned document to see the exact shape it\n produced, then adjust.\n\n### Which way you are asking\n- `list_workflow_components` \u2014 what does THIS workflow depend on. Two hops:\n what the document names, and what each agent then actually receives.\n- `list_workflows_using` / `list_dependent_agents` \u2014 who depends on THIS\n entity. Ask before you edit or delete anything shared.\n- A component's `reach` is how many workflows name it, this one included. Above\n 1, an edit reaches processes you did not open; `duplicate_workflow_component`\n gives this workflow its own copy and leaves the others on the original.\n\n## Prompt elements & placement (Memory V2)\nA prompt snippet is a LIBRARY item \u2014 creating it changes nothing on its own. It\nonly shapes a run once it is PLACED into a flow step's prompt stack. Placement is\nper flow step, in one of two channels, at an order:\n- `read_workflow_prompt_stacks(workflowId)` (or `read_flow_prompt_stacks(flowId)`)\n resolves a workflow/flow to its steps and each step's system/user stacks \u2014 this\n is how you DISCOVER the `flowStepId`s you place against. Start here.\n- `attach_prompt_snippet_to_flow_step(flowStepId, snippetId, target, displayOrder?)`\n \u2014 `target` is `\"system\"` (instructions the model is steered by) or `\"user\"`\n (content presented as the user turn).\n- `list_flow_step_prompts` shows a step's current system/user stacks (with each\n attachment's `linkId`); `update_flow_step_prompt(linkId, \u2026)` moves/reorders one;\n `reorder_flow_step_prompts(flowStepId, snippetIds)` sets the whole order;\n `detach_prompt_snippet_from_flow_step(linkId)` unlinks (the snippet survives).\n- A WILDCARD snippet applies to EVERY step (top of every stack), with no\n per-step link. `list_wildcard_prompts` shows them.\n\nWhich prompt element an instruction goes in \u2014 persona, policy, skill, reference\ndoc or a step's activities \u2014 and how large each may be, is not decided here: the\nplacement rules are served by the platform and appended to this guide, read live\nfrom this deploy each time you call it.\n\nLong-term recall for an agent is the agent's **`episodicMemoryEnabled`** flag\n(Memory V2, opt-in). Session/workflow memory are RUNTIME, not authored: they are\nwritten by a run, never set here. This connector does not expose them \u2014 the\nbackend serves them per run, but no tool wraps those reads yet, so do not\npromise a user you can show them.\n\n## Reproducing a setup (recreate, never copy ids)\nTo rebuild an agent/workflow/tool graph as NEW entities \u2014 in this tenant, or in\nanother tenant by pointing the connector at THAT tenant's token in a separate\nsession \u2014 author it fresh and wire by the NEW ids. An id from a read is\nmeaningful only in the tenant it came from; sending it back in a create/update\nelsewhere points at nothing. Do this instead:\n1. Read the source graph (in the session pointed at it) for its SHAPE: names,\n field values, which tools each agent lists, which agents/tools each workflow\n step references. START WITH `list_workflow_components`: it answers what the\n workflow is made of in one call \u2014 agents and flows, the workflows it calls,\n the tools those agents reach, the tables behind those tools, the knowledge\n attached \u2014 each row naming the STEPS that are the reason it is there. Walking\n that outward by hand is how a rebuild quietly loses a tool nobody noticed was\n attached. Then read each entity for its field values. For each source agent\n also read its LINKS \u2014 `list_agent_skills`, `list_agent_policies`,\n `list_agent_reference_docs` \u2014 they are not in the agent body, so a plain\n `read_agent` misses them and you would rebuild an agent that quietly lost\n its skills and guardrails.\n2. Create the leaf dependencies first (tools, output_schemas). Keep a map from\n each OLD id to the NEW id the create call returns.\n3. Create agents, translating every referenced id through your map:\n old toolId \u2192 new toolId in `toolIds`, etc. Never send an old id. If the\n source agent has a persona, recreate it with `create_agent_persona` on the\n new agent.\n4. Create workflows; when you add agent/tool references into the document,\n translate through the map the same way.\n5. Attach skills/policies/reference-docs by the new ids, then read them back\n (`list_agent_*`) and diff against what you read in step 1.\n6. `validate_workflow` and re-read each entity to confirm every reference\n resolved, then `request_publish_*`.\nThe connector never carries a secret across: `authConfig` stays placeholders in\nthe new tenant too, and a human fills the real value there.\n\n## Structural workflow edits\n- **Start by reading the spec.** `get_workflow_authoring_spec` returns FOUR\n lists this deploy generates from its own registries: the mutation commands,\n the trigger types, the step types (including the ones you may not author, with\n the reason), and the schedule-rule shapes with a working example each. Neither\n this guide nor the tool descriptions restate any of those lists: a list kept\n in the connector is right only by maintenance, and it has been wrong twice\n already, both times in the direction that costs an agent a 422 on something it\n was told to use. Pass `types` for the live payload schema of a command you\n are about to write.\n- **The payload schemas describe themselves.** What may go inside\n `parameters`, `inputs`, `outputs` and `contract` is IN those schemas \u2014\n including the fact that a bound input DERIVES its contract from the output it\n reads. Ask the spec for the command's `types` rather than looking for prose\n about the shape here; there deliberately is none.\n- **One thing the schemas do not make obvious: which KEY carries the value's\n type, because it is not the same key everywhere.**\n - A TRIGGER PARAMETER and a WORKFLOW CONSTANT use `type`.\n - A step INPUT or OUTPUT uses `kind`.\n - Where a field carries a nested `schema` object, `schema.kind` is what\n wins.\n\n This asymmetry is real, not a typo to normalise: the shapes came from\n different surfaces, and normalising them would make one reader wrong. The\n VALUES differ too \u2014 `get_workflow_authoring_spec` returns one list per key\n (`parameterTypes`, `outputKinds`, `schemaFieldKinds`), and they are not\n interchangeable: only one knows `constant`, and they disagree on how to\n spell a yes/no. Read the list that matches the shape you are writing; this\n guide does not restate them, for the same reason it restates no other list.\n\n Writing `kind` on a trigger parameter is not a 422; it is a key nothing\n reads, so the value silently falls back to text and the workflow runs with\n the wrong thing. Check which of the three you are writing before you write it.\n- `rulesVersion` in that response is a content hash over all four lists. While\n it is unchanged there is nothing to re-fetch; re-read the spec when it changes,\n or when a mutation fails with a 422 suggesting your idea of a command is stale.\n- `apply_workflow_mutations` takes a LIST of commands and applies them in order,\n threading the version for you \u2014 pass the version you read, not one per command.\n- It is NOT transactional. If command N fails, the ones before it stay applied;\n the error tells you how many landed and which index failed. Re-read and resume\n from that index rather than replaying the whole list.\n- `replace_workflow_document` stores the document's `name` and\n `description` \u2014 they sync onto the workflow's own columns, an omitted key\n leaves the stored value alone, and an explicit `\"\"` clears it. `stageId`,\n `capabilityId`, `valueStreamId` and `onFailure` are PATCH-only: changing\n one through this route is REFUSED with a 422 naming the field, and\n round-tripping it unchanged passes. Read, edit, write is therefore safe;\n re-linking a workflow to another stage or capability is its own call.\n- The response is a SUMMARY (version, appliedCount, launchable, validationIssues,\n stepCount, edgeCount), not the document. That is deliberate: a real workflow\n document overflows the response limit, and a successful call would come back\n looking like a failure \u2014 retrying that is how you end up with duplicate steps.\n Ask for the document with `returnDocument: true` or `read_workflow`.\n- Command payloads are STRICT and complete:\n - `add_step` builds a whole step in ONE call \u2014 `name` and `position` are\n required, and `id`, `type`, `config`, `contract`, `inputs` and\n `outputs` are accepted next to them. No follow-up `update_step` is needed\n just to fill the step in.\n - `add_trigger` does the same for a trigger: `parameters` and `outputs`\n go in that call. This is what makes a callable workflow declarable in one\n mutation, since a trigger's parameter list IS its signature.\n - `add_edge` takes the document's own `from`/`to` as well as\n `fromStepId`/`toStepId`, and keeps an `id` you supply.\n - An unknown or misspelled key is a 422 naming it, not a silent drop \u2014 so\n spelling matters and you are told when it is wrong.\n\n## Inputs and constants \u2014 the two lists that belong to the PROCESS\nA workflow declares two whole-list things of its own, both edited the same way\n(read the list, change it, write it back \u2014 there is no per-entry command,\nbecause the identity is the `name` and renaming is the common edit):\n- `set_workflow_inputs` \u2014 what the process NEEDS to start. Carried by every\n start, and the list a caller fills in whichever trigger fired. A start's own\n `parameters` are what that start declares on top; where a name is in both,\n the process-level one wins.\n- `set_workflow_constants` \u2014 what the AUTHOR pins, described below.\nRead both back with `read_workflow_trigger_parameters`, which answers\n`{ inputs, triggers, constants }`.\n\n## Constants \u2014 values the AUTHOR owns\nA constant is applied on EVERY run whichever trigger fired, and it OVERWRITES\nwhatever a caller sent. That is deliberate: a field the operator's form does not\nshow must not become an injection point for anyone who learns its name.\n- Set them with the `set_workflow_constants` command. It replaces the WHOLE\n list, the way `advanced_edit` replaces steps and edges \u2014 read the list, edit\n it, write it back. There is no per-constant command, because a constant's\n identity is its name and renaming is the common edit.\n- Every entry needs a `name`: it is what a step binds to and how the run\n payload keys it. A nameless entry is refused rather than stored, because at\n run time it would simply be skipped.\n- **A constant with no value blocks publishing.** `constant_without_value` is\n an error, not a warning, and it names the constant. It has to be: an entry\n with no value was never written into the run at all, so the workflow validated\n clean, published, and ran without it.\n- ABSENCE, not falsiness. `0`, `\"\"`, `false` and `[]` are values and pass.\n- The value lives in `defaultValue`. On a constant that is not a fallback \u2014\n it IS the value, written on every run. A constant carries no `required`\n (nobody is being asked to fill it in) and needs no flag saying it is one.\n- **A trigger parameter can be pinned the same way**, and the platform reads\n TWO spellings: `constant: true`, or the older `type: \"constant\"`. Both\n mean the author owns it and the caller must not send it, so treat a parameter\n carrying either as off-limits when you build `triggerInput`.\n `read_workflow_trigger_parameters` collapses both into `pinned`, on the\n workflow's constants and on each start's parameters \u2014 read that rather than\n checking the two spellings yourself.\n\n## Two platform invariants that look like bugs\nBoth are deliberate, both run after EVERY edit, and both now report themselves in\nthe mutation response under `systemAdjustments` \u2014 so you no longer have to diff\na document to find out what the platform did.\n- **A bound input's contract is derived, not authored.** An input that reads an\n upstream output takes its contract \u2014 description included \u2014 FROM that output.\n Write the text on the producing step's output; a downstream description changing\n when you edit an output is this rule at work, not the platform overwriting you.\n The inputs it re-derived come back in `rederivedInputs`.\n- **Every step reaches the END point.** The platform guarantees it and wires that\n edge itself; the edges it added or removed come back in `addedEdges` /\n `removedEdges`. An edge you did not ask for is this, not corruption.\n\n## Validating: two questions, two calls\n- `validate_workflow({ workflowId })` checks the STORED workflow against the\n tenant catalog \u2014 it confirms the agents, tools and flows the steps reference\n actually exist. This is the \"can this run?\" answer, and it comes back with\n `catalogChecked: true`.\n- `validate_workflow({ document })` is stateless and read-only-token safe, but\n without a catalog it can only judge STRUCTURE: `launchable` there means the\n graph is sound, not that it will run. It says so with `catalogChecked: false`.\n- Pass exactly one. Before telling a human a workflow is ready, check\n `catalogChecked` \u2014 a green structural verdict is the easiest thing to\n over-read.\n- Read the `issues` rather than only `launchable`: the validator now reports\n a sub-process call with no target, one pointing at itself, one whose target\n is deleted or was never published or has no invocation trigger, a constant\n with no value, and a trigger drawn on the canvas that nothing is wired to.\n Some of those are warnings and still let a workflow publish \u2014 read the\n `severity`, and read the `code`, which is stable where the message is not.\n\n## Triggers\n- A published workflow still does nothing until it has a trigger. A trigger has\n TWO halves, and drawing one on the canvas is only the first.\n- **A schedule and a conditional start are reconciled for you.** Draw them in\n the document, publish, and the firing row is brought into line: created,\n updated, and removed when the element is gone. A deploy does the same and\n brings new ones in switched off.\n- **A webhook is NOT.** Its row must be created separately \u2014\n `create_webhook_trigger` \u2014 and publishing will not create one. A webhook\n drawn and published with no row is an endpoint that does not exist; the\n validator says so with `trigger_not_wired`, a WARNING, so the publish still\n goes through. Take the warning seriously: nothing else will tell you.\n- Trigger deletes are HARD deletes \u2014 no restore. Webhook tokens are shown once\n at create/rotate and are never retrievable; hand them straight to your human\n and never store one in an entity field.\n- **A schedule is a claim \u2014 check it.** `run_cron_schedule_now` fires one\n immediately without moving `nextFireAt`, so you can prove a schedule starts\n something instead of waiting until tomorrow to find out it does not. It runs\n for real, with real cost and side effects.\n- **To PAUSE a schedule, disarm it** (`set_cron_schedule_enabled`), never\n delete it. Deleting throws away the rules the author wrote and makes \"stop\n this for a week\" indistinguishable from \"we do not do this any more\".\n- `create_cron_schedule` takes EITHER `cronExpr` OR `rules`, and the\n trigger must exist in the PUBLISHED document. For the rule shapes, read\n `scheduleRuleKinds` from `get_workflow_authoring_spec` \u2014 do not invent one.\n- `list_all_cron_schedules` answers \"what runs tonight?\" across the tenant;\n `reconcile_cron_schedules` answers \"is that actually what runs?\". The second\n reports rather than tidying silently and never arms something switched off.\n- **A conditional start is authored in the workflow, on the trigger element.**\n Its `typeId` is `\"conditional-poll\"`, its `id` MUST be a UUID (the\n platform cannot make a row for any other id), and everything it does lives\n on its `config`: `conditionText`, `repeatIntervalMinutes`, and the\n CHECK \u2014 `checkKind` with that kind's settings. Write it with\n `apply_workflow_mutations` (`add_trigger`, or `update_trigger`, which\n REPLACES `config` whole \u2014 read it, change it, send all of it). Publishing\n copies it onto the row `list_conditional_triggers` shows.\n `create_conditional_trigger` is not the normal path, and the platform\n refuses a check sent to it or to `update_conditional_trigger`: \"A\n conditional start's check is set on the start in the workflow, and takes\n effect when the workflow is published.\"\n- **A conditional start without a CHECK starts the workflow every beat.** It\n wakes on its interval and, with no `checkKind`, runs regardless of whether\n there was anything to do \u2014 a mailbox on fifteen minutes then makes\n ninety-six empty runs a day. Read `checkKind` in\n `list_conditional_triggers` before assuming a start has one.\n- `checkKind: \"automatic\"` decides with no model, before a run exists. Its\n settings on `config` are `plan` (the steps of an automation step, each\n with a `toolId`), `outputs` (what the plan produces \u2014 not worked out by\n an agent) and `startsWhen` (rules `{ field, op, value }` of a decision's\n rule table, where `field` names one of those outputs). A look that cannot\n be made \u2014 a broken tool, a rule on an output nothing produces \u2014 is a fault,\n never a start.\n- `checkKind: \"agent\"` is for a condition that needs judgement: the beat\n becomes a run whose first step asks the agent named by `config.agentId`\n (else the workflow's owner); it stays out of the Runs list until the answer\n is yes. Settings: `agentId`, `toolIds`, `inputs`, `reasoningLevel`.\n- Do not pre-validate a check here. `validate_workflow` judges it\n (`conditional_check_invalid`) with the same function the dispatcher runs\n when the start fires, and the refusal says what to change.\n- **Removing `checkKind` does NOT take a check off.** A `config` without it\n is read as a start written before checks existed, and the row keeps the\n check it has. There is no way to return a start to \"every beat\" today; say\n so to your human rather than looking for a route to the row.\n- Publishing also switches every start of the workflow back ON. An\n `update_conditional_trigger({ enabled: false })` lasts until the next\n publish of that workflow.\n\n## Evaluating what you built\n- There is no findings endpoint and no evaluator entity. To judge a run, read it:\n `read_run` (its `validatorVerdicts` and `agentInvocations`) and\n `read_run_trace` (the step-by-step tool calls).\n- **Start with `read_run_outline`, not `read_run`.** The outline is the run's\n table of contents and carries no bodies, so it costs what the run's SHAPE\n costs rather than its content \u2014 a fan-out over four thousand items reads about\n as cheaply as one over four. Then open only what you need:\n `read_run_value` for one large step value (by the digest in `stepStates`),\n `read_run_invocation_messages` for one agent's transcript (by the id in\n `agentInvocations`). Reading a whole run to find one message is the habit\n this replaces.\n- `itemCap` on the outline bounds items listed per fan-out step; the remainder\n is counted in `counts.truncated`, never dropped in silence.\n- **What an agent wrote to itself is readable.** `list_run_session_memory`\n lists the files an agent left during a run (metadata only) and\n `read_run_session_memory_file` opens one. When the trace shows a decision but\n not what it was reading, this is usually where the reason is. Workflow-bound\n reference material is NOT here \u2014 that lives in `reference_docs`.\n- **`list_todo_steps`** is every step in the tenant waiting on a human, across\n all runs; `read_run_waiting_on` is the same question for one run you already\n have. It is PAGED over the waiting RUNS, so `items` can be longer than\n `pageSize` \u2014 one run may park several steps. Follow `nextCursor` until it\n is null before concluding anything about how much is waiting.\n- Runs are the only thing with a real archive state \u2014 `archive_run` is\n reversible, `bulk_delete_runs` is not.\n\n## Subworkflows \u2014 one workflow calling another\nBoth halves live in the workflow DOCUMENT, so both are ordinary\n`apply_workflow_mutations` work. There is no separate \"link\" route.\n- **To make a workflow callable**, it needs TWO things: a trigger with\n `typeId: \"subprocess-invocation\"` (`add_trigger`), which is what makes it\n callable at all, and a declared SIGNATURE.\n- **The signature belongs to the PROCESS, not to the trigger.** Declare it with\n `set_workflow_inputs` \u2014 one list, carried by every start, and the same list\n a caller of any kind fills in. A trigger's own `parameters` are what THAT\n start declares on top; `parameters` goes in the `add_trigger` call itself,\n so a start that does add fields needs no follow-up `update_trigger`.\n- Older documents declared everything on the invocation trigger instead, and\n that still works: both are read, and where a name appears in both the\n PROCESS-level one wins. When you are editing a workflow you did not author,\n read `read_workflow_trigger_parameters` and see which list actually holds\n its fields before adding to either.\n- `set_workflow_inputs` REPLACES the whole list, like\n `set_workflow_constants`: read, edit, write back. An entry with no `name`\n is refused rather than stored \u2014 the name is how a caller passes the value and\n how the run payload keys it, so a nameless one would be skipped silently at\n run time.\n- For what an input or a parameter may contain, ask\n `get_workflow_authoring_spec` for the payload schema of the command you are\n about to write, rather than a shape written down here.\n- **It must also be PUBLISHED.** The catalogue reads the published document, so\n a draft-only workflow comes back `callable: false` with \"Never published\".\n Authoring the caller first and publishing the callee later is a run-time\n failure waiting to happen.\n- **To call it**, add a step with `type: \"subprocess\"` and\n `config: { targetWorkflowId, ownerAgentId }` \u2014 both required, both workflow/\n agent ids. Bind the step's `inputs` by NAME to the callee's `parameters`;\n that is the whole mapping, there is no separate argument block.\n- `config.waitForCompletion` is **IGNORED** if you write it. Drawing a step\n after this one already means \"after\", so waiting is implied. The one thing the\n graph cannot say is fire-and-forget: `config.fireAndForget: true`, and then\n nothing downstream may depend on the result.\n- `config.errorStrategy` (\"fail\" | \"skip\") is only honoured when the step\n ITERATES (`config.iteration.enabled`). On a single call \"skip\" would report\n a failed child as success, so it is forced to fail-fast.\n- **A workflow cannot call itself** \u2014 every run would spawn a child that reaches\n the same step again. Pass `exclude: <the workflow you are editing>` to\n `list_callable_workflows` and it is kept out of the list.\n- **What comes back** is the callee's `outcomes`: its end steps, each with the\n fields bound into it. WHICH end was reached is itself part of the answer.\n- **`validate_workflow` checks this now.** A missing target, a workflow\n calling itself, a target that is deleted, never published, or has no\n invocation trigger \u2014 each has its own issue code, and the ones that resolve to\n nothing are errors that block publishing. Still run\n `list_callable_workflows` BEFORE you author the step: it is how you pick a\n target and see the interface you are binding to, where the validator only\n tells you afterwards that the one you picked was wrong.\n\n## Secrets\n- Never handle a real credential. A connector's `authConfig` takes placeholders\n only (\"{{ MY_SECRET }}\" or \"\"); a human fills the real value in Axonity. This\n is enforced \u2014 a write carrying something that looks like a real secret is\n rejected before it leaves this connector.\n- You CAN read the catalogue: `list_secrets` (id, name, authType) and\n `read_secret`. That is how you get the id for a connector's\n `authConfig.secretId` \u2014 do not ask a human to copy one out of the UI.\n- Before you wire a connector to a secret, check `valueKeys` on\n `read_secret`. An empty list means nobody has filled the secret in yet: the\n connector will author fine and fail at RUN time. Say so to your human, with the\n secret's name, rather than reporting the wiring as done.\n- You cannot create, change or delete a secret. That is a human act in Axonity \u2192\n Settings \u2192 Secrets, and the backend refuses it from this connector's token.\n- A secret's `metadata` (connection config, e.g. a session_cookie handshake)\n comes back with credential-shaped entries withheld, listed under\n `metadataRedacted`. If you need one of those values, a human reads it in\n Axonity \u2014 do not try another route to it.\n\n## Tables \u2014 the tenant's own reference data\nA table is an authored library element that HOLDS ROWS: the pricing bands, the\nregion-to-owner map, the list of accepted reason codes. A person designs the\ncolumns and maintains the content; agents and decisions read it.\n\n**When a table is the right element.** Reach for one when the CONTENT is the\npoint and it changes without the process changing.\n- Not an `output_schema`: that describes a SHAPE and holds no data. If you\n find yourself putting example values in a schema, you wanted a table.\n- Not a workflow constant: a constant belongs to ONE workflow and is edited in\n its document. A table is shared, and three workflows reading the same rows is\n the case it exists for.\n- Not a function tool with a list inside it: that makes changing a row an act of\n editing code, reviewing it and publishing a tool. A business user maintains a\n table; nobody should need you to change a postcode.\n\n**Static or dynamic \u2014 who owns the rows.**\n- `isDynamic: false` (the default) is reference data the AUTHOR maintains. A\n workflow reads it and cannot change it. `allowedOperations` must be empty:\n a permission nothing honours is refused, not stored.\n- `isDynamic: true` lets a workflow change the rows, limited to\n `allowedOperations` \u2014 any of `add` / `update` / `delete`. READING IS\n NEVER LISTED there: it is what a table is for and is always allowed.\n- You edit rows through `add_data_table_row` / `update_data_table_row` /\n `delete_data_table_row` whatever the table's mode says \u2014 those grants are\n about what a RUN may do, not about authoring.\n\n**Never put rows in `update_data_table`.** `rows` and `columns` are\nwhole-collection fields: sending one row REPLACES the table's content and\ndeletes everything else. The row tools address a single row by a match\n(`matchColumn`/`matchValue`) and cannot touch the rest \u2014 use them. Reach for\n`update_data_table({ fields: { rows } })` only when you genuinely mean to\nreplace the whole content, and never as a way to append.\n\n**The rule that bites hardest: derived tools follow the PUBLISHED table.** A\npublished table mints its own CRUD tools, so granting an agent access to one is\nordinary tool granting \u2014 take a `toolId` from `list_data_table_tools` and put\nit in the agent's `toolIds`. But those tools follow the table's PUBLISHED\nversion. An author who ticked \"may add rows\" and has not published has granted\nnothing, and the tool that would let a run act on it does not exist yet.\n`list_data_table_tools` reports `offered` (what the draft would yield),\n`toolId` (whether a row exists) and `isLive` (whether a run can reach it)\nseparately for exactly this reason. Read all three before concluding a grant\nlanded. Same rule as everywhere else here \u2014 a draft changes nothing until it is\npublished \u2014 and it is the first thing an agent gets wrong about tables.\n\n**Routing a decision on a table.** A decision can branch by looking a value up\nin one column and taking the branch named in another, with\n`builtin: \"evaluators.table_lookup\"`. The behaviour worth knowing, because no\ncatalogue states it: THE CELL'S VALUE IS THE BRANCH'S OUTCOME KEY \u2014 not a new\nfield, and not something you map afterwards. Every value in the result column\nmust be the outcome key of an outgoing branch, or publishing the workflow is\nrefused. The table must be PUBLISHED: a run reads the published snapshot, so a\ntable that is only saved routes nothing. For the argument names it reads, and\nfor every other selectable evaluator, read `decisionEvaluators` from\n`get_workflow_authoring_spec` \u2014 generated from the registry that dispatches\nthem, so it cannot advertise one the platform would not run.\n\n**Column kinds** come from the same place: `tableColumnKinds` in\n`get_workflow_authoring_spec`. It is a FOURTH vocabulary beside the three for\nparameters, outputs and schema fields, with its own spellings \u2014 do not carry a\nvalue across from one of the others, and do not guess. Every column needs a\n`description`: an agent reads it to know what the column is for, and a save\nwithout one is refused.\n\n**`list_data_tables` is PAGED**, alone among the library lists \u2014 a tenant's\nreference data has no ceiling. Page one is not the library. Follow\n`nextCursor` while `hasMore` is true, or better, narrow in the query:\n`name` is exact and unique per workspace, so \"does one called X already\nexist\" is a single call rather than a walk. The page carries `rowCount`, never\nthe rows; the rows come from `read_data_table`.\n\n## Toolboxes \u2014 where a tool is filed\n- A toolbox is a per-tenant grouping label over tools. Read `list_toolboxes`\n before `create_tool` and pass the box's id as `toolboxId`; a tool created\n without one is ungrouped. Move an existing tool with\n `assign_tool_toolbox` (`toolboxId: null` takes it out of its box).\n- `set_toolbox_tools` DECLARES the membership \u2014 it does not add to it. A tool\n in the box and absent from your list is evicted. To add one tool, read the\n box's contents first, or use `assign_tool_toolbox`, which cannot evict\n anything by accident. A tool is in at most one box.\n- A toolbox NEVER changes what an agent may call. An agent is linked to\n individual tools (`agent.toolIds`), never to a box; there is no route that\n grants a whole box, and \"give this agent the Carerix toolbox\" is a bulk write\n of individual links. What the box decides is how a tool is ADVERTISED: a boxed\n tool with `surfacing: \"inherit\"` is found via discover_tools instead of\n riding in every prompt. Read `effectiveSurfacing` on the tool to answer \"is\n it in the prompt?\"; `surfacing` is the raw stored choice.\n- Deleting a toolbox does NOT delete its tools \u2014 they survive, ungrouped. The\n box itself has no restore.\n- Platform-shipped tools cannot be moved. The `Axonity`, `Checks` and\n `Branching` boxes hold locked tools; the API refuses and NAMES the tool it\n refused. Read that name to your human instead of bisecting the selection.\n- `set_toolbox_auth` sets the credential a box's tools SHARE \u2014 ten Microsoft\n Mail tools, one mailbox, said once. A tool with its own auth keeps it; only a\n tool with none inherits. Same rule as anywhere else: point `secretId` at a\n stored secret, never a real value. Before you clear or replace it, or delete\n the box, call `list_toolbox_dependent_tools` \u2014 it names the tools that would\n start failing at run time with a bare 401.\n\n## Who may read what\nEvery policy, skill, reference doc and table carries a label: `level`\n(public, internal, confidential) and `audienceIds`, whom it is meant for. An\nagent and a workflow carry a `clearance`; so does your token, and you read\nwith it. Look audience ids up with `list_audiences` \u2014 never guess one, and\ncompare on id, never on name. `level: null` makes a thing readable by nobody.\nNarrowing a label is always allowed; widening it is a colleague's act, never\nyours.\n\n## Tenant\n- Your token fixes the tenant. You cannot act across tenants; never send a\n tenant id in a body.\n- `read_model_tier_map` says what `capabilityTier` actually RESOLVES TO on\n this tenant. The tier is the field you write; the model is what you are\n choosing. Read it before you pick one.\n- When a run does not start, look outward before blaming the workflow:\n `read_concurrency_status` (is the tenant at its cap?),\n `read_queue_overview` (is dispatch alive?), `list_task_queue` (did\n something fail in the layer beneath the run?). `read_for_each_rate` explains\n a fan-out that is pacing itself rather than stalling.\n- You can READ those and not change them. Caps, rate ceilings and the tier map\n are operator decisions \u2014 report what you found, and to whom, rather than\n looking for another route to it.\n- `read_deploy_contract` says what THIS deploy speaks: build, environment and\n the routes it mounts. Read it when a call fails in a way that suggests the\n backend is older or newer than you expect.\n- `list_users` is where an `ownerId` comes from. Look one up instead of\n asking a human to copy it out of the UI.\n- `list_audit_events({ actorKind: \"service_token\" })` reads back what external\n agents \u2014 including you \u2014 actually changed.\n";
|
|
52
52
|
/** Where this deploy serves its prompt-element placement rules. */
|
|
53
53
|
export declare const PLACEMENT_ROUTE = "/api/v1/authoring/prompt-placement";
|
|
54
54
|
/** Where this deploy serves its rules for writing the semantic layer. */
|
|
@@ -62,7 +62,7 @@ export declare function readPlacementRules(client: AxonityClient): Promise<strin
|
|
|
62
62
|
* the spec does not say — where a calculation belongs, and how a number made
|
|
63
63
|
* of parts is computed — so an agent does not have to infer it from recipes.
|
|
64
64
|
*/
|
|
65
|
-
export declare const MEASURE_FORMULA_SECTION = "## A measure is one formula\n\nThe words of a formula \u2014 functions, operators, grains, limits, every refusal\ncode with its fix, and recipes \u2014 are in `get_measure_formula_spec`. Read it\nbefore writing a measure, and never write a word from memory. With\n`includeWorkspace: true` it also lists this workspace's measures, tables,\ncolumns and the dimensions each table reaches.\n\n**Four kinds, one grammar.**\n\n- **Base** \u2014 one aggregation over one column of one table, optionally with\n `where`. It reads a column, never a calculation per row.\n- **Derived** \u2014 arithmetic over other measures by name: a sum over two facts,\n a margin, a share of the total with `ignore`.\n- **Time** and **cumulative** \u2014 the same measure in another window, or a\n running total, measured in a calendar of the workspace's date table.\n\n**Time runs over one date table.** One table per workspace is the date table\n(`dateTable: true`, a row per day), and its `calendars` say which column\nholds the year, quarter, month, week and day \u2014 a production calendar is just\nanother calendar. Every fact reaches it through its own time axis, so a week\nmeans the same thing to each of them. A series runs over the calendar's\nperiods: a period without rows is shown empty, not left out. A window's total\nis the window ending at the end of the period asked for; a running total per\nmonth is the standing at the end of each month, never their sum. A fact kept\nper month is not asked per week (`grain_too_fine`).\n\n**Row logic belongs in the
|
|
65
|
+
export declare const MEASURE_FORMULA_SECTION = "## A measure is one formula\n\nThe words of a formula \u2014 functions, operators, grains, limits, every refusal\ncode with its fix, and recipes \u2014 are in `get_measure_formula_spec`. Read it\nbefore writing a measure, and never write a word from memory. With\n`includeWorkspace: true` it also lists this workspace's measures, tables,\ncolumns and the dimensions each table reaches.\n\n**Four kinds, one grammar.**\n\n- **Base** \u2014 one aggregation over one column of one table, optionally with\n `where`. It reads a column, never a calculation per row.\n- **Derived** \u2014 arithmetic over other measures by name: a sum over two facts,\n a margin, a share of the total with `ignore`.\n- **Time** and **cumulative** \u2014 the same measure in another window, or a\n running total, measured in a calendar of the workspace's date table.\n\n**Time runs over one date table.** One table per workspace is the date table\n(`dateTable: true`, a row per day), and its `calendars` say which column\nholds the year, quarter, month, week and day \u2014 a production calendar is just\nanother calendar. Every fact reaches it through its own time axis, so a week\nmeans the same thing to each of them. A series runs over the calendar's\nperiods: a period without rows is shown empty, not left out. A window's total\nis the window ending at the end of the period asked for; a running total per\nmonth is the standing at the end of each month, never their sum. A fact kept\nper month is not asked per week (`grain_too_fine`).\n\n**Row logic belongs in the query that fills the table, not in a measure.** A\nvalue per row (net = gross \u2212 discount) is a column, added to that query's\n`sql` and described like any other column. A measure is computed\nper question \u2014 period, grain, split, filters \u2014 and is never stored.\n\n**How a number is computed.** Each leaf is aggregated on its own table, over\nthe dimensions it reaches through declared many-to-one relationships. The\nresults are combined per group; two fact tables are never joined to each\nother. The formula is then applied per group, and the TOTAL is computed again\nover the whole period \u2014 never the sum or the average of the groups.\n\n**Refused, never guessed.** A dimension a leaf cannot reach, a measure that\nrests on itself, euros added to dogs, a format that does not fit what the\nnumber counts: each is refused with a code. `validate_measure` returns them\nbefore you save; repair on the code before asking a person.\n\n**Explain a number by its parts.** `query_measures` with `explain: true`\ngives what each part of the formula answered, on the total and per period and\ngroup. Use it to say why a number moved, instead of reading rows.\n\n**Order.** Base measures first, each with its `quantity`; then the derived\nones; `validate_measure` before every save; publish from the bottom up \u2014 a\npublished measure over an unpublished one is refused. A measure knows what\nrests on it (`usedBy`, `dashboards` on `read_measure`): read that before\nyou change or delete one, because those numbers move with it.\n";
|
|
66
66
|
/** Read this deploy's semantic-knowledge rules, or say why not. Exported for testing. */
|
|
67
67
|
export declare function readSemanticKnowledgeRules(client: AxonityClient): Promise<string>;
|
|
68
68
|
export declare function registerConventions(server: McpServer, client: AxonityClient): void;
|