mcpField
O
objectstack-automation

objectstack-automation

github.com/objectstack-ai開発76230 回インストール今日更新
リポジトリ

objectstack-automation について

ObjectStack の自動化を設計する開発者向けのスキルです。ビジュアルフロー、トリガー、スケジュール、承認を扱い、CEL 条件式やノードの動作、設定時の注意点も説明します。

主な機能

  1. 01自動起動・画面・スケジュールフローの設計
  2. 02レコード変更と Webhook トリガーの設定
  3. 03分岐・ループ・並列ノードの利用
  4. 04レコードノードによるデータ操作
  5. 05障害ルートと人による承認の設定

活用シーン

  1. 01レコード作成・更新後の処理の自動化
  2. 02承認を含む業務オブジェクトの状態機械の構築
  3. 03定期レポートやデータクリーンアップの実行
  4. 04外部 HTTP API や連携コネクターの呼び出し

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/objectstack-ai/objectstack --skill objectstack-automation

インストール後、Claude Code で/objectstack-automationを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
flows-triggers-approvals.json
evals/approvals/
test-revise-loop.md
references/
_index.md
examples-flows.md
state-machines-and-approvals.md

詳細

オートメーション設計 — ObjectStack Automation Protocol

このスキルを使う場面

  • ビジュアルフロー(自動起動、画面、またはスケジュール)を構築する場合。
  • ビジネスオブジェクトのステートマシンまたは承認プロセスが必要な場合。
  • イベント駆動型トリガー(レコードの作成/更新/削除)を設定する場合。
  • スケジュールされたオートメーション(日次レポート、データクリーンアップ)が必要な場合。

述語と条件には CEL を使用します — ここに記載されているすべての condition / guard / entryCondition /フィルターの value は、@objectstack/formula で評価される Expression エンベロープです。スロットには CEL 文字列をそのまま指定できます。P\...`/cel`...`` タグは、作成時の検証を行いながら同じ文字列をラップします。 どちらも解析できます。ファイルごとにどちらか一方を選んでください(サンプルアプリではプレーン文字列を使用しています)。CEL の仕様、標準ライブラリ、旧形式から CEL への対応表については、objectstack-formula を参照してください。


フロー — ビジュアルロジックのオーケストレーション

フローはノードで構成される有向グラフで、オートメーションの主要な構成要素です。

フローの種類
種類使用場面
autolaunchedユーザー操作なしで実行されます。イベント、API、または他のフローによって起動されます
screen対話型です。ウィザードやフォームなど、ユーザーに UI 画面を表示します
schedule開始ノードの config.schedule で宣言した cron/間隔の周期で実行されます(日次クリーンアップ、週次レポート)。または、時間相対トリガー を参照してください。config.timeRelative を使ったレコードごとの日付スイープも可能です
record_changeレコードの作成/更新/削除時に自動的に実行されます(start ノードの triggerType でバインドします)。autolaunched を同じ record-* バインディングと組み合わせた場合も同じ動作になります。エンジンはどちらの形式でも開始ノードを読み取ります。record_change はトリガー準備状況の lint にも含まれます
apiインバウンド webhook です。開始ノードに secret が必要です(以下のインバウンド webhook トリガーを参照)。明示的な起動のみを行うフローには autolaunched を使用します
フローノードの種類

フローは 20 種類の組み込みノードで構成されます(プラグインは、以下の approval のように registerNodeExecutor でノードを追加登録できます)。

