處理跨 Frame 的複雜業務流程,協調多個 Sub-agent (command/query/reactor) 完成 Saga/Choreography 模式的分散式交易。當需求涉及多個狀態變更、事件反應與查詢的組合時使用。
/plugin marketplace add knowlet/skills/plugin install knowlet-skills@knowlet/skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
runSubagent 進行任務分解一個完整的業務流程可能包含:
使用者下單 (CBF)
↓
庫存扣減 (CBF)
↓
發布 OrderCreated 事件 (RIF)
↓
通知倉儲系統 (RIF)
↓
更新訂單報表 (IDF)
# docs/specs/{saga-name}.yaml
metadata:
name: "{saga-name}"
type: "saga"
version: "1.0.0"
saga_steps:
- step: 1
name: "create-order"
frame_type: "CBF"
sub_agent: "command-sub-agent"
action: "建立訂單"
compensation: "取消訂單"
output_event: "OrderCreated"
- step: 2
name: "reserve-inventory"
frame_type: "CBF"
sub_agent: "command-sub-agent"
action: "扣減庫存"
compensation: "恢復庫存"
trigger_event: "OrderCreated"
output_event: "InventoryReserved"
- step: 3
name: "process-payment"
frame_type: "RIF"
sub_agent: "reactor-sub-agent"
action: "處理付款"
compensation: "退款"
trigger_event: "InventoryReserved"
output_event: "PaymentProcessed"
- step: 4
name: "update-analytics"
frame_type: "IDF"
sub_agent: "query-sub-agent"
action: "更新報表快取"
trigger_event: "PaymentProcessed"
# 無補償,查詢面向最終一致
error_handling:
strategy: "backward-compensation"
max_retries: 3
timeout_per_step: "30s"
在 Claude Code 中,可透過 runSubagent 將任務分派給專責的子代理:
當處理複雜的跨 Frame 流程時,我會:
1. 先用 analyze-frame 判斷每個步驟的 Frame 類型
2. 使用 runSubagent 分派任務給對應的 sub-agent
3. 收集結果並協調下一步驟
| 任務類型 | 分派給 | 說明 |
|---|---|---|
| 狀態變更 | command-sub-agent | 處理 Command、Aggregate、Domain Event |
| 資料查詢 | query-sub-agent | 處理 Query、Read Model、快取 |
| 事件反應 | reactor-sub-agent | 處理 Event Handler、整合、重試 |
對於「使用者下單」流程,Orchestrator 會這樣分派:
Step 1: 分派給 command-sub-agent
請根據 create-order.yaml 規格,生成 CreateOrderUseCase:
- 輸入:customerId, items, shippingAddress
- 輸出:orderId, status
- 需發布 OrderCreated 事件
Step 2: 分派給 reactor-sub-agent
請設計 OrderCreatedHandler 處理 OrderCreated 事件:
- 觸發庫存扣減
- 設計冪等策略
- 設計重試與死信機制
Step 3: 分派給 query-sub-agent
請更新 OrderDashboardQuery:
- 當 PaymentProcessed 事件發生時更新快取
- 設計投影更新策略
// saga/OrderSaga.ts
import { SagaStep, SagaOrchestrator } from './saga-framework';
interface OrderSagaContext {
orderId?: string;
customerId: string;
items: OrderItem[];
inventoryReservationId?: string;
paymentId?: string;
}
export class OrderSaga extends SagaOrchestrator<OrderSagaContext> {
protected defineSteps(): SagaStep<OrderSagaContext>[] {
return [
{
name: 'createOrder',
execute: async (ctx) => {
const result = await this.commandBus.execute(
new CreateOrderCommand(ctx.customerId, ctx.items)
);
ctx.orderId = result.orderId;
},
compensate: async (ctx) => {
await this.commandBus.execute(
new CancelOrderCommand(ctx.orderId!)
);
},
},
{
name: 'reserveInventory',
execute: async (ctx) => {
const result = await this.commandBus.execute(
new ReserveInventoryCommand(ctx.orderId!, ctx.items)
);
ctx.inventoryReservationId = result.reservationId;
},
compensate: async (ctx) => {
await this.commandBus.execute(
new ReleaseInventoryCommand(ctx.inventoryReservationId!)
);
},
},
{
name: 'processPayment',
execute: async (ctx) => {
const result = await this.commandBus.execute(
new ProcessPaymentCommand(ctx.orderId!)
);
ctx.paymentId = result.paymentId;
},
compensate: async (ctx) => {
await this.commandBus.execute(
new RefundPaymentCommand(ctx.paymentId!)
);
},
},
];
}
}
// saga/order_saga.go
package saga
import (
"context"
"fmt"
)
type OrderSagaContext struct {
OrderID string
CustomerID string
Items []OrderItem
InventoryReservation string
PaymentID string
}
type OrderSaga struct {
commandBus CommandBus
steps []SagaStep[*OrderSagaContext]
}
func NewOrderSaga(commandBus CommandBus) *OrderSaga {
saga := &OrderSaga{commandBus: commandBus}
saga.steps = []SagaStep[*OrderSagaContext]{
{
Name: "createOrder",
Execute: func(ctx context.Context, sagaCtx *OrderSagaContext) error {
result, err := saga.commandBus.Execute(ctx, &CreateOrderCommand{
CustomerID: sagaCtx.CustomerID,
Items: sagaCtx.Items,
})
if err != nil {
return err
}
sagaCtx.OrderID = result.OrderID
return nil
},
Compensate: func(ctx context.Context, sagaCtx *OrderSagaContext) error {
_, err := saga.commandBus.Execute(ctx, &CancelOrderCommand{
OrderID: sagaCtx.OrderID,
})
return err
},
},
{
Name: "reserveInventory",
Execute: func(ctx context.Context, sagaCtx *OrderSagaContext) error {
result, err := saga.commandBus.Execute(ctx, &ReserveInventoryCommand{
OrderID: sagaCtx.OrderID,
Items: sagaCtx.Items,
})
if err != nil {
return err
}
sagaCtx.InventoryReservation = result.ReservationID
return nil
},
Compensate: func(ctx context.Context, sagaCtx *OrderSagaContext) error {
_, err := saga.commandBus.Execute(ctx, &ReleaseInventoryCommand{
ReservationID: sagaCtx.InventoryReservation,
})
return err
},
},
}
return saga
}
func (s *OrderSaga) Run(ctx context.Context, sagaCtx *OrderSagaContext) error {
completedSteps := []int{}
for i, step := range s.steps {
if err := step.Execute(ctx, sagaCtx); err != nil {
// 執行補償
for j := len(completedSteps) - 1; j >= 0; j-- {
stepIdx := completedSteps[j]
if compErr := s.steps[stepIdx].Compensate(ctx, sagaCtx); compErr != nil {
return fmt.Errorf("compensation failed at step %d: %w", stepIdx, compErr)
}
}
return fmt.Errorf("saga failed at step %s: %w", step.Name, err)
}
completedSteps = append(completedSteps, i)
}
return nil
}
適用於較鬆耦合的場景:
choreography:
events:
- event: "OrderCreated"
handlers:
- service: "inventory"
action: "ReserveInventory"
publishes: "InventoryReserved"
- service: "notification"
action: "SendOrderConfirmation"
- event: "InventoryReserved"
handlers:
- service: "payment"
action: "ProcessPayment"
publishes: "PaymentProcessed"
- event: "PaymentProcessed"
handlers:
- service: "shipping"
action: "ScheduleShipment"
- service: "analytics"
action: "UpdateDashboard"
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.