Invocable actions

ContextWorks ships eight runtime actions, plus an admin toolset of nine. Register them as Agentforce agent actions or MCP tools, or call them from Flow. All sit in the ContextWorks category and return the same response shape.


How it works

The actions form a discovery sequence:

Get Catalog, then Find Records, then Get Records: what exists, which records, then those records in depth

An agent that calls Get Catalog first learns your real object and field names instead of guessing them.

ActionWhat it returnsdomainApiName
Get CatalogWhat exists: the index, or one entity’s full contractOptional
Find RecordsA filtered or aggregated set, with totalsRequired
Get RecordsOne or more records in depthRequired
Get BriefsThe standing frame the data should be read inOptional
Get SkillOne packaged procedureNot taken
Get InfoOne computed fact about the situationRequired
Update RecordsA diff to approve, then the committed changeRequired
Create RecordsA proposed record to approve, then the new recordRequired

Update Records and Create Records are independent. An org may register one without the other, and the org switch, the domain and the entity each have to allow the operation. Registering an action never decides whether it can act.

The admin tools belong on their own topic and their own MCP server, never mixed in with the runtime ones. The extension package ships two topics for that. → Administration actions

Get Catalog

Discovery in two depths. With no inputs it returns a version-stamped index. Pass entity names for each one’s full contract.

InputTypeNotes
domainApiNameStringOptional. Only the entities this domain carries — live coverage for a scoped tool
objectApiNameStringOptional. Only the entity for this object
entityApiNamesStringOptional. Comma-separated names from the index — returns each entity’s full contract, up to 5 per call
knownVersionStringOptional. The stamp from a previous call; an unchanged catalog answers with one line instead of repeating itself
formatStringmarkdown (default) · json
DepthWhat comes back
Index — no entityApiNamesOne section per domain the caller holds, carrying that domain’s purpose, synonyms and instruction, then a table of the entities it serves. Separate sections list the briefs, Info and skills available
Contract — entityApiNamesPer entity: every field with the smallest level that includes it, filterable fields with their live picklist values and sampled example values, writable and creatable fields, required fields, synonyms, and instructions
Note
GroupingDomain sections are what make a domain selectable. An entity in several domains appears in each section, and the API name is its identity
Entity instructionsAbsent from the index. They can be long, and they explain data the agent has not chosen yet. They arrive with the contract, and with every payload
BriefsA brief delivered In catalog has its body printed here in full rather than one line, so an agent needs no second call for it. → Delivery
Field tableThe contract’s fields arrive as a markdown table, with one legend line above it: Lvl L/S/D (— = filter-only) · F filterable · W writable · C settable when creating · R required to create
Picklist valuesThe complete valid set for filters and writes, as API values rather than labels
JSONThe same grouping by reference, readable both ways: entities stays a flat array where each entity lists the domains serving it, and domains[] gives each domain {apiName, label, description, instruction, synonyms, isDefault, entities[]}. Each entity contract lists its domains too, which markdown prints as Available through:

Every action resolves what the calling user may discover before anything else, and ContextWorks refuses a domain they do not hold. Get Catalog, Get Briefs and Get Skill serve whichever domains they hold when the call names none; every record call, and Get Info, names one. No agent receives an inactive entity. → Domain access

Find Records

Returns a filtered set in the entity’s curated list columns, with totals over the full matching set — a query capped at 10 rows still reports the true count and sums. The same action also aggregates (groups instead of rows) and scopes (whose records).

InputTypeNotes
objectApiNameStringThe object to query. Required unless entityApiName is given, which names its own object
entityApiNameStringOptional named entity — its curated columns are returned. Narrows within domainApiName, which must serve it
filtersStringJSON filter list, on the entity’s filterable fields only. → Filters
filterNamesStringComma-separated named filters from the catalog (open,committed). They compose by AND, with each other and with filters. → Named filters
scopeStringWhose records: all (the default — every record visible to the caller), me, user:<id|name>, team:<id|name> (that user plus direct reports), rollup:<id|name> (their full subtree). A name or part of one works — exact match first, contains as backup — and an ambiguous match comes back refused with the candidates and their ids, so no person is ever picked for the caller; me works as the id (rollup:me). Plain hierarchy walks, with no forecasting awareness, and sharing still applies on top
levelStringlist (default) · standard — how much of each row. detailed is refused by name: a find is one query at any level it serves, and detailed assembles related lists and enrichment per record
sortByStringField to sort by, ascending unless followed by a direction: Amount DESC. Filterable fields only. Default: last modified, newest first
maxRowsIntegerDefault and cap are org settings (shipped: 20/50); capped requests are noted in the payload
groupByStringOne groupable field (the catalog lists them per entity) — returns one aggregate row per distinct value instead of records
aggregateStringcount, or sum(F) · avg(F) · min(F) · max(F) over an aggregatable field. With groupBy, one value per group, largest first; alone, one summary. When set, level does not apply
maxGroupsIntegerGroups returned, largest first by the measure; org-capped (shipped default 10). A truncated response says so and still totals the full set
sampleSizeIntegerOptional records per group — the 3 biggest deals in each stage. Hard-capped at 10; requires groupBy
sampleLevelStringlist (default) · standard — depth of the sample rows under a group. For ungrouped rows use level
maxSampledGroupsIntegerHow many of the top groups get samples (org-capped, shipped default 5). The rest come back as counts and aggregates only, and the response says so
formatStringmarkdown (default) · json · both
domainApiNameStringRequired. The domain this call reads through. Requires objectApiName unless entityApiName names one