制御フロー
ノード用途
startエントリーポイントです。各フローに必ず 1 つだけ存在します
end終了ポイントです。複数設定できます(早期終了、エラー終了など)
decision条件分岐です。ノード設定ではなく、エッジの condition 述語によって経路が決まります(以下の承認例を参照)
loopconfig.collection の各項目について、ネストされた config.body 領域を繰り返し実行します。config.collection は CEL 値エンベロープ({ dialect: 'cel', source: 'rows' })またはインラインリストで指定し、文字列は使用できません。iteratorVariable(デフォルトは item)と省略可能な indexVariable は領域内でバインドされ、maxIterations で反復回数を制限します
parallelconfig.branches[] にある 2 つ以上の領域を並行して実行します。ブロックの終端で暗黙的に合流するため、誤った接続を招く分割/合流ペアは不要です
try_catchconfig.try を実行し、失敗した場合はエラーを errorVariable(デフォルトは $error)に格納して config.catch を実行します。config.retry を指定すると、まずバックオフを挟んで try を再実行します。finally はありません。コンテナーの通常の出力エッジが後続処理になります
map順次実行のマルチインスタンスです。loop と同じ形式の config.collection の各項目について、サブフローを 1 回呼び出します。各反復で一時停止できます(バッチ承認など)
waitタイマーが経過するか、名前付きシグナルを受信するまで実行を一時停止します
subflow別のフローを呼び出します(再利用可能な構成)
parallel_gateway / join_gateway / boundary_event作成者向けではありません — BPMN 相互運用形式であり、マッパーが parallel / try_catch コンテナーから変換します(automation/control-flow.zod.ts)。コンテナーを作成してください
データ操作
ノード用途
assignment変数の値を設定します
create_record新しいレコードを挿入します
update_record既存のレコードを変更します
delete_recordレコードを削除します
get_recordフィルターを使ってレコードを取得します。query_record ノードはありません(この名前に対応するエグゼキューターがなく、エラーになります)
外部連携
ノード目的
http外部 HTTP API を呼び出します — 必ず timeoutMs を設定してください(未設定の場合、タイムアウトはありません)。url と headers は、フローを読み取れるすべてのメンバーに定義とともに記載どおりに提供されます — 非公開となるのは signingSecret と開始ノードの secret のみです — そのため、ここに認証情報を記載しないでください。形式に応じてルーティングします — ヘッダー認証情報の場合は auth.credentialRef を指定した宣言型コネクター(bearer / basic / api-key)、クエリ文字列のキーの場合は paramName を指定した api-key、パスにシークレットを含む Webhook URL の場合は slack などのトークン認証型コネクターを使用します — connector_action 経由で呼び出します
notifyメッセージングサービスを通じて通知を送信します(デフォルトでは受信トレイチャンネル)
connector_action事前構築済みの連携コネクターを呼び出します
scriptconfig.function で指定された登録済み関数を呼び出します(有効だが意図せず誤動作する設定 #3 を参照)
screenユーザーに UI フォームを表示します(画面フローのみ)
人による判断
ノード目的
approval人の承認を得るためにレコードを振り分けます — 判断が下されるまで実行を一時停止し、その後 approve / reject ブランチに進みます(plugin-approvals が提供)
notify — 最もよく使われるノードタイプ

NotifyConfigSchema(automation/io-node-config.zod.ts)は strictObject です — 宣言されていないキーは、キー名を含む解析エラーになります。RAW キーは記載どおりに読み取られるため、そこに {token} を指定すると拒否されます。

{ 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
    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',              // click-through: a PAIR, else dropped
    sourceId: { dialect: 'cel', source: 'record.id' }, // (a value slot)
    actionUrl: '/tasks/{{ record.id }}', // text slot; overrides the synthesized link
} }
フロー変数

すべてのフローで入力/出力変数を定義します。variables は { name, type, isInput, isOutput } エントリーの配列です — 名前をキーとするマップではなく、変数に label プロパティもありません。

variables: [
  {
    name: 'case_id',
    type: 'text',
    isInput: true,    // passed in when flow is invoked
    isOutput: false,
  },
  {
    name: 'approval_result',
    type: 'boolean',
    isInput: false,
    isOutput: true,   // returned when flow completes
  },
],

→ references/examples-flows.md § フローの例 — 期限超過ケースの自動エスカレーションに移動しました。

失敗時のルーティングと runAs

