はじめに
hkob の雑記録の第493回目(連続66日目)は、worker で作成されるテンプレートを確認してみます。後半では、テンプレートで作成される INSTRUCTIONS.md の中で実際に AI にどんな指示がされているのかを読んでみようと思います。
テンプレートの作成
昨日解説した ntn コマンドで worker のテンプレートを作成することができます。まずコマンドを実行してみます。
hkob@hM5Air ~/D/w/test_worker> ntn workers new ✔ Directory (/Users/hkob/Library/CloudStorage/Dropbox/workers/test_worker) · . ✔ Template downloaded ✔ Initialize a git repository? · yes ✔ Install dependencies? · yes ✔ Template files extracted ✔ Git repository initialized ✔ Dependencies installed with npm ✔ Worker project created Next steps: 1. ntn workers deploy
テンプレートのディレクトリ構成
作成したフォルダで tree -a した結果を示します。.git や node_modules の中身はあまり関係ないので省略しています。
実際の worker のプログラムは src/index.ts に記載していきますが、それをサポートするための AI への指示書が大量に用意されています。AGENTS.md や CLAUDE.md などそれぞれの AI への指示書は .agents の下の INSTRUCTIONS.md にシンボリックリンクされています。また、.claude/skills も .agents/skills へシンボリックリンクが貼られています。.examples の下にも automation, oauth, sync, tool, webhook などのサンプルの ts ファイルが用意されています。
. ├── .agents │ ├── INSTRUCTIONS.md │ └── skills │ ├── auth-guide │ │ └── SKILL.md │ ├── sync │ │ └── SKILL.md │ ├── sync-debug │ │ └── SKILL.md │ ├── sync-guide │ │ ├── api-pagination-patterns.md │ │ ├── examples │ │ │ ├── incremental-basic.ts │ │ │ ├── incremental-bimodal.ts │ │ │ ├── incremental-events.ts │ │ │ ├── replace-paginated.ts │ │ │ └── replace-simple.ts │ │ └── SKILL.md │ └── sync-validate │ └── SKILL.md ├── .claude │ └── skills -> ../.agents/skills ├── .claudeignore ├── .codexignore ├── .examples │ ├── automation-example.ts │ ├── oauth-example.ts │ ├── sync-example.ts │ ├── tool-example.ts │ └── webhook-example.ts ├── .git │ ├── 省略 ├── .gitignore ├── AGENTS.md -> .agents/INSTRUCTIONS.md ├── CLAUDE.md -> .agents/INSTRUCTIONS.md ├── docs │ └── custom-tool.png ├── node_modules │ ├── 省略 ├── package-lock.json ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json 139 directories, 1125 files
INSTRUCTIONS.md の中身
実際に AI にどんな指示がされているのかを確認するために、INSTRUCTIONS.md の中身を確認してみます。Markdown の指示自体は英語なので、Notion に貼り付けて翻訳してもらいました。部分ごとに切り出してコメントを書いてみます。
プロジェクト構成とモジュール構成
src/index.tsが Worker と Capability を定義する。.examples/に、Sync / Tool / Automation / OAuth / Webhook の用途別サンプルがある。- 共有のエージェント Skill は
.agents/skills/に置く。.claude/skillsは Claude 向け検出の互換性のためのシンボリックリンクとして維持する。- 生成物:
dist/(ビルド出力)、workers.json(CLI 設定)。
最初は上で解説したようにフォルダ構成を説明しています。ほぼ上で書いたことが書かれていました。
Worker と Capability API(SDK)
@notionhq/workersはWorker、スキーマヘルパー、ビルダーを提供する。ntnCLI は Worker の管理を担う。- Capability key は CLI が参照する一意の文字列(例:
ntn workers exec tasksSync)。import { Worker } from "@notionhq/workers"; import * as Builder from "@notionhq/workers/builder"; import * as Schema from "@notionhq/workers/schema"; const worker = new Worker(); export default worker; // Sync の書き込み先データベースを宣言(Sync 以外からは書き込まない — 汎用ストレージとしては使わない) const tasks = worker.database("tasks", { type: "managed", initialTitle: "Tasks", primaryKeyProperty: "ID", schema: { properties: { Name: Schema.title(), ID: Schema.richText() } }, }); // 上流 API 向けの pacer を宣言 const myApi = worker.pacer("myApi", { allowedRequests: 10, intervalMs: 1000 }); // データベースへ書き込む Sync を宣言 worker.sync("tasksSync", { database: tasks, execute: async (state) => { await myApi.wait(); const items = await fetchItems(state?.page ?? 1); return { changes: items.map((i) => ({ type: "upsert" as const, key: i.id, properties: { Name: Builder.title(i.name), ID: Builder.richText(i.id) }, })), hasMore: false, }; }, }); worker.tool("sayHello", { title: "Say Hello", description: "Return a greeting", schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"], additionalProperties: false }, execute: ({ name }, { notion }) => `Hello, ${name}`, }); worker.oauth("googleAuth", { name: "my-google-auth", authorizationEndpoint: "<https://accounts.google.com/o/oauth2/v2/auth>", tokenEndpoint: "<https://oauth2.googleapis.com/token>", scope: "openid email", clientId: process.env.GOOGLE_CLIENT_ID ?? "", clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "", }); worker.webhook("onGithubPush", { title: "GitHub Push Webhook", description: "Handles push events from GitHub", execute: async (events, { notion }) => { for (const event of events) { console.log("Push:", event.body); } }, });
ここでは taskSync を例に、Worker の import 方法などが解説されています。ここから副節で細かく解説しています。
Notion API アクセス(context.notion)
すべての
executeハンドラはcontext.notionオブジェクト(@notionhq/clientの SDK インスタンス)を受け取る。これを使って Notion API を呼び出せる。ただし、
context.notionが 事前認証済み になるのは、Custom Agent から呼び出された tool capability の場合のみ。その場合、プラットフォームが Custom Agent の権限でNOTION_API_TOKENを自動設定するため、追加設定は不要。それ以外(sync / automation / webhook)の capability では
context.notionは 事前認証されない。ユーザーが自分でNOTION_API_TOKEN環境変数を設定する必要がある(手順):
- https://www.notion.so/profile/integrations/internal で internal integration を作成
- その integration に、対象のページ・データベースへのアクセス権を付与
- トークンをローカルの
.envに追加、またはデプロイ先にはntn workers env pushで反映
context.notionを non-tool capability で使うコードを書く前に、NOTION_API_TOKENが設定されているか確認する(例:grep -q '^NOTION_API_TOKEN=' .env)。未設定なら、https://www.notion.so/profile/integrations/internal で internal integration を作成して.envに追加するよう促す。
- ユーザー管理の OAuth(上記例)では、
name、authorizationEndpoint、tokenEndpoint、clientId、clientSecret、scope(任意:authorizationParams、callbackUrl、accessTokenExpireMs)を指定する。- 注意: Notion 管理の OAuth 省略形(
{ provider: "google" })も存在するが alpha で、多くのユーザーは利用できない。上記のユーザー管理構成を使う。- OAuth capability を含む worker をデプロイした後、OAuth プロバイダ側のリダイレクト URL を Notion が割り当てたものに合わせて設定する必要がある。
ntn workers oauth show-redirect-urlでリダイレクト URL を取得し、プロバイダの OAuth アプリ設定に反映する。OAuth capability をデプロイしたら必ずこの手順を案内する。
認証については、worker がどの立場で利用されるのかが変わるようです。
- tool capability: Custom Agent の権限で事前認証
- sync / automation / webhook: internal integration を利用。それを .env に事前に追加
- Oauth: ntn コマンドでリダイレクト URL を取得し、相手先の OAuth アプリに設定
Sync
ここから先は worker の種類ごとに説明が記載されています。最初は Sync です。
データベース、Pacer、Sync
worker.database()は sync の書き込み先(sync target)を宣言する。worker 観点ではデータベースは読み取り専用で、書き込みは sync 経由でのみ可能。 webhook payload、tool 結果、作業用データなどの汎用ストレージとしてworker.database()を使わない。sync 以外で Notion に書き込むにはcontext.notion(Notion SDK クライアント)を直接使う。データベースはハンドルとして宣言し参照する:
// 1. データベースを宣言 const tasks = worker.database("tasks", { type: "managed", initialTitle: "Tasks", primaryKeyProperty: "Task ID", schema: { properties: { "Task Name": Schema.title(), "Task ID": Schema.richText(), Status: Schema.select([{ name: "Open" }, { name: "Done", color: "green" }]), }, }, }); // 2. 上流 API 向け pacer を宣言 const myApi = worker.pacer("myApi", { allowedRequests: 10, intervalMs: 1000 }); // 3. Sync を宣言 worker.sync("tasksSync", { database: tasks, schedule: "30m", execute: async (state) => { await myApi.wait(); const { items, hasMore } = await fetchTasks(state?.page ?? 1); return { changes: items.map((item) => ({ type: "upsert" as const, key: item.id, properties: { "Task Name": Builder.title(item.name), "Task ID": Builder.richText(item.id), Status: Builder.select(item.status), }, })), hasMore, nextState: hasMore ? { page: (state?.page ?? 1) + 1 } : undefined, }; }, });複数の sync が同じデータベースに書き込める。複数の sync が同じ pacer を共有でき、その場合はサーバーが、その pacer を使用する sync 群へ予算を均等配分する。
今回の Developer Platform の売りの一つである外部ツールとのデータ同期を行うのが Sync です。Sync の場合データベースは読み込みのみが可能であり、書き込みは Sync の機能を使うだけとなります。Worker の中で同期以外のデータベースへ結果を書き込みたい場合には、 context.notion の機能を使う必要があるようです。
Pacer(レート制限)
外部 API を呼ぶ sync には 必ず pacer を宣言 する。実装前に API のレート制限を調べる。制限が可変(例: Salesforce は購入で増える)なら、割り当てる予算をユーザーに確認する。
execute内の すべての API リクエストの前にawait pacer.wait()を呼ぶ。- pacer は interval ウィンドウ内でのリクエスト間隔を均等化する。
- 例: 4 つの sync が
allowedRequests: 100, intervalMs: 60_000を共有すると、各 sync は約 25 req/min を得る。const myApi = worker.pacer("myApi", { allowedRequests: 10, intervalMs: 1000 }); // execute 内: await myApi.wait(); const data = await fetchFromApi();
外部 API にはアクセスのレート制限が設定されているものがあります。ここでは、pacer という仕組みで上限設定をできるようです。複数の Sync が動作する場合もあるので、その場合にはそれぞれの Sync にうまく配分されるようです。
Sync 戦略の選び方
単純な置換(replace)sync — 本当に小規模(<1k)か、API に変更追跡が無い場合。1 つの sync、replace モード。各サイクルで全件を返し、返ってこなかったレコードは mark-and-sweep で削除する。
バックフィル + デルタのペア — それ以外のほぼすべて(推奨)。同じデータベースへ書く 2 つの sync:
- Backfill(replace、
schedule: "manual"): 上流データ全体をページングして取得。CLI から手動実行。ドリフトの修正、スキーマ追加時の埋め戻し、デルタで検出できない削除の補足に使う。- Delta(incremental、短い間隔の schedule 例
"5m"/"30m"): 最近の変更のみ(updated_since、変更フィード等)を取得。API 使用量を抑えつつ Notion を最新に保つ。上流 API が何らかの変更追跡(
updated_since、modified_after、change feed、webhook)を提供するなら、backfill + delta を選ぶ(Salesforce, Jira, Linear, Stripe, GitHub など多くが該当)。
Sync 戦略は大きく分けて二つあるようです。小規模の場合には単純な置換が選ばれますが、ほとんどの場合には Backfill と Delta が選ばれるようです。ほとんどの外部ツールは変更追跡ができるものが多いので、その場合には Delta が使えるようです。
削除の扱い
- API がデルタで削除も返す(変更フィードに deleted が含まれる): delta sync で
{ type: "delete", key }を出す。- API は削除を返さないが、削除が稀/重要でない(例: Stripe の subscription は delete ではなく cancel): 追加対応不要(上流レコードは状態が変わるだけで存在し続ける)。
- API は削除を返さず、削除が重要: backfill sync で対応する。replace モードの mark-and-sweep が、上流に存在しないレコードを削除する。
削除の扱いは相手先の状況によって対応が分かれます。相手が削除について教えてくれない場合には、削除がクリティカルでなれば無理しないでそのまま残すように指示が書かれています。削除が問題となる場合には、上で示した全取得して存在しないものを確認する必要があるようです。どのデータベースシステムでも削除については、様々な問題がつきまといますね。
単純置換 Sync の例
const records = worker.database("records", { type: "managed", initialTitle: "Records", primaryKeyProperty: "ID", schema: { properties: { Name: Schema.title(), ID: Schema.richText() } }, }); const myApi = worker.pacer("myApi", { allowedRequests: 10, intervalMs: 1000 }); worker.sync("recordsSync", { database: records, mode: "replace", schedule: "1h", execute: async (state) => { const page = state?.page ?? 1; await myApi.wait(); const { items, hasMore } = await fetchPage(page, 100); return { changes: items.map((item) => ({ type: "upsert" as const, key: item.id, properties: { Name: Builder.title(item.name), ID: Builder.richText(item.id) }, })), hasMore, nextState: hasMore ? { page: page + 1 } : undefined, }; }, });
ここでは単純 Sync の例が示されています。 mode が replace となっています。
Backfill + Delta の例
const tasks = worker.database("tasks", { type: "managed", initialTitle: "Tasks", primaryKeyProperty: "Task ID", schema: { properties: { "Task Name": Schema.title(), "Task ID": Schema.richText(), Status: Schema.select([{ name: "Open" }, { name: "Done", color: "green" }]), }, }, }); const taskApi = worker.pacer("taskApi", { allowedRequests: 10, intervalMs: 1000 }); // Backfill: 全件をページングし、手動実行。 // 再 backfill: ntn workers sync state reset tasksBackfill && ntn workers sync trigger tasksBackfill worker.sync("tasksBackfill", { database: tasks, mode: "replace", schedule: "manual", execute: async (state) => { const page = state?.page ?? 1; await taskApi.wait(); const { items, hasMore } = await fetchAllTasks(page, 100); return { changes: items.map((item) => ({ type: "upsert" as const, key: item.id, properties: { "Task Name": Builder.title(item.name), "Task ID": Builder.richText(item.id), Status: Builder.select(item.status), }, })), hasMore, nextState: hasMore ? { page: page + 1 } : undefined, }; }, }); // Delta: 最近の変更を取得し、5 分ごとに実行。 worker.sync("tasksDelta", { database: tasks, mode: "incremental", schedule: "5m", execute: async (state) => { const cursor = state?.cursor; await taskApi.wait(); const { items, nextCursor } = await fetchTaskChanges(cursor); return { changes: items.map((item) => ({ type: "upsert" as const, key: item.id, properties: { "Task Name": Builder.title(item.name), "Task ID": Builder.richText(item.id), Status: Builder.select(item.status), }, })), hasMore: Boolean(nextCursor), nextState: nextCursor ? { cursor: nextCursor } : undefined, }; }, });
Backfill + Delta の場合には、それぞれのメソッドを記載しておきます。Delta は 5 分おきに Backfill は指定された時にだけ起動するような形で同期が実行されるサンプルになっています。
ページネーション
sync は「sync cycle」(実行チェーン)で動く。スケジュール起動から始まり、
hasMore: falseを返すまでexecuteが連続実行される。
- 常にページングする。一度に大量の changes を返すと失敗する。バッチサイズはおおむね 100 から開始する。
- 続きがある場合は
hasMore: trueとnextStateを返す。終了はhasMore: false。nextStateはシリアライズ可能であれば何でも良い(cursor 文字列、ページ番号、タイムスタンプ、複合オブジェクトなど)。
Notion API で query する際の Pagenation と同じ仕組みを Worker 側にも Pagenation として実装すればいいようです。Notion API でも cursor 文字列は page_id を返さなくなりました。一意に特定できる文字列を設定すればいいようです。
スケジュール
sync の
scheduleで実行頻度を制御する:
"continuous": 可能な限り高速に実行"manual": CLI トリガーのみ- インターバル文字列:
"5m","30m","1h","1d"(最小"1m"、最大"7d")- デフォルト:
"30m"
実行頻度は schedule キーで定義します。上にあった Backfill + Delta の例では Backfill は manual で、 Delta は 5m が指定されていました。
リレーション
2 つのデータベースは
Schema.relation(syncKey)とBuilder.relation(primaryKey)で関連付けできる:const projects = worker.database("projects", { type: "managed", initialTitle: "Projects", primaryKeyProperty: "Project ID", schema: { properties: { "Project Name": Schema.title(), "Project ID": Schema.richText() } }, }); const tasks = worker.database("tasks", { type: "managed", initialTitle: "Tasks", primaryKeyProperty: "Task ID", schema: { properties: { "Task Name": Schema.title(), "Task ID": Schema.richText(), // 関連データベースを埋める sync key を参照 Project: Schema.relation("projectsSync", { twoWay: true, relatedPropertyName: "Tasks" }), }, }, }); worker.sync("projectsSync", { database: projects, execute: async () => { ... } }); worker.sync("tasksSync", { database: tasks, execute: async () => ({ changes: [{ type: "upsert" as const, key: "task-1", properties: { "Task Name": Builder.title("Write docs"), "Task ID": Builder.richText("task-1"), Project: [Builder.relation("proj-1")], // relation 参照の配列 }, }], hasMore: false, }), });
一つのデータベースだけではなく、リレーションも同時に設定する場合には、Schema.relation などを使って同時に設定するようです。この部分はまだあまり理解できていません。ここまでが Sync に関する説明でした。
Webhook
Webhook は外部サービスが呼び出せる HTTP エンドポイントを公開する。デプロイ後に CLI が webhook URL を表示する。
ntn workers webhooks listでいつでも URL を確認できる。execute ハンドラは
WebhookEventの配列を受け取る。各 event はdeliveryId(リトライ間で安定な idempotency key)、body(パース済み JSON)、rawBody(署名検証用の文字列)、headers、methodを持つ。worker.webhook("onExternalEvent", { title: "External Event Handler", description: "Processes incoming webhook requests", execute: async (events, { notion }) => { for (const event of events) { console.log("Method:", event.method); console.log("Body:", JSON.stringify(event.body)); // event.headers でリクエストヘッダにアクセスできる } }, });セキュリティ: 各 webhook は URL パス内に一意の ID を持ち、共有シークレットの役割を果たす。URL 形式:
<{{https://www.notion.so/webhooks/worker/{spaceId}}}/{workerId}/{uniqueWebhookId}/{webhookName}>この完全 URL は
ntn workers webhooks listで取得できる。また、webhook の検証は worker 側の責務。payload が無効なら
WebhookVerificationErrorを投げる。無効 payload が 5 回連続すると、再デプロイまで webhook が短絡(ショートサーキット)する。
これまで Notion から外部の Webhook を呼び出すことはできましたが、外部のツールから Notion に対して Webhook として呼び出すことはできませんでした。今回は、Webhook worker を作ることで、外部ツールから Notion を叩くことができるようになります。
Sync 管理(CLI)
Sync 状態の監視:
ntn workers sync status # 5 秒ごとにポーリングするライブ表示 ntn workers sync status <key> # 特定の sync capability に絞る ntn workers sync status --no-watch # 1 回だけ表示して終了 ntn workers sync status --interval 10 # ポーリング間隔(秒)ステータスラベル:
- HEALTHY — 直近の実行が成功
- INITIALIZING — デプロイ済みだがまだ成功していない
- WARNING — 連続 1〜2 回の失敗
- ERROR — 連続 3 回以上の失敗
- DISABLED — capability が無効化されている
Sync のプレビュー(書き込みなし):
ntn workers sync trigger <key> --preview # execute を実行して出力を表示(書き込まない) ntn workers sync trigger <key> --preview --context '{"page":2}' # 前回プレビューの nextContext から再開preview は sync の
executeを実行し、生成されるオブジェクトを表示するが Notion データベースへ書き込まない。ロジック検証とデータ確認に使う。パイプした場合は生 JSON を出力する。Sync のトリガー(即時書き込み):
ntn workers sync trigger <key>trigger は通常スケジュールを待たず、即座に実 sync cycle を開始してデータベースへ書き込む。すぐ反映したいときに使う。
Sync state のリセット(最初から):
ntn workers sync state reset <key>cursor と stats をクリアし、次回実行を最初からにする。
Sync の有効化 / 無効化:
ntn workers capabilities list # capability 一覧 ntn workers capabilities disable <key> # sync を停止 ntn workers capabilities enable <key> # sync を再開注意:
ntn workers deployは sync state をリセットしない。デプロイ後も最後の cursor から再開する。最初からにしたい場合は明示的にntn workers sync state reset <key>を実行する。
上で作成した Sync は ntn コマンドで制御できるようです。先ほど手動に設定した Backfill などは、ここで示した ntn コマンドの trigger で強制実行できるようです。
データベースのクエリ
ntn datasources query <data-source-id>でデータベース内のページを列挙できる。引数は database ID ではなく data source ID。Notion の database は 1 つ以上の data source を含むコンテナで、公開 API は data source を直接クエリする。database ID しかない場合は、まず
ntn datasources resolve <database-id>で含まれる data source を列挙する:ntn datasources resolve <database-id>data source が 1 つなら、その ID を使って query を再実行する。複数なら、目的の名前に合うものを選ぶ。
ntn datasources query <id>が 404 や "Could not find data source" を返す場合、その ID は database ID の可能性が高い。その場合はresolveを実行して、出てきた data source ID を使って再実行する。
データベースのクエリというタイトルだが、Notion API などで実際に query を投げるのはデータソースです。ただ、人間が AI に指示する時にはデータベースと言われてしまうので、その対策のために説明していると思います。ある程度慣れたユーザでなければ、ほとんどのデータベースには一つのデータソースしか入っていない場合が多いので、404 が帰ってきた時には人間に問い合わせを返すのではなく、自分でデータソースを探して指示に従うように処理を止めないために記載された説明書ですね。
ビルド、テスト、開発コマンド
- Node >= 22、npm >= 10.9.2(
package.jsonの engines を参照)。npm run build: TypeScript をdist/にコンパイルnpm run check: 型チェックのみ(emit なし)ntn login: Notion ワークスペースへ接続ntn workers deploy: ビルドして capability を公開(sync state はリセットしない)ntn workers exec <capability>: sync または tool を実行ntn workers sync status: sync のヘルス監視(ライブ更新)ntn workers sync trigger <key> --preview: 書き込みなしで出力を確認ntn workers sync trigger <key>: 即時同期(書き込みあり)
ここには ntn コマンドの説明が書かれています。困ったらここを見るようにします。
実行のデバッグと監視
ntn workers runsで実行履歴とログを確認できる。直近の run を列挙:
ntn workers runs list特定 run のログ:
ntn workers runs logs <runId>最新 run(全 capability)のログ:
ntn workers runs list --plain | head -n1 | cut -f1 | xargs -I{} ntn workers runs logs {}特定 capability の最新 run のログ:
ntn workers runs list --plain | grep tasksSync | head -n1 | cut -f1 | xargs -I{} ntn workers runs logs {}
--plainはタブ区切りで装飾なし出力のため、パイプ処理しやすい。
実際に AI に実装させるといろんなエラーが出ることがあります。デバッグするにはどうすればいいかが説明されています。これは人間にとってもいい解説書ですね。私も勉強になります。
Sync のデバッグ
sync health の確認:
ntn workers sync status失敗回数、エラーメッセージ、最終成功時刻を見る。
sync が動かない? capability が無効化されていないか確認:
ntn workers capabilities list書き込みなしで確認:
ntn workers sync trigger <key> --preview失敗した sync の再試行(書き込みあり):
ntn workers sync trigger <key>状態が壊れた? cursor をリセットして再開:
ntn workers sync state reset <key>
Sync worker の場合に、状態確認するためのコマンドが列記されています。上にもコマンドが書いてありましたが、デバッグ方法としても再記載されているようです。
コーディングスタイルと命名規則
strictを有効にした TypeScript。I/O を整形する箇所は型を明示する。- インデントはタブ。capability key は lowerCamelCase。
実際に AI がコードを書くときのコーディング規則が書かれています。手動で書くときもこれにしたがって書くようにしましょう。
テストガイドライン
- テストランナーは未設定。
npm run checkとntn workers execによる E2E で検証する。- 各 tool capability を代表入力で実行するテストスクリプトを作る(bash の
test.shか TypeScript のtest.tsをnpx tsx test.tsで実行)。ローカル実行は--local、デプロイ先実行は省略。ローカル実行 は手元の worker コードを直接実行し、プロジェクトルートの
.envが自動ロードされるため、秘密情報と設定はprocess.env経由で利用できる。リモート実行(
--localなし)はデプロイ済み worker に対して動く。必要な秘密情報は事前にntn workers env pushでデプロイ先環境へ反映する。bash テストスクリプト例(
test.sh):#!/usr/bin/env bash set -euo pipefail # ローカル実行(.env を自動ロード): ntn workers exec sayHello --local -d '{"name": "World"}' # もしくはデプロイ先実行(事前に `ntn workers deploy` と `ntn workers env push` が必要): # ntn workers exec sayHello -d '{"name": "World"}'TypeScript テストスクリプト例(
test.ts、npx tsx test.tsで実行):import { execSync } from "child_process"; function exec(capability: string, input: Record<string, unknown>) { const result = execSync( `ntn workers exec ${capability} --local -d '${JSON.stringify(input)}'`, { encoding: "utf-8" }, ); console.log(result); } exec("sayHello", { name: "World" });このパターンで exec 呼び出しを積み上げ、各 tool を代表入力でカバーする。
作成したものが正しく動作しているのかを確認する方法として、bash によるテスト、ts によるテストのやり方が記載されています。AI が作成した場合でも、これらのテストを使って、実装したものが仕様通りに動作するかを確認するように指示しているのだと思います。
コミットと Pull Request ガイドライン
- メッセージは
feat(scope): ...、TASK-123: ...、バージョン bump などが一般的。- PR には変更内容、実行したコマンド、必要に応じて例(examples)の更新を含める。
最後に生成物を git にコミットする際のルールを記載しています。
おわりに
INSTRUCTIONS.md の解説だけでもかなり長くなってしまいました。私は Ruby ばっかりで TypeScript はあまり詳しくないので、これを機に勉強して何か書いてみようかと思いました。AI のための指示書ですが、この分野に詳しくない人にもいい学習資料になるのではないかと感じました。
https://hkob.notion.site/hkob-16dd8e4e98ab807cbe3cf3cc94cdfe0f?pvs=4hkob.notion.site