Example filter:

[{"fieldName":"IsClosed","operator":"=","values":["false"]},
 {"fieldName":"CloseDate","operator":"<=","values":["THIS_QUARTER"]}]

One call answers “what is my pipeline this quarter by stage, with the biggest deals in each”:

filterNames: open,this_period   scope: me
groupBy: StageName              aggregate: sum(Amount)
sampleSize: 3                   maxSampledGroups: 8
BehaviorDetail
SortingsortBy is ascending unless you add a direction, so “the ten biggest deals” is Amount DESC with maxRows: 10. The field must still be filterable on the entity; anything after the field that isn’t ASC or DESC comes back refused, with the grammar named. Each entity’s contract lists its filterable fields, which is the same list
TruncationGroup caps, sample caps and row caps all state what they cut, and totals always cover the full matching set
CurrencyIn multi-currency orgs the platform aggregates in corporate currency, and measures convert to the caller’s display currency — the same number in your own currency, whoever asks
Ownership and sharingscope narrows to whose records; the query still narrows to what the caller may see. Ask about someone whose records you can’t access and you get nothing back, correctly, in totals as in rows

Get Records

One or more records in depth. Pass comma-separated IDs and each comes back in full, so a set found through Find Records can be read without a call per record — its first column is the record ID for exactly this.

InputTypeNotes
recordIdsStringRequired. One or more record IDs, comma-separated. Objects may differ. me is the calling user’s own User record
objectApiNameStringOptional — resolved from the record ID when omitted
entityApiNameStringOptional named entity, narrowing within domainApiName. When omitted, the domain’s entity for each record’s object applies
levelStringlist · standard · detailed (default)
formatStringmarkdown (default) · json · both
domainApiNameStringRequired. The domain this call reads through — every record uses that domain’s entity for its own object

Records need not share an object. Each resolves independently: object from the ID, then a view for that object.

You passEach record uses
neitherits object’s default entity
entityApiNamethat entity — one object only, since an entity belongs to one
domainApiNamethe domain’s entity for that record’s object

A pinned address is single-object: entityApiName or objectApiName alongside mixed IDs comes back refused, and the error names the fix. For mixed objects, pass the domain alone. It is the only address that varies per object.

BehaviorDetail
CapOrg settings, shipped at 5 records for detailed and 10 for the other levels. Past the cap the call returns what fits and says how many were asked for
Per-record failureA record that can’t load — most often a domain-scoped call handed an object the domain doesn’t serve — is named in a note; the rest come back
Outputmarkdown payloads are separated by ---. json returns one envelope: {schemaVersion, requested, returned, truncated, records[], errors[]}
merecordIds: me loads the calling user’s own User record, which is the shortest answer to a question about the person the agent is talking to. It composes: me,006… reads you and a deal in one call

Structure and prose in one call

format takes a third value on Find Records and Get Records: both. The response is the json payload with the markdown rendering inside it, under a markdown key.

Use it when you need both halves: IDs, names and field values to build a link or a datatable, and the prose to ground a model.

CostCalling twice assembles twice — same queries, same metrics, same related lists, and two loads on your usage. both renders twice from one assembly and meters as one load
Usage reportingboth records under its own format dimension, so a rollup can still tell the two apart
ContentEverything the JSON carries, it still carries; the markdown is additive. Injected briefs ride both halves, so a domain’s frame reaches the model whichever half it reads
AggregatesgroupBy / aggregate queries take both as well

Get Briefs

The retrieve step for ambient context: briefs — the frame the data should be read in — requested by name from the catalog index, or asked for by domain in that domain’s declared order, injected briefs first. A brief delivered In catalog needs no call here at all, because Get Catalog already printed its body.

InputTypeNotes
briefApiNamesStringOptional. Comma-separated names from the index; capped per call by an org setting (shipped: 10)
domainApiNameStringOptional. Everything that domain carries, in its declared order
knownVersionStringOptional. The stamp from a previous call; an unchanged set answers with one line
formatStringmarkdown (default) · json