ノードの失敗を処理するには、fault エッジを使用します。 { source, target, type: 'fault' } は、実行を終了する代わりに、失敗したノードをハンドラーにルーティングします。**ルーティングを行うのは type: 'fault' です。label: 'error' だけでは何も起きません。**エッジは通常のままで、無条件の出力エッジはすべて SUCCESS 時にたどられるため、ノードが成功するとハンドラーが実行され、失敗しても実行されません (objectstack validate は flow-error-label-not-fault を報告します)。 処理された失敗では、フローレベルの errorHandling.retry は消費されません。これはフローを最初から再実行するため、失敗が局所的な場合は fault エッジを優先してください。ハンドラーでは {{ <nodeId>.error }}(または実行全体の {{ $error.message }}、CEL では vars["$error"])を参照できます。その後、実行は成功として報告され、失敗したステップはトレースに残ります。

ルーティングされるもの(実行時の失敗)ルーティングされないもの(いずれにせよ致命的)
404、レート制限、書き込み拒否、サブフローの失敗必須設定キーの欠落(objectName、url、flowName、connectorId/actionId)、null と評価されたフィルターのオペランド、ネスト上限を超えたグラフ、スコープなしの実行

readonly フィールドに書き込む場合は、runAs: 'system' を設定してください。 readonly: true はエンドユーザー向けの動作を制御します。デフォルトの runAs: 'user' では、エンジンはシステム以外による書き込みから readonly フィールドを除外します。create_record と update_record のどちらでも、ステップは成功を報告しますが値は反映されず、除外されたことがステップの警告に記録されます。承認スタンプ、変換フラグ、SLA マーカー、集計値などの readonly フィールドを管理するフローは、信頼された書き込みチャネルである runAs: 'system' で実行する必要があります。 os validate/os build は、readonly フィールドに書き込む runAs:'user' の update_record を失敗として扱うため、不整合は数日後に誤ったデータとして現れるのではなく、ビルド時に検出されます。(readonlyWhen フィールドも、レコードの状態に応じて同様に警告としてフラグが付けられます。)readonly を削除して回避してはいけません。フィールドの編集保護が失われます。

フロー全体ではなく、書き込みを昇格させてください。 screen フローは runAs: 'user' のままにします。 その中のあるステップで readonly フィールドへの書き込みが必要な場合は、そのステップを専用の runAs: 'system' フローに移し、subflow ノードから呼び出してください。フロー全体を昇格させると、ほかのすべての書き込みも気付かないうちに昇格してしまいます。

runAs: 'system' の一括処理では、対象組織を明示してください。 システムコンテキストにはトリガーとなったユーザーが存在しないため、クエリの範囲は自動的に絞られません。組織条件のないスキャンや集計は、すべてのテナントを対象に読み書きします。テナント列はプラットフォームによって注入されます。オブジェクトごとに再宣言せず、その列をフィルターしてください。

フックは sudo ではなく runAs で権限を昇格させます。 オブジェクトフック(objectstack-data)は、独自の runAs: 'system' | 'user' | 'inherit' を宣言します。デフォルトは 'inherit' で、フックを発火させた書き込みのコンテキストです。これにより、インプロセスの handler とサンドボックス化された body のどちらでも、そのフックの ctx.api データ操作のみがスコープされます。トリガーでユーザーが解決されなかった 'user' フックには、スコープ対象のユーザーがいません。そのため、スコープなしで実行されるのではなく、ctx.api データ操作は拒否されます(HOOK_UNSCOPED_DATA_ACCESS、403)。昇格が意図したものである場合は、runAs: 'system' を宣言してください。sudo はフックのキーではありません。

フィルターのオペランド(config.filter)
  • 各オペランドは値スロットです — リテラル、またはクエリの前に評価される CEL 値エンベロープです (filter: { id: { dialect: 'cel', source: 'record.id' } })。文字列全体が クエリプレースホルダー({current_user_id}, {today}, {current_year_start})の場合は、そのままクエリエンジンに渡され、展開されます。それ以外の {…} トークンは、エンベロープとともに拒否されます(expression-invalid)。フロー変数がプレースホルダーを上書きすることはありません。この受け渡しが行われるのは filter だけです — テキストスロットでの {{ current_year_start }} は無効な参照です。
  • オペランドが null に評価されると、ノードは拒否されます(fault へのルーティング対象ではありません)。条件が削除されるとクエリの対象が絞り込まれるのではなく、広がります。そのため、ルーティングされた delete_record はオブジェクトを空にしてしまい、実行結果は成功と報告されます。値が存在しない可能性がある場合は、decision でノードを迂回させてください。空のフィールドに一致させるには、リテラルの null を記述します。リレーションのたどり先(record.owner.email)では、開始ノードの config.expand にそのフィールドを記載する必要があります。そうしないと、エラーには解決策ではなく、欠落しているたどり先が示されます。

