LabHub

ブログ

Durable Execution エンジン 2026 — Temporal・Restate・Inngest・Trigger.dev・DBOS 深掘り比較:cron とリトライ地獄から抜け出す道

한국어English日本語

プロローグ — cron とリトライ地獄から抜け出す道

2019 年の backend engineer はこんな code を書いていた。「決済を受け、5 分後に receipt を送り、2 日後にレビュー依頼メールを発送する」。答えはたいていこう。

この pattern は動いた。ただし毎回新しく書き、毎回 bug が出て、毎回 observability が無かった。「どこまで進んだか」を見るには DB を直接掘るしかなかった。

2026 年の答えは違う。

@workflow
async function postPurchaseFlow(orderId: string) {
  await chargeCustomer(orderId)
  await sleep('5 minutes')
  await sendReceipt(orderId)
  await sleep('2 days')
  await sendReviewRequest(orderId)
}

これで終わりだ。関数そのものが workflow 定義になる。server が死んでも、5 分後でも、2 日後でも、関数はまさにその行から再開される。queue も cron も retry_count column も無い。

この魔法の名前が Durable Execution(以下 DE)。2024 年から本格的に話題になり、2026 年には backend infrastructure の標準カテゴリになった。

なぜ今か? AI agent である。1 時間にわたって tool を 30 回呼ぶ agent loop は、server が一度でも死ぬと最初からやり直しになる。それは即ち請求書 — token cost が一度壊れるごとに 5 ドルずつ蒸発する。DE はそれを防ぐ。

この記事の流れはこう。

  1. Durable Execution とは何か — deterministic replay と workflow-as-code
  2. 2026 年の engine landscape 一覧 — comparison matrix
  3. Temporal — DE の標準になった経緯
  4. Restate — 軽量な挑戦者
  5. Inngest — event 駆動の DX 優位
  6. Trigger.dev — Next.js 時代の DE
  7. DBOS — Postgres 1 つで済む light-weight
  8. 同じ workflow を 3 つの engine で — 実 code 比較
  9. AWS Step Functions・Azure Durable Functions・Cadence の立ち位置
  10. AI agent と DE — なぜ爆発したか
  11. 導入 decision tree と anti-pattern

読み終えたら「自分のチームに何が合うか」が 5 分で決まるべきだ。


1 章 · Durable Execution とは何か

1.1 定義 — 意図と実行の分離

Durable Execution は関数の意図(business logic)と実際の実行(いつ・どこで・何回回すか)を分離する paradigm である。関数は普通の code に見えるが、runtime が各 step の進捗を永続 storage に checkpointing する。server が死ぬと別の worker がその行から継続する。

中核の保証:

1.2 2 つの mechanism

DE engine は通常 2 つの流派のどちらか。

Mechanism説明代表
Journal-based replay完了した全 step を append-only log に記録。worker が起きると最初から code を再実行するが、すでに記録された結果は再呼び出しせず cache から使う。Temporal・Cadence・Restate
Database checkpointing各 step 終了時に DB transaction で state を保存。再開時に DB から最終 state を読み、次の step から実行。DBOS・Inngest・Trigger.dev(系統で異なる)

Journal 系は code を deterministic に書く制約がある。例えば Math.random()Date.now() を直接呼ぶと replay 時に違う値が出て壊れる。engine が提供する deterministic API を使う必要がある。

DB checkpointing は制約は緩いが、step の input/output serialization・DB 負荷・transaction 境界が運用の中核になる。

1.3 Workflow-as-code モデル

昔の workflow engine(例: Airflow・初期 Cadence・Step Functions)は DAG・JSON・YAML で書いた。DE はただの code である。iffortryawait がそのまま意味を持つ。

@workflow
async function orderFlow(input: OrderInput) {
  const payment = await ctx.run('charge', () => chargeCard(input))

  try {
    await ctx.run('ship', () => shipItem(input))
  } catch (e) {
    // saga: compensating transaction
    await ctx.run('refund', () => refundCard(payment))
    throw e
  }

  await ctx.sleep('1 day')
  await ctx.run('review-request', () => sendReviewMail(input))
}

これは普通の JS だ。だが runtime は ctx.run ごとに結果を checkpointing し、ctx.sleep は worker memory を占有せず別の worker で再開される。

1.4 DE が解く pattern

