objectstack-ai/objectstack/objectstack-automation

Claude Code and agent skill by objectstack-ai/objectstack

Trust tier 3
  • community
  • ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Source check pending

Checking that the SKILL.md file is still in the repository.

Design ObjectStack automation — Flows (visual logic), Triggers, Approvals, state machines, and the `jobs` (`defineJob`) / `webhooks` (`defineWebhook`) stack collections. Use when the user is adding `*.flow.ts`, wiring an event-driven rule, modelling an approval chain, or building an interactive screen flow / wizard (objectstack-ui routes those here). Do not use for data lifecycle hooks at the object layer (see objectstack-data) or for kernel / plugin events (see objectstack-platform). CEL expre…

Install

npx vskill@latest install objectstack-ai/objectstack/objectstack-automation

Semi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard

SKILL.md

Automation Design — ObjectStack Automation Protocol

When to Use This Skill

Flows (auto-launched, screen, scheduled), state machines and approvals, record-change triggers, vendor integrations and their callbacks, and assignment rules.

Predicates and conditions are CEL — every condition / guard /
entryCondition / filter value is an Expression envelope: a plain CEL
string, or the P\...\` / cel\...\` tag (author-time validation); pick one
per file. Contract, stdlib, legacy → CEL table: objectstack-formula.

Flows — Visual Logic Orchestration

Flow Types
TypeWhen to Use
autolaunchedRuns without user interaction — triggered by events, APIs, or other flows
screenInteractive — presents UI screens to the user (wizards, forms)
scheduleRuns on a cron/interval cadence declared on the start node's `config.schedule` — or a per-record date sweep via config.timeRelative, see Time-relative triggers
record_changeFires automatically on record create/update/delete. autolaunched + the same record-* binding behaves identically; record_change also opts into the trigger-readiness lint
apiInbound webhook — needs a start-node secretRef (see Inbound webhook triggers below); a flow only ever started explicitly is autolaunched

A flow an installed package ships is read-only. To change what it does, save a same-named flow in the base's paired .custom package declaring overrides: { package: '<base>', basedOn: '<base version>' } (objectstack-platform § Customizing an installed package): it is armed in the base's place, keeps the base's name and its one activation switch (POST /api/v1/automation/:name/toggle, { enabled: false } = off for this deployment), and a run suspended on the old body finishes on that body. An undeclared same-named flow in the pair or in a dependent is refused at publish. A flow beside it is "new similar": POST /api/v1/automation/:name/clone { name, label, package? } lands the copy switched off in the pair, never package-less; a flow holding a secret / signingSecret is refused (409): author it anew.

Flow Node Types

Flows are built from built-in node types (plugins register more):

Control Flow
NodePurpose
startEntry point — every flow has exactly one
endExit point — can have multiple (early exit, error exit)
decisionConditional branching — routed by edge `condition` predicates, not node config
loopIterate a nested `config.body` region once per item of config.collection — a CEL value envelope ({ dialect: 'cel', source: 'rows' }) or an inline list, never a string; iteratorVariable (default item) and optional indexVariable bind inside it, maxIterations caps it
parallelFan out into config.branches[] (≥ 2 regions) run concurrently, joined implicitly at block end — no split/join pair to mis-wire
try_catchRun config.try; on failure run config.catch with the error in errorVariable (default $error); config.retry re-runs try with backoff first. No `finally` — the container's ordinary out-edges are the continuation
mapSequential multi-instance — invoke a subflow once per item of config.collection (shaped as loop's); each iteration may pause (batch approvals)
waitPause execution until a timer elapses or a named signal arrives
subflowInvoke another flow (reusable composition)
parallel_gateway / join_gateway / boundary_eventNot author-facing — BPMN-interop forms a parallel / try_catch container lowers into; author the container
Data Operations
NodePurpose
assignmentSet variable values
create_recordInsert a new record
update_recordModify existing records
delete_recordRemove records
get_recordFetch records with filters — there is no `query_record` node (that name has no executor and throws)
save_noticeTell the saver what this create/update started — record create/update trigger only, not after a pause: objectstack-data rules/lifecycle.md § Save Notice
External Integration
NodePurpose
httpCall an external HTTP API — always set timeoutMs (unset = no timeout). url and headers are served as written with the definition to every member who can read flows — only signingSecret and a start node's secret are withheld — so never a credential there: a credential means a declared connector called via connector_action
notifySend a notification through the messaging service
connector_actionDispatch an action of a declared connector (§ Integrations are declared, not coded) or a plugin's. connectorConfig.input is a value-slot map like subflow input. Once evaluated, it is judged against the action's inputSchema (required, type, properties, items) before dispatch: a breach fails the step VALIDATION_ERROR (fault-routable), nothing sent; os validate does not judge the inputSchema fit yet
scriptCall a registered function named by config.function (see Valid-but-silently-wrong #3)
screenDisplay a UI form to the user (screen flows only). config.fields[]: { name, label, type, required, options, defaultValue }, type an open widget hint; 'file' renders the upload widget and resumes with the sys_file id bound to name — a later create_record / update_record writes it into a file field, which claims the file: collect first, write after the last screen. 'record_link' (never 'record', an object field type) takes no input — it shows the reference record whose id defaultValue evaluates to, by display name, as a link to its record page: the result screen of a flow that created records, { name: 'account_link', type: 'record_link', reference: 'crm_account', defaultValue: { dialect: 'cel', source: 'account.id' } } over a create_record outputVariable: 'account'. Both keys required; required / options / placeholder / min / max refused; a resume value under its name refused (400 INVALID_SCREEN_INPUT). A completed end message is refused and a markdown link in a screen description renders literally. Next / Submit and "Step N of M" are not authored: the engine reads the graph when the screen pauses and serves progress (`nextScreen: 'none''possible''certain'; step / stepCount only when every path agrees), which alone labels the button and draws the counter. A decision, fault edge, loop back, non-screen pause (approval, wait) or subflow after a screen answers possible — "Next", no count. progress in a node config is refused; never hand-roll a button label or step counter into fields`
Human Decision
NodePurpose
approvalRoute a record for human sign-off — suspends the run until a decision, then continues down the approve / reject branch
notify — the most-used node type

NotifyConfigSchema is strictObject — an undeclared key is a named parse error. RAW keys are read as written: a {token} in one is refused.

ts
{ id: 'tell_owner', type: 'notify', label: 'Notify Owner', config: {
recipients: { dialect: 'cel', source: 'record.assignee' }, // REQUIRED — id, list, or envelope; a CSV is ONE id.
// 4th form `{ emailField: 'applicant_email' }`: an `email` field of the trigger record, for a non-user —
// then `channels: 'email'` alone. An envelope must yield USER ids: an address no user holds fails the node
title: 'Done: {{ record.title }}', // inline; XOR `template` (RAW, localizable)
message: 'Closed {{ record.closed_at | date:long }}', // body; only with inline `title`
topic: 'task', // RAW; default 'notify'
severity: 'warning', // RAW; CLOSED enum info|warning|critical
channels: ['inbox'], // RAW; default inbox
sourceObject: 'task', // link: PAIR, else dropped; none of the 3 ⇒ the
sourceId: { dialect: 'cel', source: 'record.id' }, // trigger record (not on delete/before-create), if any
actionUrl: '/tasks/{{ record.id }}', // overrides the pair
} }

An option label in a notice: {{ stage | option:opportunity.stage }} in the mail template (each recipient's language; mail-only, refused in inline title / message) — never label() in templateData, evaluated once per run, before fan-out.

Flow Variables

variables is an array of { name, type, isInput, isOutput } entries — not a name-keyed map; a variable has no label: variables: [{ name: 'case_id', type: 'text', isInput: true }, { name: 'approval_result', type: 'boolean', isOutput: true }].

→ references/examples-flows.md § Flow Example — Auto-Escalate Overdue Cases.

Failure routing & runAs
Handling a failed node: a `fault` edge. { source, target, type: 'fault' }
routes a failed node to a handler instead of ending the run. **type: 'fault'
is what routes — a label: 'error' alone does nothing:** an ordinary edge
traverses on SUCCESS (objectstack validate reports flow-error-label-not-fault).
A handled failure does not consume a flow-level errorHandling.retry, which
replays from the start — prefer a fault edge for a local failure. The handler
reads {{ <nodeId>.error }} (run-wide {{ $error.message }}; in CEL,
vars["$error"]); the run then reports success and the failed step stays in the trace.
ROUTES (runtime failure)Does NOT route (fatal either way)
404, rate-limit, rejected write, failed subflowmissing required config key (objectName, url, flowName, connectorId/actionId); filter operand that evaluated to null or a non-value; graph past the nesting ceiling; unscoped run
Writing a `readonly` field? Set `runAs: 'system'`. Under the default
runAs: 'user' the engine strips it from create_record / update_record
— the step succeeds, the value never lands, the drop is named in the step's
warnings — so a flow maintaining one (approval stamps, SLA markers, rollups)
runs runAs: 'system'; os validate / os build fail a runAs:'user'
update_record writing one (readonlyWhen: a warning). Do not remove
readonly instead.
Elevate the write, not the flow. A screen flow stays runAs: 'user'; a
step that must write a readonly field moves into a dedicated runAs: 'system'
flow called from a subflow node — raising the whole flow silently elevates
every other write in it.
Elevation stops at identity assignment. A runAs: 'system' write (insert
through purge) of sys_user_position, sys_user_permission_set,
sys_invitation or sys_member is refused PERMISSION_DENIED, a system
subflow included. Grant positions and permission sets from a runAs: 'user'
flow — the delegated-administration gate judges that user and records them as
granted_by; invitations and memberships change through the organization
endpoints (row 1b, https://objectstack.ai/docs/permissions/system-context#1-authorization-and-scoping).
A `runAs: 'system'` sweep must pin its organization. System context has no
trigger user, so a scan or rollup with no organization predicate reads and
writes across every tenant; filter on the platform-injected tenant column, never
re-declare it per object.

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
76
Repository forks
20
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Extension points

  • hookPre/post lifecycle hooks

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/objectstack-ai/objectstack/objectstack-automation/badge)](https://verified-skill.com/skills/objectstack-ai/objectstack/objectstack-automation)

Something wrong with this skill or its page? Tell us.