有効だが誤った結果になる(ビルドには通るが実行時に失敗)

正当なメタデータでも、作成者(特に AI)が誤ることがあります。ほとんどは objectstack build で検出されますが、最初から正しく記述してください。

  1. 値スロットに指定できるのはリテラルか CEL エンベロープであり、{…} トークンは指定できません。 assignments.*、fields.*、subflow / map の input.*、script の inputs.*、loop / map の collection、notify の recipients / templateData.* / sourceId、http の body、すべての CRUD filter オペランドでは、文字列はそのままリテラルテキストとして扱われ、{…} トークンは拒否されます — objectstack validate(expression-invalid)、 registerFlow、executor の各段階で拒否され、対応する CEL の記述が示されます。 { dialect: 'cel', source: '…' } を使って計算してください。

    • '{record.owner}' → source: 'has(record.owner) ? record.owner : null' — CEL では、テンプレートで何も記述されていない場合にキーが存在しないと拒否されるため、何も指定しないか、null にするか、デフォルト値を使うかは、今度はあなたが決めます。'{record.tags.0}' → 'record.tags[0]'; 深い階層のパスでは各段階をガードします:has(vars.source) && has(vars.source.id) ? vars.source.id : null
    • 金額:source: 'round(x * 100) / 100.0' — CEL では整数同士の除算は整数になるため、/ 100 では 123.46 が 123 になります
    • 日付:{TODAY()} → isoDate(today())、{TODAY() ± N} → isoDate(daysFromNow(N)) / isoDate(daysAgo(N))、{NOW()} → isoDatetime(now())、{NOW() ± N} → isoDatetime(addDays(now(), N))
    • 実行ユーザー:{$User.Id} → current_user.id。ユーザーなしで実行される可能性のあるフローではガードします:current_user != null ? current_user.id : null。 current_user が保持するのは id、positions、organizationId、isPlatformAdmin のみです — メールアドレスや名前は、その ID を使って読み取る sys_user の値です。

    テキストスロット(notify の title / message / actionUrl、screen の title / description、end の message、http の url / headers)では、{{ }} のプレースホルダーをレンダリングします — 変数パスと任意のフォーマッターを指定でき、ロジックは使えません。そこで {…} トークンを使うと、対応する {{ }} の記述とともに拒否されます。日付や実行ユーザーにはプレースホルダーがありません。先に割り当ててから (assignments: { v: { dialect: 'cel', source: 'current_user.id' } })、{{ v }} と記述してください。

  2. create_record の outputVariable に入るのは作成された RECORD であり、その ID ではありません。 フィールドを明示的に参照してください。 ❌ fields: { ref: { dialect: 'cel', source: 'newRec' } } → レコードオブジェクト全体を書き込みます ✅ source: 'newRec.id'

  3. script ノードが呼び出すのは登録済みの関数だけです。 config.function に、 defineStack({ functions: { my_fn: (ctx) => … } }) を介して登録された関数を指定してください。これは 必須 です。空の script ノードは実行時に拒否され、未登録の関数を指定すると明示的に失敗します。

    これ以外のディスパッチ形式はありません。配信には notify、 Slack には slack コネクター上の connector_action、ロジックには関数を使ってください。 フローの function は純粋な計算ステップです。データベースの読み書きは行いません。 ctx.input を受け取り、値を返します。config.outputVariable によってその値をフロー変数として公開し、後続の宣言型ノードで永続化します。データへの作用はフローグラフ上に置いてください(可視化され、管理され、ビルド時にチェックできます)。

    // ✅ function returns values → outputVariable → update_record persists
    { id: 'ai', type: 'script', config: {
        function: 'helpdesk.aiTriageStub',     // returns { ai_category, ai_sentiment, … }
        inputs: { ticketId: { dialect: 'cel', source: 'record.id' } }, // CEL values or literals
        outputVariable: 'ai',
    } },
    { id: 'apply', type: 'update_record', config: {
        objectName: 'helpdesk_ticket',
        filter: { id: { dialect: 'cel', source: 'record.id' } },
        fields: { ai_category: { dialect: 'cel', source: 'ai.ai_category' } },
    } },
    

    データのライフサイクルに関わる副作用(他のレコードの読み書き)が本当に必要な場合は、L2 のフック(objectstack-data)を使います。フックは ctx.api を取得できますが、フロー関数は取得できません。

    プラットフォームから確認できない書き込みを行う関数は、そのことを宣言します。宣言しない場合、実行結果は acted: 0 ではなく「不明」と報告されます。

    defineStack({ functions: {
      'helpdesk.aiTriageStub': (ctx) => ({ ai_category: 'other' }),  // pure — the default
      'billing.sync': { handler: syncBilling, effect: 'writes' },    // declared writer
    } });
    
  4. 条件には CEL をそのまま記述します。標準ライブラリの関数だけをそのまま呼び出せます。 この表は objectstack-formula の CEL_STDLIB_FUNCTIONS で、単独で呼び出せる公開関数のサブセットです。レシーバーメソッド(値に対して呼び出すもので、単独では呼び出せません)は含まれません。 不明な関数(PRIOR()、名前のタイプミスなど)や、{…} で囲まれたフィールド参照はどちらもビルドに失敗します。波括弧はテンプレートであり、CEL ではありません。{record.x} ではなく record.x と記述してください。

  5. messaging 機能がない場合でも、notify は成功を報告します。 実行エンジンは no messaging service registered をログに記録し、output: { delivered: 0, failed: 0, skipped: true } および metrics.acted: 0 を伴って成功を返します。実際には何も配信されていないのに、実行は成功扱いになります。requires に messaging を宣言してください。