DE が輝く workflow の形:

1.5 DE が要らない場合

すべての workflow が DE を呼ぶわけではない。

「workflow が 5 分を超え、外部呼び出しが 3 つ以上で、失敗が高い」→ DE を検討せよ。


2 章 · 2026 年 engine 比較 matrix

項目TemporalRestateInngestTrigger.devDBOS
カテゴリDE 標準・enterpriseDE + state・軽量DE + event・serverlessDE + Next.js 親和DE library・Postgres
Language SDKGo・Java・TS・Python・.NET・PHP・RubyTS・Java・Kotlin・Go・Python・RustTS・Python・Go・JavaTS・PythonTS・Python・Java・Go
Self-host可(Apache 2.0)可(single binary・BSL→Apache)可(Apache 2.0、Inngest server)可(Apache 2.0、v4)可(Apache 2.0、library)
Infra 依存自前 server + Cassandra/PostgresSingle binary + RocksDB自前 server + Postgres自前 server + Postgres + RedisPostgres のみ
Cloud 価格約 0.00025 USD/action から初期段階の SaaS実行数 + event の freemium同時実行 + 月額 freemiumcheckpoint 100 万あたり 50 USD
無料 tierSelf-host 無料Self-host 無料5 万実行/月 無料5 USD クレジット/月Self-host 無料
Workflow modelDeterministic 関数(journal)Deterministic 関数(journal + state key)Step 関数(event trigger)Task 関数(trigger 駆動)Transactional 関数(Postgres)
AI agent 親和度非常に高い(OpenAI Codex 採用)高い(Pydantic AI 統合)非常に高い(Agent Kit)非常に高い(Session・Realtime)中(library 統合)
ObservabilityWeb UI + tctl + OTelWeb UI + OTelWeb UI + live streamWeb UI + live log・triggerDB query + dashboard
最強点scale・multi-language・成熟度軽量・single binaryevent 駆動・DX・価格TS full-stack・time-limit 無し単純・Postgres 1 つ
弱点learning curve・運用負担ecosystem 新生multi-language が弱いTS 中心deterministic replay 無し
想定 user大企業・金融・AI infrafull-stack backend・DB 親和TS・Python startupNext.js・full-stack teamPostgres 中心 backend

この表 1 つで意思決定の 80% が決まる。どの行がチームの deal-breaker かを抜き出せ。たいていは 3 つのどれかだ — 言語・self-host 義務・価格 model。


3 章 · Temporal — DE の標準になった経緯

3.1 出資・採用・規模

Temporal は 2019 年、Cadence(Uber で Maxim Fateev・Samar Abbas が作った OSS)を離れた同じ team による後継だ。2026 年 2 月の Series D は 3 億ドル、valuation 50 億ドル。累計 9.1 兆 action 実行、AI native 企業 1.86 兆。OpenAI Codex が Temporal で毎日数百万件の coding agent request を処理する公開事例がある。NVIDIA・Netflix を含む 3,000 以上の有料 customer。

3.2 Architecture — journal と worker の分離

中核 component:

Worker は state を持たない。workflow 進行 state はすべて server にある。worker が死ぬと別の worker が同じ workflow の次の task を取って history を replay し継続する。

3.3 Deterministic workflow code — 制約と報酬

ルールは単純だが厳格。workflow 関数の中では:

この制約を守ると replay が deterministic になる。workflow code を最初から回しても同じ step sequence が出る。server の history を辿り、すでに終わった activity は cache から結果を返す。

3.4 Activity — 外界と接する場所

// activities.ts
export async function chargeCard(orderId: string): Promise<PaymentReceipt> {
  return await stripe.charge({ orderId })
}

export async function shipItem(orderId: string): Promise<TrackingNumber> {
  return await fedex.createShipment({ orderId })
}

// workflows.ts
import { proxyActivities, sleep } from '@temporalio/workflow'
import type * as activities from './activities'

const { chargeCard, shipItem } = proxyActivities<typeof activities>({
  startToCloseTimeout: '30 seconds',
  retry: { maximumAttempts: 5, initialInterval: '1s', backoffCoefficient: 2 },
})

export async function orderFlow(orderId: string) {
  const receipt = await chargeCard(orderId)
  await sleep('5 minutes')
  const tracking = await shipItem(orderId)
  return { receipt, tracking }
}

