@cxtms/cx-schema 1.9.259 → 1.9.260
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/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/workflowValidator.d.ts +7 -0
- package/dist/workflowValidator.d.ts.map +1 -1
- package/dist/workflowValidator.js +54 -0
- package/dist/workflowValidator.js.map +1 -1
- package/package.json +1 -1
- package/schemas/actions/sound.json +4 -3
- package/schemas/actions/vibrate.json +6 -4
- package/schemas/components/README.md +1 -0
- package/schemas/components/avatar.json +117 -1
- package/schemas/components/infoLine.json +82 -1
- package/schemas/components/planner.json +369 -1
- package/schemas/components/progressBar.json +115 -1
- package/schemas/fields/attachment.json +35 -2
- package/schemas/workflows/agent/agent.json +98 -0
- package/schemas/workflows/tasks/attachment.json +58 -2
- package/schemas/workflows/workflow.json +14 -19
- package/skills/cxtms-developer/ref-entity-notification.md +2 -2
- package/skills/cxtms-developer/ref-entity-shared.md +6 -2
- package/skills/cxtms-module-builder/SKILL.md +83 -5
- package/skills/cxtms-module-builder/ref-components-data.md +1 -1
- package/skills/cxtms-module-builder/ref-components-display.md +98 -15
- package/skills/cxtms-module-builder/ref-components-forms.md +27 -1
- package/skills/cxtms-module-builder/ref-components-interactive.md +3 -3
- package/skills/cxtms-module-builder/ref-components-layout.md +1 -1
- package/skills/cxtms-workflow-builder/SKILL.md +3 -1
- package/skills/cxtms-workflow-builder/ref-agent.md +283 -0
- package/skills/cxtms-workflow-builder/ref-communication.md +37 -10
- package/templates/workflow-agent.yaml +48 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Agent Workflow YAML Reference
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- When to use `workflowType: Agent`
|
|
5
|
+
- Agent top-level structure and the full `agent:` property table
|
|
6
|
+
- Chat display metadata (`agent.ui`)
|
|
7
|
+
- Inputs: how they become the first message, and the tool schema seen by callers
|
|
8
|
+
- The built-in `set_result` tool and `agent.result`
|
|
9
|
+
- The `__session` override input
|
|
10
|
+
- Outputs: `result`, `transcript`, `sessionId`
|
|
11
|
+
- Sessions: how an agent is invoked — a workflow task vs. the Responses API chat
|
|
12
|
+
- Tool approval (`tools[].mode`) and the chat's Ask/Auto approval mode
|
|
13
|
+
- Built-in data tools (`tools[].builtin`): `data.query`, `data.schema`, `data.type`
|
|
14
|
+
- History compression
|
|
15
|
+
- Session ownership and live events
|
|
16
|
+
- Triggers: synchronous execution and lock behavior for trigger-bound agents
|
|
17
|
+
- The `ai.default` organization config shape
|
|
18
|
+
- Tool name derivation (workflow name → tool name)
|
|
19
|
+
- Best practices
|
|
20
|
+
- AGT_001–AGT_015 validation codes and one-line fixes
|
|
21
|
+
|
|
22
|
+
Agent workflows wrap an LLM agent that reasons over a system prompt, calls other workflows as tools, and returns a structured result. Use `workflowType: Agent` in the workflow section. Scaffold with `npx cxtms create workflow <name> --template agent`.
|
|
23
|
+
|
|
24
|
+
## When to Use `workflowType: Agent`
|
|
25
|
+
|
|
26
|
+
Use Agent when the task needs judgment calls over unstructured or ambiguous input — triage, summarization, exception handling, freeform Q&A — rather than a fixed sequence of steps. If the logic is deterministic (same inputs always produce the same steps), use a standard workflow (`activities`) instead. Agent workflows cannot have `activities`; all behavior comes from `instructions`, `tools`, `agents`, `mcp`, and the model's own reasoning.
|
|
27
|
+
|
|
28
|
+
## Top-Level Structure
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
workflow:
|
|
32
|
+
workflowId: "<uuid>"
|
|
33
|
+
name: "Agent Workflow Name"
|
|
34
|
+
workflowType: Agent # Required - identifies this as an Agent workflow
|
|
35
|
+
executionMode: Sync # Required - Agent workflows must be Sync
|
|
36
|
+
isActive: true
|
|
37
|
+
|
|
38
|
+
agent: # Required (replaces activities)
|
|
39
|
+
instructions: "..." # Required
|
|
40
|
+
ui: { ... } # Optional - how the AI Assistant chat shows the agent
|
|
41
|
+
model: { ... }
|
|
42
|
+
session: { ... }
|
|
43
|
+
result: { ... }
|
|
44
|
+
skills: [...]
|
|
45
|
+
tools: [...]
|
|
46
|
+
mcp: [...]
|
|
47
|
+
agents: [...]
|
|
48
|
+
|
|
49
|
+
inputs: [...] # Becomes the agent's first message
|
|
50
|
+
# outputs: not needed — result/transcript/sessionId are fixed and always produced
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Agent Section — Property Reference
|
|
54
|
+
|
|
55
|
+
| Property | Type | Default | Description |
|
|
56
|
+
|----------|------|---------|-------------|
|
|
57
|
+
| `description` | string | — | What this agent does. Shown to callers and to other agents that may invoke it (e.g. as the tool description when this workflow is listed in another agent's `tools[]`). |
|
|
58
|
+
| `instructions` | string (required, `minLength: 1`) | — | System instructions. A Handlebars template evaluated over workflow variables and inputs, same as other template expressions in this schema. |
|
|
59
|
+
| `ui` | object | — | Display metadata for the AI Assistant chat; no runtime effect. `additionalProperties: false`. See [Chat display metadata](#chat-display-metadata-agentui). |
|
|
60
|
+
| `ui.name` | string | workflow name | Display name. |
|
|
61
|
+
| `ui.shortDescription` | string | — | One line under the name. |
|
|
62
|
+
| `ui.icon` | string | `robot` | Tabler icon name without the `tabler-` prefix (e.g. `map-pin`). |
|
|
63
|
+
| `ui.color` | string, enum `primary` \| `secondary` \| `info` \| `success` \| `warning` \| `error` | `primary` | Theme palette color of the agent's icon. Case-sensitive. |
|
|
64
|
+
| `ui.prompts` | array of strings (`maxItems: 5`) | — | Suggested prompts on an empty chat. |
|
|
65
|
+
| `model` | object | — | Model selection. `additionalProperties: false`. |
|
|
66
|
+
| `model.fromConfig` | string | `ai.default` | Organization config name holding `provider`, `model`, `apiKey`, `endpoint`. |
|
|
67
|
+
| `model.name` | string | — | Overrides the config's model name for this workflow only. |
|
|
68
|
+
| `model.temperature` | number (`0`–`2`) | — | Sampling temperature. |
|
|
69
|
+
| `model.contextWindow` | integer (`>= 1`) | `256000` when unset | The model's context window in tokens, used to decide when history is compressed (see [History compression](#history-compression)). Set it when `model.name` overrides the config's model — the organization config's own `contextWindow` (if any) describes *its* model, not the override. Falls back to the resolved model config's window, then to the runtime default of 256,000 tokens. |
|
|
70
|
+
| `session` | object | — | Session behavior. `additionalProperties: false`. |
|
|
71
|
+
| `session.type` | string, enum `task` \| `chat` | `task` | Documentation only — **this field has no runtime effect.** The caller decides the actual session type: a workflow run (direct invocation, a trigger, another workflow calling this one as a tool) always runs a `task` session; a conversation held over the Responses API (`POST .../ai/responses`) always runs a `chat` session. See [Sessions](#sessions-how-an-agent-is-invoked). |
|
|
72
|
+
| `session.maxTurns` | integer (`1`–`100`) | `20` | Maximum agent turns before the session is forced to end. |
|
|
73
|
+
| `session.timeout` | integer (`>= 1`) | `300` | Session timeout in seconds. |
|
|
74
|
+
| `result` | object (JSON Schema, `type` required and must be `object`) | — | The JSON Schema the `set_result` tool's argument must satisfy. Defines the shape of the `result` output. |
|
|
75
|
+
| `skills` | array of strings | — | Installed skill names to enable. Runtime support ships in a later release; safe to declare now. |
|
|
76
|
+
| `tools` | array of objects | — | Tools the agent may call: other workflows (`workflow`) or built-in data tools (`builtin`). Each entry has exactly one of the two. |
|
|
77
|
+
| `tools[].workflow` | string (`minLength: 1`) | — | Workflow name or `workflowId` to expose as a tool. Exactly one of `workflow`/`builtin` per entry. |
|
|
78
|
+
| `tools[].builtin` | string, enum `data.query` \| `data.schema` \| `data.type` | — | A built-in, read-only data tool — see [Built-in data tools](#built-in-data-tools-toolsbuiltin). Exactly one of `workflow`/`builtin` per entry. |
|
|
79
|
+
| `tools[].instructions` | string | — | When and how the agent should use this tool — folded into the tool's description for the model. |
|
|
80
|
+
| `tools[].mode` | string, enum `auto` \| `approval` \| `always` | `auto` | `auto` runs the tool as soon as the model calls it. `approval` pauses the call for a person unless the chat is in Auto mode. `always` pauses for a person in every chat — see [Tool approval](#tool-approval-toolsmode). |
|
|
81
|
+
| `mcp` | array of objects | — | Outbound MCP server connections. Runtime support ships in a later release. |
|
|
82
|
+
| `mcp[].fromConfig` | string (required) | — | Organization config name for the MCP connection. |
|
|
83
|
+
| `agents` | array of objects | — | Allow list of other agents this agent may invoke. Runtime support ships in a later release. |
|
|
84
|
+
| `agents[].agent` | string | — | Name of another Agent workflow in this organization. Exactly one of `agent`/`url` is required per entry. |
|
|
85
|
+
| `agents[].url` | string (`format: uri`) | — | Remote A2A agent card URL. Exactly one of `agent`/`url` is required per entry. |
|
|
86
|
+
| `agents[].modes` | array of strings, enum `task` \| `chat` | — | Which session modes this agent may be invoked in. |
|
|
87
|
+
|
|
88
|
+
## Chat Display Metadata (`agent.ui`)
|
|
89
|
+
|
|
90
|
+
The AI Assistant lists an organization's agents from `GET .../ai/models`, which includes each agent's
|
|
91
|
+
`agent.description` and `agent.ui`. Set `ui` on any agent people will chat with:
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
agent:
|
|
95
|
+
description: Looks up shipment status, ETAs and exceptions across carriers.
|
|
96
|
+
ui:
|
|
97
|
+
name: Tracking Agent # optional; defaults to the workflow name
|
|
98
|
+
shortDescription: Status, ETAs, exceptions, POD
|
|
99
|
+
icon: map-pin # Tabler icon name without prefix
|
|
100
|
+
color: info # primary | secondary | info | success | warning | error
|
|
101
|
+
prompts: # at most 5
|
|
102
|
+
- Which shipments are delayed today?
|
|
103
|
+
- What is the ETA for order ORD-1?
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Keep `shortDescription` to a few words and write `prompts` as questions a user would actually type —
|
|
107
|
+
each is sent as-is when clicked.
|
|
108
|
+
|
|
109
|
+
## Inputs: First Message and Tool Schema
|
|
110
|
+
|
|
111
|
+
`inputs:` serves two roles for an Agent workflow:
|
|
112
|
+
|
|
113
|
+
1. **Direct invocation** — when the workflow is run directly (API, scheduler, another workflow via `Workflow/Execute@1`), the input values are rendered into the agent's first user-turn message, alongside anything referenced by `instructions`.
|
|
114
|
+
2. **Invocation as a tool** — when this workflow appears in another Agent workflow's `tools[]` (or `agents[]`), `inputs` (name, `type`, `props.required`, `props.description`) is converted into the JSON Schema tool-call signature the calling agent sees. Keep input `props.description` populated — it becomes the parameter description the calling model reads to decide how to fill the tool call.
|
|
115
|
+
|
|
116
|
+
## `set_result` and `agent.result`
|
|
117
|
+
|
|
118
|
+
A **task** session (a workflow run) gets a built-in `set_result` tool at runtime — you don't declare it under `tools`. Its argument schema is exactly `agent.result` (must be `type: object`). Calling `set_result` ends the session and populates the `result` output with the call's argument — this is how an agent invoked as a workflow (directly, by a trigger, or as another agent's tool) reports back. A **chat** session (a conversation held over the Responses API) is never given the `set_result` tool at all — write `agent.instructions`/`agent.result` for the task-session case; a chat-only agent doesn't need `agent.result` to be meaningful.
|
|
119
|
+
|
|
120
|
+
**Validation note:** at runtime, `set_result`'s argument is checked against `agent.result` by verifying only the top-level `required` array is satisfied — nested property types, formats, and other JSON Schema keywords in `agent.result` are not enforced when the tool is called. (`cxtms` still validates that `agent.result` itself is a well-formed JSON Schema with `type: object` at author time via `AGT_008`.)
|
|
121
|
+
|
|
122
|
+
## `__session` Override Input
|
|
123
|
+
|
|
124
|
+
Pass `__session` as an input (it does not need to be declared in `inputs:`) to override the agent's session limits for this one run. It is an optional object: `{ maxTurns, timeout }` (a `type` field is also accepted but not yet used by the runtime). `__session` is **not** a session id and does **not** resume or attach to a prior session — every run of an Agent workflow creates a brand-new session, whether or not `__session` is passed. Use it to tighten or relax `session.maxTurns`/`session.timeout` for a specific call site (e.g. a trigger-bound agent that needs a shorter timeout than the workflow's own `agent.session` default) without editing the workflow itself.
|
|
125
|
+
|
|
126
|
+
## Outputs
|
|
127
|
+
|
|
128
|
+
Outputs are **fixed** for Agent workflows — when the session completes via `set_result`, the engine always produces exactly these three, regardless of what (if anything) you declare under `outputs:`:
|
|
129
|
+
|
|
130
|
+
| Output | Description |
|
|
131
|
+
|--------|-------------|
|
|
132
|
+
| `result` | The argument the agent passed to `set_result`, matching `agent.result`'s schema. |
|
|
133
|
+
| `transcript` | The full turn-by-turn conversation log for the session (prompts, tool calls, tool results, model responses). |
|
|
134
|
+
| `sessionId` | The session identifier. |
|
|
135
|
+
|
|
136
|
+
If a `task` session ends **without** calling `set_result` — it hits `session.maxTurns`, `session.timeout`, or stops responding with tool calls after two nudges — the workflow **fails**: the engine raises an error naming the session id (e.g. `Agent session <sessionId> ended without a result: exhausted its turn budget (20)`), and there are **no** `result`/`transcript`/`sessionId` outputs in that case. Look up the `AgentSession` row by the session id in the error message to inspect the transcript of a failed run.
|
|
137
|
+
|
|
138
|
+
An `outputs:` section is **not required** for an Agent workflow — the scaffolded template omits it entirely, and `result`/`transcript`/`sessionId` are still produced when the session completes. The engine ignores `outputs:` for this workflow type: it does not consult it to decide what to produce. If you add an `outputs:` section anyway (e.g. to rename an output for a caller, or because a shared tool expects one), the normal `output.json` rule still applies — each entry still needs a `mapping` — but it has no effect on which outputs the Agent runtime actually populates.
|
|
139
|
+
|
|
140
|
+
## Sessions: How an Agent Is Invoked
|
|
141
|
+
|
|
142
|
+
The same `agent:` YAML backs two different ways of running an agent, and the caller — not `agent.session.type` — decides which one you get:
|
|
143
|
+
|
|
144
|
+
| | **Task session** | **Chat session** |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| Started by | A workflow run: direct invocation, a trigger, `Workflow/Execute@1`, or another agent's `tools[]`/`agents[]` | A conversation held over the OpenAI-Responses-compatible API (`POST /api/organizations/{id}/ai/responses`, or the public-api equivalent) |
|
|
147
|
+
| `set_result` tool | Registered; calling it ends the session and produces `result`/`transcript`/`sessionId` (see [Outputs](#outputs)) | Not registered at all |
|
|
148
|
+
| Ends when | `set_result` is called, or `session.maxTurns`/`session.timeout` is hit | The client stops sending turns, or `session.maxTurns`/`session.timeout` is hit — there is no `set_result` to end it early |
|
|
149
|
+
| Approval tools | Refused outright — see [Tool approval](#tool-approval-toolsmode) | Pause the conversation for a person to decide |
|
|
150
|
+
| Owner default | `Organization` (no person is present to default to) | `User` — the session's starter |
|
|
151
|
+
|
|
152
|
+
One agent workflow can be used both ways — e.g. an internal trigger runs it as a `task` to make an automated decision, while a support UI holds a `chat` conversation with the same agent. Design `instructions`/`tools`/`result` with whichever mode(s) you intend to use it in.
|
|
153
|
+
|
|
154
|
+
The Responses API, streaming, conversation resume (`previous_response_id`), and GraphQL session/transcript queries are documented in `docs/agent-api.md` in `tms-backend-api` (client/UI-facing) and `docs/agent-testing.md` (testing recipes, including a curl walkthrough of both session types) — this skill only covers the workflow YAML.
|
|
155
|
+
|
|
156
|
+
## Tool Approval (`tools[].mode`)
|
|
157
|
+
|
|
158
|
+
Mark a tool `mode: approval` when the model calling it unattended is a risk you don't want to take — cancelling a shipment, sending an external message, charging a card, anything destructive or hard to undo. `mode: auto` (the default) runs the tool the instant the model calls it; `mode: approval` never does, without a person's decision:
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
tools:
|
|
162
|
+
- workflow: "Orders / Get Status" # auto (default): runs immediately
|
|
163
|
+
- workflow: "Orders / Cancel Shipment"
|
|
164
|
+
mode: approval # waits for a person, unless the chat is in Auto mode
|
|
165
|
+
- workflow: "Invoices / Void"
|
|
166
|
+
mode: always # waits for a person in every chat
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**In a chat session:** the model calls the tool, the conversation pauses with an `mcp_approval_request` output item, and the session status becomes `AwaitingApproval`. A person (anyone who can see the session, per its ownership scope) approves or declines by continuing the conversation with a decision instead of new text. On approval, the tool runs and the agent continues; on decline, the model is told the user declined (and why) and carries on without running it. Sending an ordinary chat message instead of a decision declines every pending request automatically. Full request/response shapes are in `docs/agent-api.md` §8 in `tms-backend-api`.
|
|
170
|
+
|
|
171
|
+
**Approval mode (Ask / Auto):** each chat has an approval mode the user picks in the chat UI (sent as
|
|
172
|
+
`metadata.approval_mode`). In `Ask` (the default) every `approval` and `always` call pauses. In `Auto`,
|
|
173
|
+
`approval` calls run without pausing and are recorded on the transcript as approved by
|
|
174
|
+
`auto:<userId>`; `always` calls still pause. Use `always` for the few actions that must never run
|
|
175
|
+
without an explicit click, whatever the user's mode — voiding an invoice, charging a card.
|
|
176
|
+
|
|
177
|
+
**In a task session** (no person is present to ask): an approval tool is **refused** the moment the model calls it — the agent is told it needs human approval for that call and continues reasoning from there (typically escalating via `set_result` rather than completing the original action). Don't rely on `mode: approval` to gate a tool inside a task-only agent; a task session can never satisfy it. If an agent needs to run in both modes, write its `instructions` to handle the "this needs a person" outcome explicitly (see `AGT_010` below for the schema-level check on `mode`'s value; there is no schema check for whether an agent using `mode: approval` will ever run as a chat).
|
|
178
|
+
|
|
179
|
+
## Built-in Data Tools (`tools[].builtin`)
|
|
180
|
+
|
|
181
|
+
Three built-in tools let an agent read the organization's data through GraphQL without a workflow per question:
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
agent:
|
|
185
|
+
instructions: |
|
|
186
|
+
Answer questions about this organization's orders and shipments.
|
|
187
|
+
tools:
|
|
188
|
+
- builtin: data.schema
|
|
189
|
+
- builtin: data.type
|
|
190
|
+
instructions: "Look up field types before writing a query."
|
|
191
|
+
- builtin: data.query
|
|
192
|
+
- workflow: "Assistant / Cancel Shipment" # changing data still goes through a workflow tool
|
|
193
|
+
mode: approval
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| `builtin` | Tool name the model sees | What it does |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `data.query` | `data_query` | Runs one GraphQL **query** (`query`, optional `variables`, `operationName`) in the agent's organization and returns `{ data, errors? }`. |
|
|
199
|
+
| `data.schema` | `data_schema` | Lists query fields (`category: queries`, the default), types (`types`) or both (`all`), with an optional case-insensitive `filter`. Mutations are not listed. |
|
|
200
|
+
| `data.type` | `data_type` | Describes one type (`typeName`): fields, types, arguments, descriptions, enum values; an unknown name returns up to 5 suggestions. |
|
|
201
|
+
|
|
202
|
+
Guards, enforced by the backend: query operations only (mutations and subscriptions return `read_only` — change data with a workflow tool, ideally `mode: approval`); every root Query field must declare `organizationId` and pass the session's organization, except `currentUser`, `personalAccessToken`/`personalAccessTokens`, `hasUserSecret` and introspection (`__schema`/`__type`/`__typename`) — anything else is refused as `organization_scope` ("Field {name} is not scoped to an organization and cannot be queried."), which fails closed for new resolvers; `organizations` is refused too, even though it takes `organizationId` — its resolver ignores the argument; `exportRates` (uploads an export file) and `uploadUrl` (issues a presigned upload URL) take `organizationId` but have side effects, so they are refused as `read_only`, even in the session's organization — use a workflow tool for them; `organizationConfig`, `organizationConfigs`, `contactPaymentMethod`, `contactPaymentMethods`, `outboxMessages`, `deadLetterMessages` and `outboxStatus` hold secrets, payment data or internal infrastructure data and are refused as `restricted` ("Field {name} holds sensitive data and cannot be queried by agents."), whatever alias or fragment is used; inside `where:` filters, `organizationId` accepts only `{ eq: <org> }` or `{ in: [<org>] }`; selection depth at most 12 (`depth_exceeded`; `__schema`/`__type`-only queries are exempt); duplicate keys in `variables` are rejected as `syntax`; results over 64 KB are cut and marked `truncated` with a hint to page with `take`/`skip`. The runner also tells the model which organization it is in whenever a data tool is enabled.
|
|
203
|
+
|
|
204
|
+
`mode` and `instructions` work as for workflow tools. Built-ins run as the session user, so row-level security applies. A workflow tool whose derived name would collide with `data_query`/`data_schema`/`data_type` is suffixed (`data_query_2`).
|
|
205
|
+
|
|
206
|
+
## History Compression
|
|
207
|
+
|
|
208
|
+
Each chat turn adds to a growing conversation history, bounded by the model's context window. When the previous call's reported input-plus-output tokens reach **80% of `model.contextWindow`** (or the 256,000-token default — see the `model.contextWindow` row above), the runtime summarizes the older messages into a single `summary` transcript entry before the next turn, and the turn proceeds on the shorter history. This is automatic — nothing in `agent:` YAML opts in or out of it.
|
|
209
|
+
|
|
210
|
+
- The summary call is given at most half of the session's remaining time budget; if it fails or runs out, the turn just proceeds on the full, uncompressed history instead of failing the turn.
|
|
211
|
+
- A provider that reports no token usage never triggers compression (there's nothing to measure against the window).
|
|
212
|
+
- On the client side, the internal Responses route announces a compression with a `response.tms.history_compressed` stream event; the transcript (GraphQL) always shows the `summary` message and a `usage` entry with `kind: "summary"`, on both routes.
|
|
213
|
+
- If an agent frequently needs long conversations against a small model, either raise `model.contextWindow` to match the model actually in use (see the property table above) or keep `agent.instructions` terse so more of the window is available for turns.
|
|
214
|
+
|
|
215
|
+
## Session Ownership and Live Events
|
|
216
|
+
|
|
217
|
+
Every agent session (task or chat) has an owner scope — `User`, `Division`, or `Organization` — that governs who may see it, continue it, decide its approvals, and watch it live. A chat session defaults to `User` (its starter); a task session defaults to `Organization` (it has no starter). Ownership can be changed afterward (e.g. shared with a division) via a GraphQL mutation, and every session change publishes a live event.
|
|
218
|
+
|
|
219
|
+
A GraphQL subscription, `onAgentSessionEvent(organizationId, agentSessionId?, workflowId?)`, delivers coarse events (`StatusChanged`, `ApprovalRequested`, `ToolCallCompleted`, `HistoryCompressed`, `OwnerChanged`) for every session the caller may see — useful for an approvals inbox or a monitoring view that shouldn't poll. None of this is configured in `agent:` YAML; it's a property of every session the runtime creates. Full details, the subscription shape, and delivery guarantees are in `docs/agent-api.md` §§11–12 in `tms-backend-api`.
|
|
220
|
+
|
|
221
|
+
## Triggers
|
|
222
|
+
|
|
223
|
+
An Agent workflow can carry a `triggers:` entry (e.g. `type: Entity`) just like a standard workflow. When it fires, the agent session runs **synchronously inside the saving request** — the request that added/modified/deleted the triggering entity waits for the full agent session (potentially several model round-trips) before it can complete, for up to `session.timeout` (default 300s) — and it holds the workflow lock for that entity/organization for the whole duration.
|
|
224
|
+
|
|
225
|
+
For a trigger-bound agent, either:
|
|
226
|
+
|
|
227
|
+
- keep `session.timeout` short and `session.maxTurns` low, so a slow or looping agent can't stall the triggering request for long, or
|
|
228
|
+
- keep the trigger workflow itself lightweight (no `agent:` section) and have it invoke the Agent workflow asynchronously via `Workflow/Execute@1` with `executionMode: Async`, so the triggering request returns immediately and the agent runs out-of-band.
|
|
229
|
+
|
|
230
|
+
## The `ai.default` Organization Config
|
|
231
|
+
|
|
232
|
+
`model.fromConfig` (default `ai.default`) points at an organization config record shaped:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"provider": "anthropic",
|
|
237
|
+
"model": "claude-sonnet-4-5",
|
|
238
|
+
"apiKey": "...",
|
|
239
|
+
"endpoint": "https://api.anthropic.com",
|
|
240
|
+
"contextWindow": 200000
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Set up this config once per organization (or per named config for `model.fromConfig` overrides); every Agent workflow that doesn't override `model.name`/`model.temperature` shares it. The config's own `contextWindow` (if set) is the fallback used when the agent's YAML doesn't declare `model.contextWindow` — but only while the agent doesn't also override `model.name`; overriding the model name without also setting `agent.model.contextWindow` falls straight through to the runtime default of 256,000 tokens, since the config's window describes the config's model, not the override.
|
|
245
|
+
|
|
246
|
+
## Tool Name Derivation (Workflow Name → Tool Name)
|
|
247
|
+
|
|
248
|
+
When a workflow is exposed as a tool (via another agent's `tools[].workflow`, or via `workflowType: McpTool`), its display name is converted into a tool name matching `^[a-zA-Z0-9_-]{1,64}$`:
|
|
249
|
+
|
|
250
|
+
- Runs of one or more characters outside `[a-zA-Z0-9_-]` (spaces, `/`, punctuation) collapse to a single `_`. E.g. `"MCP / Get Order Status"` → `MCP_Get_Order_Status`.
|
|
251
|
+
- If two workflows collapse to the same tool name, later collisions get a numeric suffix: the second occurrence becomes `_2`, the third `_3`, and so on.
|
|
252
|
+
- Keep workflow names short and distinguishable after this substitution if you're exposing several as tools to the same agent — two names that only differ by punctuation will collide and get suffixed, which is harder for the model to reason about than a small rename up front.
|
|
253
|
+
|
|
254
|
+
## Best Practices
|
|
255
|
+
|
|
256
|
+
- **Put side-effecting tools under `mode: approval` in chat agents.** Anything destructive, external-facing, or hard to undo (cancel, charge, send, delete) should pause for a person rather than run the instant the model decides to call it. Read-only or easily-reversible tools (status lookups, previews) can stay `auto`. See [Tool approval](#tool-approval-toolsmode).
|
|
257
|
+
- **Use `mode: always` for actions a person must confirm every time.** `approval` tools run unattended once a user switches the chat to Auto; `always` tools never do.
|
|
258
|
+
- **Give chat agents `agent.ui`.** A `shortDescription`, an `icon`, a `color` and a few `prompts` make the agent recognizable in the AI Assistant's agent menu and empty state.
|
|
259
|
+
- **Set `model.contextWindow` whenever you set `model.name`.** Otherwise a smaller model than the org default silently gets the 256K default window, and history compression won't kick in until it's already over budget (or a larger model gets compressed too eagerly). See the `model.contextWindow` row above.
|
|
260
|
+
- **Design `agent.result`/`agent.instructions` around whichever session type(s) the agent is actually used in.** A chat-only agent doesn't need `agent.result` (it has no `set_result` tool); a task-only agent should tell the model explicitly to call `set_result` exactly once (the scaffolded template's instructions already do this).
|
|
261
|
+
- **Keep trigger-bound agents fast**, or move them off the triggering request entirely (see [Triggers](#triggers)) — a slow agent session holds the entity's workflow lock for its whole duration.
|
|
262
|
+
|
|
263
|
+
## Validation Codes (AGT_001–AGT_015)
|
|
264
|
+
|
|
265
|
+
These are backend validation codes; `cxtms` validates the same constraints client-side via `agent/agent.json` and `workflow.json` so you catch them before deploying.
|
|
266
|
+
|
|
267
|
+
| Code | Meaning | One-line fix |
|
|
268
|
+
|------|---------|---------------|
|
|
269
|
+
| `AGT_001` | `agent` section required | Add a top-level `agent:` section to the workflow. |
|
|
270
|
+
| `AGT_002` | `instructions` required | Add a non-empty `agent.instructions` string. |
|
|
271
|
+
| `AGT_003` | `executionMode` must be `Sync` | Set `workflow.executionMode: Sync`. |
|
|
272
|
+
| `AGT_004` | `activities` not allowed | Remove the `activities` property — Agent workflows can't have activities. |
|
|
273
|
+
| `AGT_005` | `session.type` must be `task` or `chat` | Set `agent.session.type` to `task` or `chat`. |
|
|
274
|
+
| `AGT_006` | `maxTurns` must be 1–100, `timeout` must be positive, `model.contextWindow` must be positive | Set `agent.session.maxTurns` to a value between 1 and 100, `agent.session.timeout` to a positive number of seconds, and `agent.model.contextWindow` (if set) to a positive number of tokens. |
|
|
275
|
+
| `AGT_007` | Retired — replaced by `AGT_013` | — |
|
|
276
|
+
| `AGT_008` | `result` must be a JSON Schema with `type: object` | Set `agent.result.type` to `object`. |
|
|
277
|
+
| `AGT_009` | `agents[]` needs exactly one of `agent`/`url`, and `modes` from `task`/`chat` | Give each `agent.agents[]` entry exactly one of `agent` or `url`, and only `task`/`chat` values in `modes`. |
|
|
278
|
+
| `AGT_010` | `tools[].mode` must be `auto`, `approval` or `always` | Set `agent.tools[].mode` to `auto`, `approval` or `always` (or omit it — `auto` is the default). |
|
|
279
|
+
| `AGT_011` | `ui.color` must be one of `primary`, `secondary`, `info`, `success`, `warning`, `error` | Set `agent.ui.color` to one of the six palette names, lowercase, or omit it. |
|
|
280
|
+
| `AGT_012` | `ui.prompts` has more than 5 entries | Keep at most 5 `agent.ui.prompts`. |
|
|
281
|
+
| `AGT_013` | a `tools[]` entry needs exactly one of `workflow`/`builtin` | Give each `agent.tools[]` entry either `workflow` (name or `workflowId`) or `builtin`, not both and not neither. |
|
|
282
|
+
| `AGT_014` | unknown `tools[].builtin` | Use `data.query`, `data.schema` or `data.type` (case-sensitive). |
|
|
283
|
+
| `AGT_015` | the same `builtin` listed twice | List each built-in tool once. |
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- Email/VerifyCode task (send and verify email verification codes)
|
|
6
6
|
- Document/Render task (render PDF or Excel from HTML templates)
|
|
7
7
|
- Document/Send task (send a previously rendered document)
|
|
8
|
-
- Attachment tasks (Create, Update, Thumbnail, PdfThumbnail, RegenerateThumbnails)
|
|
8
|
+
- Attachment tasks (Create, Update, Link, Unlink, Thumbnail, PdfThumbnail, RegenerateThumbnails)
|
|
9
9
|
- PdfDocument/Merge task (merge multiple PDFs into one)
|
|
10
10
|
|
|
11
11
|
## Email/Send
|
|
@@ -135,23 +135,50 @@ Sends a previously rendered document.
|
|
|
135
135
|
|
|
136
136
|
| Task | Description |
|
|
137
137
|
|------|-------------|
|
|
138
|
-
| `Attachment/Create` | Create file attachment on
|
|
139
|
-
| `Attachment/Update` | Update attachment
|
|
138
|
+
| `Attachment/Create@1` | Create a file attachment on a primary parent, optionally linked to more entities |
|
|
139
|
+
| `Attachment/Update@1` | Update attachment fields; changing `parentType`/`parentId` moves the primary link |
|
|
140
|
+
| `Attachment/Link@1` | Link an existing attachment to another entity (idempotent) |
|
|
141
|
+
| `Attachment/Unlink@1` | Remove a link; the primary link (current parent) can't be unlinked — re-parent instead |
|
|
140
142
|
| `Attachment/Thumbnail` | Generate image thumbnail |
|
|
141
143
|
| `Attachment/PdfThumbnail` | Generate PDF thumbnail |
|
|
142
144
|
| `Attachment/RegenerateThumbnails` | Regenerate all thumbnails |
|
|
143
145
|
|
|
146
|
+
One attachment can be linked to many Orders, Contacts, Jobs and TrackingEvents. `parentType`/`parentId` is the primary link; `links` and `Attachment/Link@1` add more. Link `entityType`: `Order`, `Contact`, `Job`, `TrackingEvent`; `entityId`: int id, uuid for Job. A missing link target fails the task.
|
|
147
|
+
|
|
144
148
|
```yaml
|
|
145
|
-
- task: "Attachment/Create"
|
|
146
|
-
name:
|
|
149
|
+
- task: "Attachment/Create@1"
|
|
150
|
+
name: CreatePhoto
|
|
151
|
+
inputs:
|
|
152
|
+
attachment:
|
|
153
|
+
fileName: "photo.jpg"
|
|
154
|
+
attachmentType: "Picture" # Picture, OtherDocument, Avatar, CustomerDocument
|
|
155
|
+
parentType: "Order"
|
|
156
|
+
parentId: "{{ orderId }}"
|
|
157
|
+
links: # optional
|
|
158
|
+
- entityType: "TrackingEvent"
|
|
159
|
+
entityId: "{{ trackingEventId }}"
|
|
160
|
+
fileData: "{{ fileData }}" # base64 / bytes / stream, or fileUrl: "{{ url }}"
|
|
161
|
+
outputs:
|
|
162
|
+
- name: attachment
|
|
163
|
+
mapping: "attachment"
|
|
164
|
+
|
|
165
|
+
- task: "Attachment/Link@1"
|
|
166
|
+
name: LinkToJob
|
|
147
167
|
inputs:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
attachmentId: "{{ Main.CreatePhoto.attachment.attachmentId }}" # activity "Main"
|
|
169
|
+
entityType: "Job"
|
|
170
|
+
entityId: "{{ jobId }}"
|
|
171
|
+
|
|
172
|
+
- task: "Attachment/Unlink@1"
|
|
173
|
+
name: UnlinkFromJob
|
|
174
|
+
inputs:
|
|
175
|
+
attachmentId: "{{ Main.CreatePhoto.attachment.attachmentId }}"
|
|
176
|
+
entityType: "Job"
|
|
177
|
+
entityId: "{{ jobId }}"
|
|
153
178
|
```
|
|
154
179
|
|
|
180
|
+
`Attachment/Link@1` and `Attachment/Unlink@1` output `attachment`.
|
|
181
|
+
|
|
155
182
|
## PdfDocument/Merge
|
|
156
183
|
|
|
157
184
|
Merges multiple PDF documents into one.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# {{displayName}} Agent
|
|
2
|
+
# Generated by cxtms create workflow --template agent
|
|
3
|
+
|
|
4
|
+
workflow:
|
|
5
|
+
workflowId: "{{uuid}}"
|
|
6
|
+
name: "{{displayName}}"
|
|
7
|
+
description: "{{displayName}} - AI agent"
|
|
8
|
+
version: "1.0"
|
|
9
|
+
workflowType: Agent
|
|
10
|
+
executionMode: Sync
|
|
11
|
+
logLevel: Information
|
|
12
|
+
isActive: true
|
|
13
|
+
enableAudit: true
|
|
14
|
+
filePath: "{{fileName}}"
|
|
15
|
+
|
|
16
|
+
agent:
|
|
17
|
+
description: "Describe what this agent does, for callers and other agents."
|
|
18
|
+
instructions: |
|
|
19
|
+
You are an operations analyst for this organization.
|
|
20
|
+
Load the order with the tool first, then reason about it.
|
|
21
|
+
When you have a decision, call set_result with { action, reason }.
|
|
22
|
+
model:
|
|
23
|
+
fromConfig: "ai.default"
|
|
24
|
+
# contextWindow: 200000 # set when overriding model.name below: the org config's window describes its own model, not this one
|
|
25
|
+
session:
|
|
26
|
+
type: task
|
|
27
|
+
maxTurns: 20
|
|
28
|
+
timeout: 300
|
|
29
|
+
result:
|
|
30
|
+
type: object
|
|
31
|
+
required: [action, reason]
|
|
32
|
+
properties:
|
|
33
|
+
action: { type: string }
|
|
34
|
+
reason: { type: string }
|
|
35
|
+
tools:
|
|
36
|
+
- workflow: "MCP / Get Order Status"
|
|
37
|
+
instructions: "Call first to load the order before reasoning."
|
|
38
|
+
- workflow: "MCP / Cancel Shipment"
|
|
39
|
+
instructions: "Cancels the order's shipment. Side-effecting — waits for a person to approve."
|
|
40
|
+
mode: approval
|
|
41
|
+
|
|
42
|
+
# Outputs are fixed for Agent workflows: result (set_result argument), transcript, sessionId. No outputs: section is needed.
|
|
43
|
+
inputs:
|
|
44
|
+
- name: orderNumber
|
|
45
|
+
type: string
|
|
46
|
+
props:
|
|
47
|
+
required: true
|
|
48
|
+
description: "The order to work on"
|