The stamp folds in the fiscal period, so a quarter rollover always refreshes, and the unchanged response still carries a fresh computed-context line. A brief that none of the caller’s domains carries is absent from every list and unknown by name.

Get Skill

The catalog carries each skill’s name and one-line description; this returns one skill’s full procedure and output shape when a task matches. Runtime skills reach agents through a domain; admin skills serve the admin agent only.

InputTypeNotes
skillApiNameStringRequired. From the catalog’s Skills section
formatStringmarkdown (default) · json

Get Info

One computed fact about the situation, in full. A short summary already rides every payload the domain serves; this is the parameterized rest: the boundaries of a named fiscal period, the org’s currencies and rates, who reports to whom, or where this org’s records live.

InputTypeNotes
infoApiNameStringRequired. From the catalog’s Info section: fiscal_periods, currency, user_team, record_links
parametersStringOptional JSON object, e.g. {"period":"FY2026Q3","count":4}. The catalog lists each Info’s parameters; an undeclared one comes back refused by name rather than ignored
domainApiNameStringRequired. The domain this call reads through — it decides which Info is available
formatStringmarkdown (default) · json

Ask for an Info the caller’s domain does not carry and the refusal lists what it does carry. → Info

Update Records

Changes declared-writable fields on one or more records, through each entity’s write access. Two-phase by design: the first call changes nothing and returns a current-vs-proposed diff (with the fields’ instructions) for the user to approve; the same call with confirm=true commits. Every commit, failed commit and refused attempt is durably logged, one row per field.

A set found through Find Records can be pushed in a single call, so eight deals cost two calls and one approval instead of sixteen calls and eight approvals. Every record carries its own changes. The set does not have to be homogeneous.

There are two ways to name records, because agent tools accept different shapes:

InputTypeNotes
recordIdStringThe record to update, when updating one. Pair with changes
changesStringThat record’s changes: JSON object of field API name → new value, e.g. {"StageName": "Negotiation/Review"}. Picklists take the API value; dates are yyyy-MM-dd
recordsStringSeveral records, each with its own changes: a JSON array of {"recordId": …, "changes": {…}}. Use instead of recordId/changes — naming both comes back refused
confirmBooleanOmit for the preview diff; true commits. Applies to every record in the call
changeDigestStringThe digest the preview printed, echoed back with confirm=true. Required wherever the domain requires confirmation. In records, each entry carries its own
objectApiNameStringOptional — validated against each record ID’s type
entityApiNameStringOptional named entity whose write contract applies to every record, narrowing within domainApiName. When omitted, the domain’s entity for the object applies
domainApiNameStringRequired. The domain this call writes through — it must allow writes

An Agentforce function schema cannot carry a list, so records travels as text, the same way Get Records takes comma-separated IDs. MCP and Flow callers may also repeat the whole input; the two compose.

{ "records": "[{\"recordId\": \"006…\", \"changes\": {\"StageName\": \"Closed Won\"}}, {\"recordId\": \"006…\", \"changes\": {\"CloseDate\": \"2027-01-01\"}}]" }
BehaviorDetail
Capentity_write_max_records (shipped: 10) counts records, not inputs — summed across records entries and repeated inputs. Over it the call is refused whole: nothing previewed, nothing saved, and the refusal names the limit. Get Catalog publishes the number so an agent can size the call
One domain, one phaseAn input is one entity and domain and one confirm applied to every record in it. A set needing different domains is different inputs
Per recordEach record resolves, diffs and saves on its own. Diffs come back joined by ---, the same convention Get Records uses
Partial successOne record failing validation leaves the others unchanged. errorMessage names the records that were not updated; the ones that were are still in result
ConfirmationDecided by the domain, not by this action: Allow unattended writes on the domain the call names. Since every write names one, that is always the answer here — Apex calling the service without a domain always confirms. Get Catalog reports it as writes.confirmationRequired. Where confirmation is required, a commit without a matching changeDigest is refused, including one whose record moved since the preview

A validation-rule failure comes back as data in the rule’s own words, so the agent can adjust and retry, or relay the reason. An invalid picklist value comes back refused, with the valid values named. A field outside the writable set refuses that record’s whole write; nothing partially applies within a record.

Create Records

Creates one or more records through each entity’s create access. Two-phase like Update Records: the first call creates nothing and returns the proposed record (with the fields’ instructions) for the user to approve; the same call with confirm=true creates it.

An org allows creating and updating separately. Letting agents log a task while never touching an opportunity is a common and deliberate posture. Check Get Catalog rather than inferring one from the other.