→ ステートマシンと承認については references/state-machines-and-approvals.md に移動しました。状態を調べるルートは GET /api/v1/meta/object/:name/state/:field?from=:state です。


トリガー — イベント駆動型の自動化

record_change フローは、データイベントに応じて自動的に実行されます。独立したトリガーオブジェクトや、トップレベルの trigger / event キーはありません。バインディングはフローの start ノードの config のみに設定します。

前提条件 — ノードが必要とする機能を宣言する

メタデータはトークンがなくても登録されますが、該当する機能は実行されません。うち 2 つは警告もなく機能しません。

requires トークン有効になる機能不在の場合
automationフローエンジンとノード実行エンジンフローは実行されません
triggersrecord-* / スケジュール / api の開始バインディングフローは登録されますが、実行されません
jobcron の実行間隔と timeRelative の一括処理スケジュールされたフローは起動しません
queueインバウンド Webhook のコンシューマーインバウンド POST は 503 を返します
approvalsapproval / approval_revise(plugin-approvals)ノードタイプに対応する実行エンジンがありません
messaginginbox(sys_inbox_message)への notify 配信警告なし: skipped: true で成功します
webhooksdefineStack({ webhooks }) によるアウトバウンド登録アウトバウンド配信は行われません
defineStack({
  // …
  requires: ['automation', 'triggers', 'job', 'queue', 'approvals', 'messaging'],
});
インバウンド Webhook(api)トリガー(ADR-0041 Tier 1)

api フローはインバウンド HTTP エンドポイントにバインドされます。 POST /api/v1/automation/hooks/:flowName/:hookId。開始ノードの config に設定します(開始ノードの config は、Zod の形状を持たない自由形式のレコードです)。