Activity は普通の関数。retry・timeout・heartbeat・idempotency が option として乗る。

3.5 価格と self-host

規模と成熟度が武器。学習曲線の急峻さが小 team には弱点。


4 章 · Restate — 軽量な挑戦者

4.1 立ち位置

Restate は「Temporal が重すぎる」という問題意識から始まった。Single binary(Rust 製、内蔵 RocksDB)で動き、外部 DB 依存がない。既存 service の前段に proxy のように挟まり呼び出し journal を捕える。

4.2 核心の差別点

4.3 Code の形

import * as restate from '@restatedev/restate-sdk'

const order = restate.workflow({
  name: 'order',
  handlers: {
    run: async (ctx: restate.WorkflowContext, orderId: string) => {
      const receipt = await ctx.run('charge', () => chargeCard(orderId))
      await ctx.sleep(5 * 60_000)
      const tracking = await ctx.run('ship', () => shipItem(orderId))
      ctx.set('status', 'shipped')
      return { receipt, tracking }
    },
  },
})

restate.endpoint().bind(order).listen(9080)

これを立てて、Restate server を 1 台立てて、登録すれば終わり。

4.4 価格と self-host

Temporal より運用負担が小さい、DBOS より workflow model が表現力ある。ただし ecosystem はまだ新生。


5 章 · Inngest — event 駆動の DX 優位

5.1 立ち位置

Inngest の直観は「event → 関数」モデル。関数が event を subscribe し、関数内のすべての step.run 呼び出しが自動で checkpointing される。workflow 定義が別に無い — 関数自体が workflow。

5.2 Code の形

import { Inngest } from 'inngest'

const inngest = new Inngest({ id: 'shop' })

export const orderFlow = inngest.createFunction(
  { id: 'order-flow' },
  { event: 'order.placed' },
  async ({ event, step }) => {
    const receipt = await step.run('charge', async () => {
      return await chargeCard(event.data.orderId)
    })

    await step.sleep('wait-5m', '5m')

    const tracking = await step.run('ship', async () => {
      return await shipItem(event.data.orderId)
    })

    await step.sleep('wait-2d', '2d')

    await step.run('review-mail', async () => {
      return await sendReviewMail(event.data.orderId)
    })

    return { receipt, tracking }
  }
)

Event → 関数モデルなので domain event 発行と自然に噛み合う。AWS EventBridge 感覚で self-host 可能。

5.3 2026 年の注目点

5.4 価格

TypeScript・Python team、特に Vercel・Next.js deploy と相性がよい。multi-language が弱い。


6 章 · Trigger.dev — Next.js 時代の DE

6.1 立ち位置

Trigger.dev は「Vercel で回せない長い task をどこで回すか」という切実な問題に答える tool だ。v4 で deterministic replay model・session 基盤の双方向 channel・realtime log streaming を導入。

6.2 Code の形

import { task, logger, wait } from '@trigger.dev/sdk/v3'

export const orderFlow = task({
  id: 'order-flow',
  retry: { maxAttempts: 5, factor: 2, minTimeoutInMs: 1000 },
  run: async (payload: { orderId: string }, { ctx }) => {
    const receipt = await chargeCard(payload.orderId)
    logger.info('charged', { receipt })

    await wait.for({ minutes: 5 })

    const tracking = await shipItem(payload.orderId)
    logger.info('shipped', { tracking })

    await wait.for({ days: 2 })

    await sendReviewMail(payload.orderId)

    return { receipt, tracking }
  },
})

3 つの強み:

  1. Timeout 無し: Lambda・Vercel・Cloudflare の時間制限なしに時間単位の task が可能。
  2. 待機中は課金されない: task が wait に入ると container が凍結され課金停止。
  3. Realtime log: user が実行を見ている間 log が SSE で流れる。

6.3 2026 年の注目点

6.4 価格

Next.js・full-stack TS team の default に近い選択肢になった。


7 章 · DBOS — Postgres さえあればよい light-weight

7.1 立ち位置

DBOS は「Postgres があるのにまた infra を立てるのか」に答える。Library で完結。別 server 無し。workflow state は user の Postgres にそのまま保存。

7.2 Code の形(TypeScript)

import { DBOS, Workflow, Step } from '@dbos-inc/dbos-sdk'

class OrderFlow {
  @Step()
  static async chargeCard(orderId: string) {
    return await stripe.charge({ orderId })
  }