InputTypeNotes
objectApiNameStringThe object to create, when creating one. Pair with values
valuesStringThat record’s values: JSON object of field API name → value, e.g. {"Subject": "Follow up"}. Picklists take the API value; dates are yyyy-MM-dd
recordsStringSeveral records, each with its own values: a JSON array of {"objectApiName": …, "values": {…}}. Use instead of objectApiName/values — naming both comes back refused
confirmBooleanOmit for the preview; true creates. Applies to every record in the call
changeDigestStringThe digest the preview printed, echoed back with confirm=true. Required wherever the domain requires confirmation. In records, each entry carries its own
entityApiNameStringOptional named entity whose create contract applies, narrowing within domainApiName. When omitted, the domain’s entity for the object applies
domainApiNameStringRequired. The domain this call creates through — it must allow creating
BehaviorDetail
CapShares entity_write_max_records with Update Records and counts records. Over it the call is refused whole — and it matters more here, because serving up to the cap would leave records nobody asked for
Partial successOne entry failing leaves the others created. errorMessage names only what failed and says plainly that the rest exist and must not be sent again: a re-sent update is a no-op, a re-sent create is a duplicate
RetriesThe changeDigest doubles as an idempotency key. The same confirmed digest within five minutes returns the record already made rather than a second one
OwnershipEverything an agent creates, it owns. OwnerId is never settable, so a record cannot be created for someone else
Required fieldsGet Catalog marks them R. A field the object requires that the entity has not declared comes back as a note, not a refusal — your org’s automation may fill it. If nothing does, the create is refused with a message saying an admin must declare it and that retrying will not help

The digest proves less here than on an update: with no existing record to compare against, it proves a preview happened and nothing more.

Response shape

Every action returns:

OutputTypeNotes
resultStringThe payload — markdown or JSON
successBooleanFalse when the request failed
errorMessageStringWhy it failed, with corrective guidance

errorMessage is written for an agent to read. A domain coverage miss names what the domain currently serves, so the agent can re-plan.

Administration actions

Nine tools make up the admin agent: the seven below, which read and change configuration rather than records, plus Get Catalog and Get Skill. Registering those two here as well is how an admin agent reads what a runtime agent receives. Full inputs: The admin tools.

ActionWhat it does
Get Full ConfigThe configuration graph, scoped by domain, object or entity list. analysis (default) or export
Get HealthUsage, quality, issues, trends and the write audit — one area per call
Get Change LogWho created, activated, rolled back, deactivated or deleted what, when, and why
Save DraftComposes or updates a draft: a brief, a skill, or an entity’s text. Never a version, never the active pointer
Activate DraftPublishes a draft as the next served version, after a preview the user approves
Update Domain ContentsAdds or removes an entity, brief or skill from a domain, after a preview the user approves
DeactivateWithdraws an artifact from service immediately. Versions are kept, so re-activating restores it

Every call runs as the conversing user and needs the ContextWorks Admin or ContextWorks Domain Manager permission set. A domain manager sees only the domains they manage.

A draft is not activated, so no agent receives it, and it carries a “composed via” attribution the reviewer sees in Studio. Activate Draft, Update Domain Contents and Deactivate each preview first, then commit only against the digest that preview printed.

Registering

Each action takes two different names.

What to use
The Apex class you pick in Setup → Agent Actions → New → ApexGetCatalogAction, FindRecordsAction, GetRecordsAction, GetBriefsAction, GetSkillAction, GetInfoAction, UpdateRecordsAction, CreateRecordsAction — or the generated classes for scoped tools
The agent action name, which the generated AgentScript names in source:Get_Catalog, Find_Records, Get_Records, Get_Briefs, Get_Skill, Get_Info, Update_Records, Create_Records

Name each agent action exactly as shown: the generated AgentScript looks for these names, and when you register by hand the name is whatever you type. Register UpdateRecordsAction and CreateRecordsAction only where writes belong, because an absent tool cannot be misused.

Namespace. Three names the generated script resolves follow the same rule: bare in the package’s own namespaced org, prefixed in a subscriber org.

ReferenceSubscriber orgThe package’s own dev org
apex:// targetapex://ctxl__GetRecordsActionapex://GetRecordsAction
source: function namectxl__Get_CatalogGet_Catalog
Apex class, as the org lists itctxl__GetRecordsActionGetRecordsAction

The Deploy page generates the right form for the org you’re in. Either mistake fails at runtime, not on save.

See Agentforce and MCP.

Calling from Flow

Add an Action element and pick from the ContextWorks category. Use it to attach a briefing to a record on a schedule, or to ground a Prompt Builder template. For anything beyond the exposed inputs, call the Apex API from your own invocable.

Notes

Every input carries a description written for an agent: date literals like THIS_QUARTER, the row cap, full-set totals, entity and domain exclusivity. Those descriptions travel into every registration and are the agent’s only documentation, so change them only deliberately.

What’s next