FlowAI - Agent Skills Guide
FlowAI lets you create AI agents that use LLMs (Anthropic, OpenAI, Google, Ollama, AWS Bedrock, Databricks, or platform-managed models) to autonomously operate the Itential Platform. Agents can call adapters, run workflows, and invoke IAG services — all driven by natural language instructions and a typed input contract.
This skill covers Agent Project Service, Model Registry Service, Tools Service, Agent Session Manager, Tool RPC, and (read-only) Agent Execution Engine — plus Work Items via the separate WorkCenter Service. See the API Reference below for each.
Several endpoints (notably Agent Project Service and Tools Service) declare an untyped {"type": "object"} success response in the OpenAPI spec — see Gotchas below for what this means in practice.
Customization
Before using this skill, check custom/org/, custom/team/, and custom/dev/
in this skill's own directory. Read every .md file found, in that order
(any folder may be empty or absent). Apply them in addition to everything
below — where a file overrides a specific rule from this document, prefer
the override; more specific wins (dev over team over org). See
.claude/CUSTOMIZATION.md for the full framework and what belongs in
which layer.
Verifying This Skill Against Your Platform
This skill is a map, not a substitute for checking the live API. Don't hardcode a field name or endpoint from memory when you can look it up in seconds:
- Pull the real spec and search it locally.
GET /help/openapi?url={ENCODED_BASE}(or reuse an already-pulledopenapi.json), thenjq '.paths["/agent-project-service/agents/{agentId}"]' openapi.json. The services in this skill live under/agent-project-service,/model-registry-service,/tools,/agent-session-manager,/tool-rpc, and/work-center-service— filter on those base paths. - Get a tool's live schema instead of assuming it.
GET /tools/{referenceId}always returns that tool's currentinputSchemaexactly as the LLM sees it — adapters and app methods change independently of this skill, so this call is the one source that's always current. - When the OpenAPI spec itself is untyped, call the endpoint and read the real response rather than trusting a shape in this skill as final — every response shape documented below was built that way, and your platform version may have moved on.
- Prefer real exported structures over hand-authored JSON.
GET /agent-project-service/project-bundles/{projId}/exporton any existing project returns a complete, valid Agent + Project payload straight from the platform — exporting something that already works and reading it is faster and more reliable than composing a bundle from memory. Two ready-made local references follow the same idea:helpers/create/create-flowagent-project-bundle.json— a structurally-correct starting template withREPLACE_*placeholders. Edit and import it rather than typing a bundle out from scratch.helpers/assets/flowagent-sample-agent-project.json— a real project bundle, exported after building and running it against a live platform: one project with three agents, including a multi-tool agent that calls a device command, opens a ServiceNow incident through a decorated tool, and presents a WorkCenter approval step. It's exact platform data, not a hand-written example — but it's still one specific environment's snapshot: itsreferenceIds,decoratorId, andprovidernames won't exist on your platform verbatim. Read it to see the real shape (in particular, how{{ deviceName }}ininstructionslines up withinputSchema, and howtools[].decoratorIdattaches), then re-resolve every ID against your ownGET /toolsandGET /model-registry-service/profilesbefore reusing it.
Concepts
- Project — the top-level container that owns agents. GBAC-controlled (
owner/editor/viewerroles viamembers). Agents cannot exist outside a project. Supports portable bundle import/export. - Agent — a named AI entity:
instructions(system prompt), a typedinputSchema(what parameters it accepts), aproviderreference (which LLM profile + model it uses), atoolslist, and anoperatorsaccess list. - Profile — a configured, credentialed instance of an LLM provider (e.g., "Production Anthropic"). Owned by Model Registry Service. Holds masked credentials and a curated list of enabled models, each with its own UUID.
- Model — one specific model enabled on a profile (e.g., a Claude or GPT model), addressed by a UUID assigned when it's added to the profile. An agent's
providerfield is{profile: <uuid>, model: <uuid>}— both required together. - Tool — a callable platform capability (adapter method, IAG service, app method), addressed by a structured
referenceId(<type>:<source>:<method>, e.g.application:ConfigurationManager:runCompliancePlan). Discovered viaPOST /tools/discover, never created by hand. - Decorator — a standalone, ID-addressed override of a tool's description and input schema for a specific use case. Cloneable and portable (bulk export/import). An agent attaches a decorator to a specific tool reference, not globally.
- Session — a single run of an agent. Has an 8-state lifecycle (
PENDING→RUNNING→COMPLETE/FAILED/CANCELED, plusPAUSING/PAUSED/CANCELING) and a typed activity log (messages). - Operators — an agent-level access-control list (account/group IDs) granting specific callers the right to run that agent, independent of their project role. Only project owners can edit it.
- Builder Groups — a profile-level access-control list controlling which groups can build agents against a given LLM profile.
- Work Item — a human-in-the-loop task, created when an agent calls a
view-type tool (e.g.view:WorkCenter:QuickForm). Lives in a separate WorkCenter Service (/work-center-service/*), not the Tools Service. The agent's tool call sits atstatus: "pending"until a person completes the work item.
Gotchas
inputSchemaonly allowsstring/numberproperty types, requiresadditionalProperties: false, and validates sessioninputsat start time — a session start with inputs that don't match returns a validation error, not a soft failure inside the agent run.- Every declared
inputSchemaproperty MUST be used ininstructions.instructionsisn't just a static system prompt — it's a template, andinputSchemaproperties are substituted into it as{{ propertyName }}at session-start time. Declaring a property and never referencing it fails agent create/update with"'<name>' is defined in schema but not used in template". - Agent create vs. update field asymmetry:
instructions/inputSchemaare top-level on create, but nested underpromptonPATCH. Tool changes are a full array on create (tools) but deltas on update (addTools/removeTools/decorateTools/authorizeTools). providerhas no inline API key, temperature, or other override at the agent level (additionalProperties: false) — all of that lives on the Profile; the agent only referencesprofile/modelUUIDs (see Concepts above).- Profile credentials are always masked on read (
credential.masked: true) — there's no way to retrieve a saved secret via the API, by design. - Provider type is immutable on a profile once created. To switch providers, create a new profile and repoint agents at it —
GET /model-registry-service/profiles/{id}/agent-impactfirst to see what breaks. - Deleting a profile is irreversible (hard delete) — always check
agent-impactfirst. - Deleting a project cascades to every agent inside it — no soft-delete/recovery.
operatorsdoesn't set tool-call identity, and needs the owner GBAC role to update — see Agents →operatorsfor what it actually controls.- Decorators replace the ENTIRE tool input schema, not just the fields you specify — see Decorators below for the concrete example. Omitting a required field means the agent will never send it, and the underlying adapter call fails with a schema validation error.
agentSnapshoton a session is frozen at session-start time. Editing the agent afterward does not change what an already-running or already-completed session executed.- Tool execution is asynchronous internally, but resolves fully in
messageswithin seconds in practice — see Agent Execution Engine below. If a tool call has been pending materially longer than that, checkGET /tool-rpc/executions?status=runningfor a genuinely stuck execution. - Generic WorkFlowEngine utility tasks (merge, query, getTime, etc.) are not discoverable as tools.
POST /tools/discoveronly registers adapter methods, app methods, workflows, and IAG gateway services — a task existing intasks.jsondoesn't mean it's addressable as a toolreferenceId. If you need simple platform-level info, look for it via an app method (e.g.,application:ConfigurationManager:*) instead. - No bulk session delete. You must delete sessions one at a time.
- No documented ad-hoc/ephemeral agent capability. Every session-start path requires a saved
agentDefinitionId— there is no "run this agent definition once without saving it" endpoint. run-agent's HTTP response is not the final answer — it returns{sessionId, status}immediately just like plainsessionsstart. The "wait for the result" behavior only happens through theterminationCallbackSignaturemechanism at the workflow-engine layer, not by blocking the HTTP call.- Most Agent Project Service and Tools Service responses are untyped in the OpenAPI spec (
{"type":"object"}). Verify exact field names against a live call before hardcoding a$varpath in a workflow task. - A session stuck in
RUNNINGmay just be waiting on a human. The session's ownstatusnever enters a distinct "awaiting input" state — checkGET /work-center-service/work-items?rootExecutionId=<sessionId>before assuming it's stuck. See Work Items below.
Quick fixes for common problems
| Problem | Cause | Fix |
|---|---|---|
| Tool execution fails | Wrong parameters | Test the tool directly (see Tools below), check openapi for correct inputs, update instructions or add a decorator |
| Agent calls wrong tool | Unclear objective | Be more specific in instructions about what to do and when |
Agent loops (iterationCount high) |
Too many tools or vague instructions | Reduce the tools array, add step-by-step guidance in instructions |
| Session input validation error | Inputs don't match inputSchema |
Check required/properties/additionalProperties — only string/number types allowed |
| Agent create/update rejected: "defined in schema but not used in template" | An inputSchema property isn't referenced in instructions |
Add {{ propertyName }} somewhere in instructions, or remove the unused property |
| Agent doesn't use a tool | Tool not in tools array or instructions don't mention it |
Add the tool's referenceId, mention it by purpose in instructions |
Session stuck in PENDING/RUNNING |
Long-running tool call, a stuck external tool executor, or a pending human-in-the-loop task | Check GET /work-center-service/work-items?rootExecutionId=<sessionId> before assuming it's stuck; if genuinely stuck, POST /agent-session-manager/sessions/{sessionId} with {"action":"CANCEL"} |
| High token usage | Agent is exploring too many options | Constrain with "use ONLY these tools, in this order" in instructions |
API Reference
Projects (Agent Project Service)
Base path: /agent-project-service
| Method | Endpoint | Description |
|---|---|---|
| GET | /agent-project-service/projects |
List projects (limit, skip, sort: name|created|lastUpdated, order: 1|-1, search) |
| POST | /agent-project-service/projects |
Create a project |
| GET | /agent-project-service/projects/{projId} |
Get a project (projId = UUID _id or integer iid) |
| PATCH | /agent-project-service/projects/{projId} |
Update name/description/members |
| DELETE | /agent-project-service/projects/{projId} |
Delete a project and all agents within it — requires owner role |
| GET | /agent-project-service/admin/projects |
Admin: list all projects, bypassing GBAC |
| PATCH / DELETE | /agent-project-service/admin/projects/{projId} |
Admin: update/delete any project, bypassing GBAC |
Create:
{ "name": "Network Operations", "description": "..." }
name: 1–100 chars, no leading/trailing whitespace. description: max 500 chars. Creator defaults to sole owner.
Update membership:
{
"members": [
{ "type": "account", "reference": "<24-char-hex-account-id>", "role": "owner" },
{ "type": "group", "reference": "<24-char-hex-group-id>", "role": "editor" }
]
}
Roles: owner | editor | viewer. Only owners can update members. This is a full field replacement per the PATCH body shape (only send what you're changing — name, description, members are each independently optional, but if you send members at all, send the complete list).
Project Bundles (Import/Export) — the preferred way to create a project + agents together
| Method | Endpoint | Description |
|---|---|---|
| GET | /agent-project-service/project-bundles/{projId}/export |
Export a project and all its agents as a portable bundle |
| POST | /agent-project-service/project-bundles/import |
Import a bundle to create (or merge into) a project |
Prefer this over individual POST /projects + POST /projects/{projId}/agents calls when creating a project with one or more agents — same rationale as Automation Studio's project import: build the whole thing locally, import atomically, avoid multi-call intermediate state.
Bundle shape (agentProjectBundleVersion: 1):
{
"_id": "<project-uuid>",
"name": "NERC Compliance",
"description": "Runs NERC-CIP compliance plan, collects device violations, ...",
"agentProjectBundleVersion": 1,
"created": "2026-07-01T13:59:16.070Z",
"createdBy": { "provenance": "CloudAAA", "username": "joksan.flores@itential.com" },
"agents": [
{
"_id": "<agent-uuid>",
"name": "NERC CIP Compliance",
"description": "",
"instructions": "You are a NERC-CIP compliance automation engineer...",
"inputSchema": {
"type": "object",
"additionalProperties": false,
"required": ["device"],
"properties": { "device": { "type": "string" } }
},
"created": "2026-07-01T14:09:15.000Z",
"createdBy": { "username": "joksan.flores@itential.com", "provenance": "CloudAAA" },
"provider": { "profileName": "anthropic-selab-gw", "modelName": "claude-sonnet-4-6" },
"tools": [
{ "referenceId": "application:ConfigurationManager:runCompliancePlan", "lastKnownName": "runCompliancePlan", "decoratorId": "6a453e7b025a4623ad3df433" },
{ "referenceId": "application:ConfigurationManager:searchCompliancePlanInstances", "lastKnownName": "searchCompliancePlanInstances" },
{ "referenceId": "adapter:Servicenow:ServiceNow:createChangeRequest", "lastKnownName": "createChangeRequest", "decoratorId": "6a4522569c7614ba882f176c" },
{ "referenceId": "gatewayService:selab-iag5-standalone:04a19e29-f2dc-41f7-b9c7-102e6b19df08", "lastKnownName": "sleep-and-echo" }
]
}
]
}
provider is de-identified on export to {profileName, modelName} strings (not the live UUIDs) — portable across environments where those UUIDs would differ. agents[] supports multiple agents per project. tools[].decoratorId is present only on entries that have one attached.
Import:
POST /agent-project-service/project-bundles/import
{
"bundle": { "...same shape as export, agentProjectBundleVersion: 1..." },
"conflictMode": "keep-both",
"name": "Network Operations",
"description": "optional override of the bundle's project name/description",
"providerResolutions": {
"<agent identifier from the bundle>": { "profileName": "Production Anthropic", "modelName": "claude-sonnet-4-6" }
}
}
conflictMode: keep-both (duplicate) | replace (overwrite an existing project/agent with matching identity). providerResolutions remaps each agent's profileName/modelName to a profile/model that actually exists in the target environment — required because profiles are environment-specific (different credentials per environment) even though the bundle references them by portable name. The named profile/model must already exist in the target environment before import — bundle import does not create profiles.
Agents (Agent Project Service)
| Method | Endpoint | Description |
|---|---|---|
| POST | /agent-project-service/projects/{projId}/agents |
Create an agent inside a project |
| DELETE | /agent-project-service/projects/{projId}/agents/{agentId} |
Delete an agent |
| GET | /agent-project-service/agents/{agentId} |
Get an agent (flat path — not project-nested) |
| PATCH | /agent-project-service/agents/{agentId} |
Update an agent |
| GET | /agent-project-service/operable-agents |
Paginated list of agents the caller can run (project role OR operators membership) |
| GET | /agent-project-service/operable-agents/{agentId} |
Single operable agent |
| GET | /agent-project-service/agent-names/accessible |
Minimal names of agents visible under read/write/manage GBAC; filterable by modelId, toolReferenceId, projUUID, access |
| GET | /agent-project-service/agent-names/operable |
Minimal {name, _id, project} for every agent the caller can operate (unpaginated) |
There is no endpoint to list all agents within one project — use agent-names/accessible?projUUID=<uuid> instead.
Create (full body):
{
"name": "network-ops-agent",
"description": "...",
"instructions": "...",
"inputSchema": {
"type": "object", "additionalProperties": false,
"required": ["deviceName"],
"properties": { "deviceName": { "type": "string" } }
},
"provider": { "profile": "<uuid>", "model": "<uuid>" },
"tools": [
{ "referenceId": "string", "decoratorId": "<24-hex, optional>", "lastKnownName": "string, optional" }
],
"operators": ["<24-hex-account-or-group-id>"]
}
tools[].referenceId is the only required field per tool entry. provider requires profile+model together — see Concepts above.
Writing instructions and inputSchema:
instructions is a single string (not a chat array) — tell the agent WHO it is and HOW to work: its role, what tools are available and when to use each, expected output format, and constraints (read-only, require approval, etc.).
inputSchema is a strict, flat contract for what a caller must supply when starting a session — only string/number property types are allowed, additionalProperties must be false, and required lists which of the declared properties are mandatory:
{
"type": "object",
"additionalProperties": false,
"required": ["deviceName"],
"properties": {
"deviceName": { "type": "string" },
"priority": { "type": "string" }
}
}
This is a real function-signature-style contract now, not a free-form context bag — the platform validates session inputs against it.
CRITICAL — every declared inputSchema property MUST appear as a {{ propertyName }} template variable somewhere in instructions. instructions isn't just a static system prompt — it's a template, and inputSchema properties are substituted into it at session-start time. Declaring a property that isn't referenced fails agent create/update with: "'<name>' is defined in schema but not used in template". A session's agentSnapshot.instructions shows the post-substitution text — e.g. a schema property count referenced as {{ count }} in the instructions becomes the literal value (3) in the snapshot once a session starts with inputs: {count: 3}. Declare a property only if you actually reference it with {{ }} somewhere in instructions.
Update — note the shape differs from create:
{
"name": "string, optional",
"description": "string, optional",
"prompt": { "instructions": "string", "inputSchema": { "...same strict schema..." } },
"provider": { "profile": "<uuid>", "model": "<uuid>" },
"addTools": [{ "referenceId": "string", "decoratorId": "<24-hex, optional>" }],
"decorateTools": [{ "referenceId": "string", "decoratorId": "<24-hex-or-null>" }],
"authorizeTools": [{ "referenceId": "string" }],
"removeTools": [{ "referenceId": "string" }],
"operators": ["<24-hex-id>"]
}
instructions/inputSchemaare top-level on create but nested underprompton update — an intentional API asymmetry, not a typo.- Tool changes on update are deltas, not a full-array replace:
addTools,removeTools,decorateTools(attach/detach/change a decorator on an existing reference —decoratorId: nullclears it), andauthorizeTools(marks a tool reference as explicitly authorized — exact semantics not documented beyond the field name; verify against your platform before relying on it for anything security-sensitive).operators— what it actually is: a direct, agent-level access grant (array of 24-hex account/group IDs) letting those specific callers operate (run) this one agent, independent of their project role. It's additive to project GBAC, not a replacement — a project editor/owner can already operate every agent in the project;operatorsextends operate-access to accounts that otherwise wouldn't have it. This does not control what identity the agent's own tool calls run as — that's a separate concern not configured on the agent definition itself. Updatingoperatorsrequires the owner GBAC role on the parent project, even though other agent edits only need editor.
Providers and Profiles (Model Registry Service)
Base path: /model-registry-service
| Method | Endpoint | Description |
|---|---|---|
| GET | /model-registry-service/providers |
List supported provider types (read-only catalog — cannot create/edit/delete) |
| GET | /model-registry-service/providers/{providerId} |
Get one provider type's credential field requirements |
| POST | /model-registry-service/providers/{providerId}/fetch-models |
Validate a credential and preview its available models |
| GET | /model-registry-service/profiles |
List profiles (search, provider, sortBy: name|provider|agentCount|createdAt, sortDir, page, pageSize) |
| POST | /model-registry-service/profiles |
Create a profile |
| GET | /model-registry-service/profiles/{id} |
Get a profile (credentials always masked) |
| PATCH | /model-registry-service/profiles/{id} |
Update a profile (provider type is immutable) |
| DELETE | /model-registry-service/profiles/{id} |
Hard-delete a profile |
| GET | /model-registry-service/profiles/{id}/agent-impact |
List agents that will break if this profile is deleted |
| GET | /model-registry-service/gateways |
List GatewayManager clusters available for category: "gateway" profiles |
Provider type IDs observed in the credential union: openai, anthropic, google, ollama, bedrock, bedrock-proxy, databricks, gateway-manager, plus managed (platform-hosted, no credential). There is no distinct azure-openai provider ID — reach Azure OpenAI via provider: "openai" with credential.baseURL pointed at your Azure endpoint, or via gateway/proxy routing. Always confirm against GET /providers on your actual deployment before assuming an ID exists.
Create a profile — three categories, discriminated by category:
"direct" (you supply the credential straight to the provider):
{
"profile": {
"category": "direct",
"name": "Production Anthropic",
"provider": "anthropic",
"credential": { "type": "anthropic", "apiKey": "sk-ant-..." },
"models": [{ "name": "claude-opus-4-6-20260201" }],
"builderGroups": []
}
}
"gateway" (routed through a GatewayManager cluster — adds a required gatewayCluster):
{
"profile": {
"category": "gateway",
"name": "Gateway-Routed Bedrock",
"provider": "bedrock",
"gatewayCluster": "<cluster-id-from-GET-gateways>",
"credential": { "type": "bedrock", "config": { "region": "us-east-1", "accessKeyId": "...", "secretAccessKey": "..." } },
"models": [{ "name": "anthropic.claude-3-5-sonnet-20241022-v2:0" }],
"builderGroups": []
}
}
"managed" (platform-hosted, no credential at all):
{
"profile": {
"category": "managed",
"name": "Platform-Managed Model",
"provider": "<provider-id-with-managedModels>",
"models": [{ "name": "<must-match-one-of-provider.managedModels[].name>" }],
"builderGroups": []
}
}
Credential shapes by type:
type |
Required | Optional |
|---|---|---|
openai |
apiKey |
baseURL |
anthropic |
apiKey |
baseURL |
google |
apiKey |
— |
ollama |
(none) | baseURL |
bedrock |
config.region, config.accessKeyId, config.secretAccessKey |
config.iamRole |
bedrock-proxy |
config.serviceUrl, config.tokenUrl, config.clientId, config.clientSecret |
— |
databricks |
config.type (const "oauth-m2m"), config.host, config.clientId, config.clientSecret |
— |
gateway-manager |
config.clusterId, config.backendProvider, config.credential |
config.properties |
Response (create/get):
{
"id": "<profile-uuid>",
"name": "Production Anthropic",
"provider": "anthropic",
"credential": { "type": "api-key", "masked": true, "baseUrl": "..." },
"models": [
{ "id": "<model-uuid>", "name": "claude-opus-4-6-20260201", "enabled": true, "status": "active" }
],
"builderGroups": [],
"agentCount": 0,
"createdAt": "...", "updatedAt": "...", "createdBy": "...", "updatedBy": "..."
}
credential.masked is always true on read — the actual secret is never echoed back. models[].id is the UUID you use as an agent's provider.model; id (top level) is provider.profile.
Update: wrapped in {"update": {...}}, all fields optional. Provider type cannot change. models[] items on update require both name and enabled (unlike create, which only requires name).
Before deleting a profile, always check impact first:
GET /model-registry-service/profiles/{id}/agent-impact
→ { "affectedAgents": [{ "id", "name", "modelId", "modelName" }] }
Discover models for a credential before saving it:
POST /model-registry-service/providers/{providerId}/fetch-models
{ "credential": { "type": "anthropic", "apiKey": "sk-ant-..." } }
or, to refresh using an already-saved profile's credential:
{ "profileId": "<existing-profile-uuid>" }
Response: { "success": true, "models": [{ "id", "name", "enabled", "status" }], "retrievedAt": "..." }. These models[].id values are provider-native, not registry UUIDs — use models[].name to populate a profile's models array; the registry assigns a new UUID once the model is actually saved into a profile.
Related read-through view for agent authoring (/agent-project-service/profiles, /agent-project-service/profiles/{profileId}) — GBAC-scoped proxy onto the same profiles, used when wiring an agent so the UI only shows profiles the current user is allowed to use. Same profile id/UUID either way.
Tools
Base path: /tools
| Method | Endpoint | Description |
|---|---|---|
| GET | /tools |
Search tools (see query params below) |
| GET | /tools/{referenceId} |
Get a single tool |
| POST | /tools/bulk |
Batch lookup by referenceIds |
| POST | /tools/discover |
Scan the platform and register/refresh tools |
GET /tools query parameters: skip, limit, type, name, referenceIds (comma-separated), description (keyword), active (boolean), parentIds/parentTypes/parentTitles (comma-separated — tools can be hierarchical, e.g. children of an adapter instance), excludeToolChildren (top-level only), sort (name|type|description|source|referenceId), order (asc|desc).
Test a tool directly before wiring it to an agent. Don't give an agent a tool you haven't tested yourself — every tool wraps a real platform API call, and if the direct call fails, the agent's call will too:
# Look up the tool's registry entry (schema/description the LLM will see)
GET /tools/{referenceId}
# Test the underlying endpoint directly — same as testing any platform call, independent of FlowAI:
# Adapter: POST /ServiceNow/createChangeRequest {"body": {...}}
# App: POST /configuration_manager/getDevice {"name": "IOS-CAT8KV-1"}
# IAG service: POST /gateway_manager/v1/gateways/{clusterId}/services/{serviceName}/run {"params": {...}}
# Workflow: POST /operations-manager/jobs/start {"workflow": "...", "options": {...}}
Look up the exact route and request body from openapi.json (jq '.paths | keys[] | select(contains("<adapter-or-app-name>"))' openapi.json) the same way you would for any platform call — this doesn't depend on FlowAI at all. If the tool's native schema is too broad or the LLM keeps sending wrong/incomplete inputs, create a decorator (see Decorators below) — but only after confirming the native tool actually works when called correctly.
Discover:
POST /tools/discover
No body. Scans adapters, IAG services, and app methods, persisting each as a registry entry addressed by referenceId. Safe to re-run — refreshes the registry rather than duplicating entries.
Tool identity — referenceId format: a colon-separated <type>:<source>:<method> string. Observed type values: application, adapter, gatewayService, workflow, integration, template, jsonForm, method (a method belonging to a parent application/adapter entry — see parentType/parentId/parentTitle on the tool object):
| Type | Example referenceId |
Structure |
|---|---|---|
application |
application:ConfigurationManager:runCompliancePlan |
application:<app-name>:<method> |
adapter |
adapter:Servicenow:ServiceNow:createChangeRequest |
adapter:<adapter-instance-id>:<app-type-name>:<method> |
gatewayService |
gatewayService:selab-iag5-standalone:04a19e29-f2dc-41f7-b9c7-102e6b19df08 |
gatewayService:<cluster-id>:<service-uuid> |
workflow |
workflow:7473bb49-f317-4280-9d7a-9e4bd4969365 |
workflow:<workflow-uuid> |
integration |
integration:BECentral%3A2.3:BECentral23:getDevicesByTagId |
integration:<instance-id-may-be-url-encoded>:<app-name>:<method> |
Tool object shape (GET /tools/{referenceId}):
{
"_id": "<mongo-id>",
"type": "method",
"referenceId": "application:ConfigurationManager:getDevicesFiltered",
"active": true,
"checksum": "...",
"description": "Gets a specific subset of devices for based on given options",
"inputSchema": { "...full JSON Schema draft 2020-12, with real property definitions, enums, and examples..." },
"lastUpdated": "...",
"name": "getDevicesFiltered",
"parentId": "ConfigurationManager",
"parentInstance": null,
"parentTitle": "ConfigurationManager",
"parentType": "application"
}
inputSchema on a tool is a full, real JSON Schema (types, enums, patterns, examples) — this is what an LLM actually sees for that tool's parameters, and it's what a decorator's toolInputSchema replaces if one is attached.
There is no create/update/delete for individual tools — the registry is populated only by discovery.
Decorators
Base path: /tools/decorators
| Method | Endpoint | Description |
|---|---|---|
| POST | /tools/decorators |
Create a decorator for a tool |
| GET | /tools/decorators/{decoratorId} |
Get a decorator |
| DELETE | /tools/decorators/{decoratorId} |
Delete a decorator |
| POST | /tools/decorators/{decoratorId}/clone |
Clone a decorator (start from an existing one, adapt for a new team/agent) |
| GET | /tools/decorators/bulk/export |
Export all decorators (paginated) |
| POST | /tools/decorators/bulk/import |
Bulk-import decorators |
| GET | /tools/{referenceId}/decorators |
List all decorators for one tool — a tool can have many |
helpers/create/create-flowagent-decorator.json is a ready-to-edit starting template for the body below.
Create — required shape:
{
"toolDecorator": {
"referenceId": "<tool's referenceId>",
"name": "<decorator name>",
"description": "<what this decorator customizes and why>",
"toolDescription": "<replacement description the LLM sees>",
"toolInputSchema": { "...replacement JSON Schema..." }
}
}
referenceId, name, description, toolDescription, toolInputSchema are all required. Response contains the generated decoratorId (24-char hex Mongo ObjectId), plus the full decorator record (toolDescription, toolInputSchema with $schema auto-added, created/createdBy/lastUpdated/lastUpdatedBy) — this response shape isn't in the OpenAPI spec, so treat the fields above as the reference.
Example — narrowing a vague native schema: adapter:Servicenow:ServiceNow:createIncident's native inputSchema only declares {body: {type: object}}, with no field-level detail. An LLM working from that schema alone has no way to know summary and short_description are required, and the adapter rejects a call missing them: "Schema validation failed on must have required property 'summary'". A decorator fixes this by declaring the fields explicitly:
POST /tools/decorators
{
"toolDecorator": {
"referenceId": "adapter:Servicenow:ServiceNow:createIncident",
"name": "create-incident-required-fields",
"description": "Ensures summary and short_description are always included -- the native schema doesn't declare them.",
"toolDescription": "Creates a ServiceNow incident. The body MUST include both 'summary' and 'short_description' -- omitting summary causes a schema validation error from the adapter. Include 'description' for full diagnostic detail.",
"toolInputSchema": {
"type": "object",
"properties": {
"body": {
"type": "object",
"properties": {
"summary": { "type": "string", "description": "Required. Short one-line summary of the incident." },
"short_description": { "type": "string", "description": "Required. Brief description shown in incident lists -- usually the same text as summary." },
"description": { "type": "string", "description": "Full diagnostic detail." }
},
"required": ["summary", "short_description"]
}
},
"required": ["body"]
}
}
}
Response shape:
{
"_id": "6a465ed52d79d885c63eb250",
"toolDescription": "...",
"toolInputSchema": { "note": "same shape as sent, with $schema auto-added" },
"referenceId": "adapter:Servicenow:ServiceNow:createIncident",
"name": "create-incident-required-fields",
"description": "...",
"created": "...",
"createdBy": { "_id": "...", "username": "...", "provenance": "..." },
"lastUpdated": "...",
"lastUpdatedBy": { "_id": "...", "username": "...", "provenance": "..." }
}
_id is the decoratorId. Note the decorator only narrows body's known properties (summary, short_description, description) — it doesn't set additionalProperties: false, so other real adapter fields the LLM already knows about (from training or context) can still pass through; only omit additionalProperties: false if you deliberately want to lock the schema down to exactly those fields.
Two ways to attach it to an agent:
- At agent creation — include
decoratorIddirectly in thetools[]entry:
{ "tools": [{ "referenceId": "adapter:Servicenow:ServiceNow:createIncident", "decoratorId": "6a465ed52d79d885c63eb250" }] }
- On an existing agent —
PATCHwithdecorateTools:
PATCH /agent-project-service/agents/{agentId}
{ "decorateTools": [{ "referenceId": "adapter:Servicenow:ServiceNow:createIncident", "decoratorId": "6a465ed52d79d885c63eb250" }] }
Decorators are looked up by ID when an agent runs, not embedded inline — the same decorator can be referenced by multiple agents, and clone lets you start from an existing decorator rather than hand-authoring overrides from scratch for a new team/use case.
Effect: with the decorator attached, createIncident produces a single tool-execution message with status: "succeeded" — the LLM has the exact required fields up front and doesn't need a failed first attempt to discover them.
A decorator's toolInputSchema replaces the entire schema the LLM sees — any field you omit will never be sent by the agent, even if the underlying adapter requires it. Test the tool directly (see Tools above) to find every required field before writing the decorator.
When to create a decorator (and when NOT to): create one only when the tool's native schema is too broad and the LLM sends wrong/incomplete inputs despite good instructions, or when different teams need different required fields on the same tool. Skip it for read-only tools and skip it if fixing the instructions text alone solves the problem.
Sessions (Agent Session Manager)
Base path: /agent-session-manager
| Method | Endpoint | Description |
|---|---|---|
| GET | /agent-session-manager/sessions |
List/search sessions |
| POST | /agent-session-manager/sessions |
Start a session (async — fire and forget) |
| GET | /agent-session-manager/sessions/{sessionId} |
Get one session's metadata |
| POST | /agent-session-manager/sessions/{sessionId} |
Cancel / Pause / Resume a session |
| DELETE | /agent-session-manager/sessions/{sessionId} |
Delete a session (per-session only — no bulk clear) |
| POST | /agent-session-manager/sessions/run-agent |
Run an agent from inside an Itential workflow |
| GET | /agent-session-manager/sessions/sources |
Distinct trigger.source values (UI filter helper) |
| GET | /agent-session-manager/sessions/{sessionId}/messages |
Session activity log (paginated) |
| GET | /agent-session-manager/sessions/{sessionId}/messages/{eventId} |
One event, untruncated |
List — query parameters: filters (array of field/operator/value), offset, limit (max 100), sortBy (createdAt|updatedAt|startedAt|status|createdBy|agentDefinitionId), sortOrder.
Session object (agentSnapshot.instructions shows {{ }} template variables already substituted with the actual session inputs):
{
"sessionId": "string",
"agentDefinitionId": "string",
"agentSnapshot": { "_id": "...", "name": "...", "instructions": "... (template vars substituted) ...", "namespace": { "_id": "...", "name": "..." } },
"status": "PENDING | RUNNING | PAUSING | PAUSED | COMPLETE | FAILED | CANCELING | CANCELED",
"startedAt": "...", "endTime": "...", "durationMs": 0,
"createdAt": "...", "createdBy": "<account-id>",
"provider": "anthropic",
"modelVersion": "claude-sonnet-4-6",
"sessionType": "root | child",
"trigger": { "type": "eventSystem|endpoint|schedule|manual|job|session", "name": "...", "source": "..." },
"errorMessage": "...", "errorCategory": "...",
"iterationCount": 0, "toolGroupCount": 0, "totalToolCallCount": 0,
"totalInputTokens": 0, "totalOutputTokens": 0,
"inputs": {}
}
provider/modelVersion are plain strings (provider id and model name) at the session level — not the {profile, model} UUID pair used on the agent itself. agentSnapshot is a
…(truncated)