  @Step()
  static async shipItem(orderId: string) {
    return await fedex.create({ orderId })
  }

  @Workflow()
  static async run(orderId: string) {
    const receipt = await OrderFlow.chargeCard(orderId)
    await DBOS.sleep(5 * 60_000)
    const tracking = await OrderFlow.shipItem(orderId)
    return { receipt, tracking }
  }
}

@Step は単一 Postgres transaction 内で実行できる — DB 操作なら exactly once を DB 自体が保証する。workflow が失敗すると DB row がそれを記録し、worker 再起動時に同じ workflow ID で再開する。

7.3 強みと弱み

7.4 価格

Postgres 中心の backend team に最も摩擦が少ない。


8 章 · 同じ workflow を 3 つの engine で — 実 code 比較

シナリオ: card 決済 → 4 回まで retry → 成功時に receipt → 2 日後に review mail。同じ形を Temporal・Inngest・Trigger.dev で書く。

8.1 Temporal(TS)

// activities.ts
export async function chargeCard(orderId: string) {
  return await stripe.charge({ orderId })
}
export async function sendReceipt(orderId: string, receipt: Receipt) {
  await mailer.send({ to: orderId, body: receipt })
}
export async function sendReviewMail(orderId: string) {
  await mailer.send({ to: orderId, template: 'review' })
}

// workflow.ts
import { proxyActivities, sleep } from '@temporalio/workflow'
import type * as acts from './activities'

const { chargeCard, sendReceipt, sendReviewMail } = proxyActivities<typeof acts>({
  startToCloseTimeout: '60 seconds',
  retry: {
    maximumAttempts: 4,
    initialInterval: '2s',
    backoffCoefficient: 2,
    nonRetryableErrorTypes: ['CardDeclinedError'],
  },
})

export async function postPurchaseFlow(orderId: string) {
  const receipt = await chargeCard(orderId)
  await sendReceipt(orderId, receipt)
  await sleep('2 days')
  await sendReviewMail(orderId)
  return receipt
}

特徴: retry・timeout・例外分類が activity option に分離。workflow code 自体は綺麗。

8.2 Inngest(TS)

import { Inngest, NonRetriableError } from 'inngest'

const inngest = new Inngest({ id: 'shop' })

export const postPurchaseFlow = inngest.createFunction(
  {
    id: 'post-purchase',
    retries: 4,
  },
  { event: 'order.placed' },
  async ({ event, step }) => {
    const receipt = await step.run('charge', async () => {
      try {
        return await stripe.charge({ orderId: event.data.orderId })
      } catch (e) {
        if (e.code === 'card_declined') {
          throw new NonRetriableError('card declined')
        }
        throw e
      }
    })

    await step.run('send-receipt', () => mailer.send({
      to: event.data.orderId, body: receipt,
    }))

    await step.sleep('wait-2d', '2 days')

    await step.run('review-mail', () => mailer.send({
      to: event.data.orderId, template: 'review',
    }))

    return receipt
  }
)

特徴: retry は関数 option、分類は NonRetriableError を投げる。event payload が workflow input。

8.3 Trigger.dev(TS)

import { task, wait, AbortTaskRunError } from '@trigger.dev/sdk/v3'

export const postPurchaseFlow = task({
  id: 'post-purchase-flow',
  retry: {
    maxAttempts: 4,
    factor: 2,
    minTimeoutInMs: 2000,
    maxTimeoutInMs: 30000,
  },
  run: async (payload: { orderId: string }) => {
    let receipt
    try {
      receipt = await stripe.charge({ orderId: payload.orderId })
    } catch (e) {
      if (e.code === 'card_declined') {
        throw new AbortTaskRunError('card declined')
      }
      throw e
    }

    await mailer.send({ to: payload.orderId, body: receipt })

    await wait.for({ days: 2 })

    await mailer.send({ to: payload.orderId, template: 'review' })

    return receipt
  },
})

特徴: task に単一 retry option。AbortTaskRunError で retry 中止。最も小さい surface area。

8.4 同じ仕事を Go で — Temporal activity

Temporal の強みである multi-language の例:

// activities.go
package activities

import (
    "context"
    "errors"
)

type ChargeInput struct {
    OrderID string
}

type Receipt struct {
    PaymentID string
    Amount    int
}