config キー目的
hookIdURL パスのトークン(デフォルトは 'default')。漏えいしたエンドポイントを無効にするには変更してください
secretHMAC-SHA256 共有シークレット。必須です。空白以外の値が設定されていない場合、フローは登録時に拒否され(os validate も同様)、起動時に有効化されません。署名は x-objectstack-signature に指定します
  • 署名: t=<unix seconds>,v1=<hex> — v1 は secret を使って <t>.<raw body> に対して計算した HMAC-SHA256 です。t がサーバー時刻から 300 秒を超えて離れている場合は拒否されます。
  • 冪等性: x-idempotency-key で再試行の重複を排除します。配信は少なくとも 1 回行われるため、フローが冪等になるように作成してください。
  • キュー経由: エンドポイントは 202 を返してキューに追加します。フローはコンシューマー上で実行され、リクエスト処理中には実行されません。
  • JSON ボディは、トリガーレコード(record.* / フィールド名をそのまま指定)と params としてフローに渡されます。
トリガータイプ(start-node config.triggerType)
triggerType発火タイミングObjectQL フック
record-before-create挿入前(変更または拒否が可能)beforeInsert
record-after-create挿入後afterInsert
record-before-update更新前beforeUpdate
record-after-update更新後afterUpdate
record-before-delete削除前beforeDelete
record-after-delete削除後afterDelete
record-before-write / record-after-write作成または更新 — 1つのフローで両方のイベントに対応挿入と更新の両方のフック
トリガーの設定 — start ノード上

バインド先は START NODE です。これはそのノードであり、フロー全体ではありません(フローには、FlowSchema が要求する label、type、nodes、edges も含まれます)。

{
  id: 'start',
  type: 'start',
  label: 'On Case Escalated',
  config: {
    objectName: 'support_case',
    triggerType: 'record-after-update',
    // bare CEL; gates whether the flow launches on the event
    condition: "previous.status != 'escalated' && record.status == 'escalated'",
  },
}

レコードの変更または拒否が必要な場合を除き、record-after-* を優先してください。自身のオブジェクトに書き込むトリガーにはガードを設定してください。設定しないと、自分自身を再トリガーします。例外をスローする record-before-* フローは、書き込みを通知なしでブロックします。ユーザーに表示するメッセージを設定してください。

更新トリガーの条件における、変更前の previous と変更後の record については、objectstack-formula §5 を参照してください。

→ references/examples-flows.md § 時間基準のトリガー — レコードごとの日付を定期的に確認、に移動しました。


CRM 自動化の設計指針

メタデータアプリでは、まず Flow/Approval メタデータでビジネスライフサイクルをモデル化し、エッジケースの統合にはカスタムコードを使用するのが基本です。この CRM スタイルの構成では、次のようになります。

自動化タイプ一般的な配置場所パターン
画面フローsrc/flows/*.flow.ts明示的な variables、ノードグラフ(nodes + edges)、および分岐を使用します
承認フローsrc/flows/*.flow.tsapproval ノードを含むフローを使用します。ノードの config に approvers / behavior / lockRecord / approvalStatusField を設定し、approve / reject エッジで分岐します
フローレジストリsrc/flows/index.tsallFlows: Flow[] をエクスポートし、defineStack({ flows }) で一元的に登録します

作業の検証

単独の参照(status == 'open')も解決されます。エンジンがレコードのフィールドをスコープに展開するため、そこでのタイプミスに対して表示されるのは「もしかして」の提案だけです。record.status が正式な形式であり、未知の record.<field> は os validate でエラーになります。

os validate     # CEL/predicate validation (record.<field> existence) + schema
# or: os build  # the same gates, plus emits dist/

すべての条件、エッジガード、検証ルール、共有ルールに対して ADR-0032 のゲートが実行されます。スキャフォールドされたプロジェクトでは、npm run validate を実行します。


参考資料

差し戻しの形式には、evals/approvals/test-revise-loop.md に採点付きの評価テストがあります。

Zod スキーマについては、references/_index.md を参照してください(1行の説明と、node_modules/@objectstack/spec/src/ 内の該当箇所へのポインターがあります)。フィールドの正確な形式を確認するには、必ずソースを Read してください。プロパティ名の記憶に頼らないでください。

コメント