func ChargeCard(ctx context.Context, input ChargeInput) (*Receipt, error) {
    r, err := stripeClient.Charge(ctx, input.OrderID)
    if err != nil {
        if errors.Is(err, stripe.ErrCardDeclined) {
            return nil, temporal.NewNonRetryableApplicationError(
                "card declined", "CardDeclinedError", err,
            )
        }
        return nil, err
    }
    return &Receipt{PaymentID: r.ID, Amount: r.Amount}, nil
}
// workflow.go
package workflows

import (
    "time"
    "go.temporal.io/sdk/workflow"
)

func PostPurchaseFlow(ctx workflow.Context, orderID string) (*Receipt, error) {
    ao := workflow.ActivityOptions{
        StartToCloseTimeout: time.Minute,
        RetryPolicy: &temporal.RetryPolicy{
            MaximumAttempts:    4,
            BackoffCoefficient: 2.0,
            InitialInterval:    2 * time.Second,
        },
    }
    ctx = workflow.WithActivityOptions(ctx, ao)

    var receipt Receipt
    if err := workflow.ExecuteActivity(ctx, ChargeCard, ChargeInput{OrderID: orderID}).Get(ctx, &receipt); err != nil {
        return nil, err
    }

    if err := workflow.Sleep(ctx, 48*time.Hour); err != nil {
        return nil, err
    }

    if err := workflow.ExecuteActivity(ctx, SendReviewMail, orderID).Get(ctx, nil); err != nil {
        return nil, err
    }
    return &receipt, nil
}

同じ意味。Temporal の強みは Go・Java・Python・.NET など multi-language で統一された model にある。

8.5 まとめ — 同じだが違う

3 つの engine とも「state 付き retry」を綺麗に表現する。違いは周辺環境にある。


9 章 · AWS Step Functions・Azure Durable Functions・Cadence の立ち位置

9.1 AWS Step Functions

古典 classic。JSON・YAML 基盤の ASL(Amazon States Language)。state machine を宣言的に描く model。強み: AWS service 統合が深い(Lambda・SQS・SNS・DynamoDB がほぼ 1 行)。弱み: JSON は code じゃないので大きい workflow の保守が辛い、state 遷移ごとの課金が意外と積もる。

re:Invent 2025 で発表された AWS Lambda Durable Functions が gear を変えた。Lambda 内で直接 deterministic replay workflow が書ける — Python 3.12+・Node.js 22+・TypeScript 5+ 対応。Step Functions の代替であり補完でもある。

9.2 Azure Durable Functions

Microsoft の正統。C#・JS・Python・F#・PowerShell。Orchestrator 関数は deterministic であるべき、Activity 関数が副作用を起こす。Temporal と同じ paradigm だが Azure Functions infrastructure に縛られる。2026 年は .NET Microsoft Agent Framework に統合され agent workflow の標準位置を狙う。

9.3 Cadence

Temporal の親に当たる。Uber では今も core で使われ OSS としても生きている。新規 project なら Temporal を選ぶのがほぼ常に正解 — Temporal が後継で active な方だ。

9.4 それぞれの居場所


10 章 · 2024 ~ 2026 でなぜ爆発したか — AI agent が答え

10.1 5 つの爆発要因

  1. AI agent の長時間実行: LLM 呼び出し → tool 呼び出し → 次の LLM が 30 回反復。1 時間 task が普通になる。server が 1 度死ぬと 50 USD が蒸発する。
  2. Token cost の非対称: compute cost より LLM 呼び出し cost が圧倒的。同じ呼び出しを繰り返さないよう checkpointing が必須。
  3. Big-tech の採用: OpenAI Codex が Temporal で回り、Cloudflare・Vercel・AWS が自前 durable solution を GA。market signal が強くなった。
  4. Framework が 1 級 citizen に: LangGraph・Pydantic AI・OpenAI Agents SDK が DE を option ではなく標準として採用。
  5. Developer experience の改善: 5 年前に比べ Inngest・Trigger.dev・Restate・DBOS が「単一関数・単一 file・単一 infra」で入門ハードルを下げた。

10.2 AI agent loop — DE がまさに解く形

@workflow
async function researchAgent(query: string) {
  let context = []
  for (let i = 0; i < 30; i++) {
    const plan = await ctx.run('llm-plan', () => llm.plan(query, context))
    if (plan.action === 'done') return plan.answer

    const result = await ctx.run(`tool-${i}`, () => tools.run(plan.tool, plan.args))
    context.push({ plan, result })

    // guard: cost 暴走防止
    if (cost(context) > 5) throw new Error('budget exceeded')
  }
}

これを queue と cron で書いたことのある人だけが知る — debug 地獄。DE はこれを 1 関数に縮める。


11 章 · 導入 decision tree

workflow が 5 分を超えるか?
├── いいえ → DE 不要。HTTP・gRPC で十分。
└── はい
    ├── 外部呼び出しが 3 つ以上か?
    │   ├── いいえ → queue + idempotency key で十分かも。
    │   └── はい
    │       ├── multi-language が必要か?
    │       │   ├── はい → Temporal・Restate
    │       │   └── いいえ(TS 中心)
    │       │       ├── event 駆動 domain? → Inngest
    │       │       ├── Next.js full-stack? → Trigger.dev
    │       │       └── Postgres 1 つで終わらせたい? → DBOS
    │       └── self-host が義務か?
    │           ├── はい → Temporal・Restate・Inngest・Trigger.dev・DBOS の self-host
    │           └── いいえ → cloud SaaS すべて OK
    └── AI agent loop か?
        ├── はい → Temporal・Inngest・Trigger.dev(この 3 つが最強)
        └── いいえ → 一般 matrix で選択

追加分岐:


12 章 · Anti-pattern

12.1 Workflow 内で直接 時刻・random・外部呼び出し

Deterministic model(Temporal・Restate)の workflow で Date.now()Math.random()fetch() をそのまま呼ぶと replay 時に違う値が出て壊れる。必ず engine API(ctx.now()ctx.random()ctx.run())で包む。

12.2 すべての関数を step に分割

Step(activity)は checkpointing cost がある。1ms の純粋関数まで step にすると workflow が 100 倍遅くなる。外部副作用・外部呼び出し・retry 単位が step の境界。

12.3 DE 1 つで ETL・Kafka・DAG まで処理

DE は transactional・長時間・state ある workflow 用。data pipeline は Airflow・dbt・Spark が適切。2 つを混ぜると両方ぎこちなくなる。

12.4 Self-host を運用人員無しで

Temporal cluster を EC2 に立てて終わり。6 ヶ月後 Cassandra disk が満杯で backup も無い。DE の self-host は無料じゃない。運用 cost を cloud SaaS cost と比較すべき。

12.5 Workflow code 変更に versioning 無し

実行中の workflow の code を変えると古い worker が新しい history に出会って壊れる。すべての DE engine は versioning API(Patched・Workflow Versioning・Side Effects)を提供する。変更ごとに version を切れ

12.6 AI agent に無制限 tool・予算

LLM 呼び出し・tool 呼び出しが deterministic なのは良いが無限 loop は deterministic でも高い。tool whitelist と予算 guard が workflow 内に入る必要がある。

12.7 Idempotency key 無しの外部 API 呼び出し

Activity が retry できるということは外部 API も同じ呼び出しを 2 度受け得るという意味。idempotency key を入力に含め、外部 API が対応していれば header(Idempotency-Key)で送る。Stripe・SendGrid・Slack はすべて対応。


エピローグ — 「engine を選ぶ」ではなく「workflow 思考を買う」

Durable Execution engine の本当の価値は道具ではない。workflow を code として見る思考だ。queue と cron で組む backend と、関数 1 つで組む backend は思考 model 自体が違う。

導入 checklist:

Anti-pattern 短い list:

次回予告

次回は Temporal self-host production guide — Cassandra と Postgres backend の選び方、multi-cluster・HA・blue-green worker deploy、Worker Versioning と workflow migration、cost・observability まで。6 ヶ月で 1 つの会社が壊したことを整理する。Temporal Cloud の請求書が月 5,000 USD を超え始めたら、その記事が次の意思決定の基準になる。

Durable Execution は単一道具の選択ではない。時間そのものを関数の 1 級 input として扱う行為だ。その思考を買えば、道具は後からついてくる。


参考 / References

Temporal

Restate

Inngest

Trigger.dev

DBOS

AWS・Azure・Cadence・調査

Pattern・AI Agent・比較

コメント

まだコメントはありません。

ログインするとコメントできます