symbi 1.14.2

AI-native agent framework for building autonomous, policy-aware agents that can safely collaborate with humans, other agents, and large language models
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
# スケジューリングガイド

## 概要

Symbiontのスケジューリングシステムは、AIエージェント向けの本番レベルのcronベースタスク実行機能を提供します。以下の機能をサポートしています:

- **cronスケジュール**: 定期タスク用の標準的なcron構文
- **ワンショットジョブ**: 指定時刻に一度だけ実行
- **ハートビートパターン**: 監視エージェント向けの継続的な評価-アクション-スリープサイクル
- **セッション分離**: エフェメラル、共有、または完全分離のエージェントコンテキスト
- **配信ルーティング**: 複数の出力チャネル(Stdout、LogFile、Webhook、Slack、Email、Custom)
- **ポリシー適用**: 実行前のセキュリティおよびコンプライアンスチェック
- **本番環境の堅牢化**: ジッター、同時実行制限、デッドレターキュー、AgentPin検証

## アーキテクチャ

スケジューリングシステムは3つのコアコンポーネントで構成されています:

```
┌─────────────────────┐
│   CronScheduler     │  バックグラウンドティックループ(1秒間隔)
│   (Tick Loop)       │  ジョブ選択と実行オーケストレーション
└──────────┬──────────┘
┌─────────────────────┐
│   SqliteJobStore    │  永続的なジョブストレージ
│   (Job Storage)     │  トランザクションサポート、状態管理
└──────────┬──────────┘
┌─────────────────────┐
│DefaultAgentScheduler│  エージェント実行ランタイム
│ (Execution Engine)  │  AgentContextライフサイクル管理
└─────────────────────┘
```

### CronScheduler

`CronScheduler`は主要なエントリポイントです。以下を管理します:

- 1秒間隔で動作するバックグラウンドティックループ
- 次回実行時刻に基づくジョブ選択
- 同時実行制御とジッター挿入
- メトリクス収集とヘルスモニタリング
- 実行中ジョブの追跡を伴うグレースフルシャットダウン

### SqliteJobStore

`SqliteJobStore`は以下の機能を備えた永続的なジョブストレージを提供します:

- ジョブ状態更新のためのACIDトランザクション
- ジョブライフサイクル追跡(Active、Paused、Completed、Failed、DeadLetter)
- 監査証跡付きの実行履歴
- ステータス、エージェントIDなどによるフィルタリングクエリ機能

### DefaultAgentScheduler

`DefaultAgentScheduler`はスケジュールされたエージェントを実行します:

- 分離または共有の`AgentContext`インスタンスを作成
- セッションライフサイクル(作成、実行、破棄)を管理
- 設定されたチャネルへの配信をルーティング
- 実行前にポリシーゲートを適用

## DSL構文

### スケジュールブロックの構造

スケジュールブロックはSymbiont DSLファイルで定義されます:

```symbiont
schedule {
  name: "daily-report"
  agent: "reporter-agent"
  cron: "0 0 9 * * *"

  session_mode: "ephemeral_with_summary"
  delivery: ["stdout", "log_file"]

  policy {
    require_approval: false
    max_runtime: "5m"
  }
}
```

### Cron構文

6つのフィールドを持つ拡張cron構文(秒が先頭、オプションの7番目のフィールドは年):

```
┌─────────────── 秒 (0-59)
│ ┌───────────── 分 (0-59)
│ │ ┌─────────── 時 (0-23)
│ │ │ ┌───────── 日 (1-31)
│ │ │ │ ┌─────── 月 (1-12)
│ │ │ │ │ ┌───── 曜日 (0-6, 日曜日 = 0)
│ │ │ │ │ │
* * * * * *
```

**例:**

```symbiont
# 毎日午前9時
cron: "0 0 9 * * *"

# 毎週月曜日の午後6時
cron: "0 0 18 * * 1"

# 15分ごと
cron: "0 */15 * * * *"

# 毎月1日の深夜0時
cron: "0 0 0 1 * *"
```

### ワンショットジョブ(At構文)

指定時刻に一度だけ実行するジョブの場合:

```symbiont
schedule {
  name: "deployment-check"
  agent: "health-checker"
  at: "2026-02-15T14:30:00Z"  # ISO 8601タイムスタンプ

  delivery: ["webhook"]
  webhook_url: "https://ops.example.com/hooks/deployment"
}
```

### ハートビートパターン

評価 → アクション → スリープの継続的な監視エージェント向け:

```symbiont
schedule {
  name: "system-monitor"
  agent: "heartbeat-agent"
  cron: "0 */5 * * * *"  # 5分ごとに起動

  heartbeat: {
    enabled: true
    context_mode: "ephemeral_with_summary"
    max_iterations: 100  # 安全制限
  }
}
```

ハートビートエージェントは以下のサイクルに従います:

1. **評価**: システム状態を評価(例:メトリクス、ログの確認)
2. **アクション**: 必要に応じて是正措置を実行(例:サービスの再起動、運用チームへのアラート)
3. **スリープ**: 次のスケジュールされたティックまで待機

## CLIコマンド

`symbi cron`コマンドにより、完全なライフサイクル管理が可能です:

### ジョブ一覧

```bash
# すべてのジョブを一覧表示
symbi cron list

# ステータスでフィルタリング
symbi cron list --status active
symbi cron list --status paused

# エージェントでフィルタリング
symbi cron list --agent "reporter-agent"

# JSON出力
symbi cron list --format json
```

### ジョブ追加

```bash
# DSLファイルから追加
symbi cron add --file agent.symbi --schedule "daily-report"

# インライン定義(JSON)
symbi cron add --json '{
  "name": "quick-task",
  "agent_id": "agent-123",
  "cron_expr": "0 0 * * * *"
}'
```

### ジョブ削除

```bash
# ジョブIDで削除
symbi cron remove <job-id>

# 名前で削除
symbi cron remove --name "daily-report"

# 強制削除(確認をスキップ)
symbi cron remove <job-id> --force
```

### 一時停止/再開

```bash
# ジョブを一時停止(スケジューリングを停止、状態は保持)
symbi cron pause <job-id>

# 一時停止中のジョブを再開
symbi cron resume <job-id>
```

### ステータス

```bash
# 次回実行時刻を含むジョブ詳細
symbi cron status <job-id>

# 直近10件の実行記録を含む
symbi cron status <job-id> --history 10

# ウォッチモード(5秒ごとに自動更新)
symbi cron status <job-id> --watch
```

### 即時実行

```bash
# 即時実行をトリガー(スケジュールをバイパス)
symbi cron run <job-id>

# カスタム入力付きで実行
symbi cron run <job-id> --input "Check production database"
```

### 履歴

```bash
# ジョブの実行履歴を表示
symbi cron history <job-id>

# 直近20件の実行
symbi cron history <job-id> --limit 20

# ステータスでフィルタリング
symbi cron history <job-id> --status failed

# CSVにエクスポート
symbi cron history <job-id> --format csv > runs.csv
```

## ハートビートパターン

### HeartbeatContextMode

ハートビートのイテレーション間でコンテキストがどのように保持されるかを制御します:

```rust
pub enum HeartbeatContextMode {
    /// Fresh context each iteration, append summary to run history
    EphemeralWithSummary,

    /// Shared context across all iterations (memory accumulates)
    SharedPersistent,

    /// Fresh context each iteration, no summary (stateless)
    FullyEphemeral,
}
```

**EphemeralWithSummary(デフォルト)**:
- イテレーションごとに新しい`AgentContext`を作成
- 前回のイテレーションのサマリーをコンテキストに追加
- 無制限のメモリ増加を防止
- 関連するアクション間の継続性を維持

**SharedPersistent**:
- すべてのイテレーションで単一の`AgentContext`を再利用
- 完全な会話履歴を保持
- メモリ使用量が高い
- 深いコンテキストを必要とするエージェントに最適(例:デバッグセッション)

**FullyEphemeral**:
- イテレーションごとに新しい`AgentContext`、引き継ぎなし
- 最小のメモリフットプリント
- 独立したチェックに最適(例:APIヘルスプローブ)

### ハートビートエージェントの例

```symbiont
agent heartbeat_monitor {
  model: "claude-sonnet-4.5"
  system_prompt: """
  You are a system monitoring agent. On each heartbeat:
  1. Check system metrics (CPU, memory, disk)
  2. Review recent error logs
  3. If issues detected, take action:
     - Restart services if safe
     - Alert ops team via Slack
     - Log incident details
  4. Summarize findings
  5. Return 'sleep' when done
  """
}

schedule {
  name: "heartbeat-monitor"
  agent: "heartbeat_monitor"
  cron: "0 */10 * * * *"  # 10分ごと

  heartbeat: {
    enabled: true
    context_mode: "ephemeral_with_summary"
    max_iterations: 50
  }

  delivery: ["log_file", "slack"]
  slack_channel: "#ops-alerts"
}
```

## セッション分離

### セッションモード

```rust
pub enum HeartbeatContextMode {
    /// Ephemeral context with summary carryover (default)
    EphemeralWithSummary,

    /// Shared persistent context across all runs
    SharedPersistent,

    /// Fully ephemeral, no state carryover
    FullyEphemeral,
}
```

**設定:**

```symbiont
schedule {
  name: "data-pipeline"
  agent: "etl-agent"
  cron: "0 0 2 * * *"

  # 実行ごとに新しいコンテキスト、前回実行のサマリーを含む
  session_mode: "ephemeral_with_summary"
}
```

### セッションライフサイクル

スケジュールされた各実行について:

1. **実行前**: 同時実行制限の確認、ジッターの適用
2. **セッション作成**: `session_mode`に基づいて`AgentContext`を作成
3. **ポリシーゲート**: ポリシー条件を評価
4. **実行**: 入力とコンテキストでエージェントを実行
5. **配信**: 設定されたチャネルに出力をルーティング
6. **セッションクリーンアップ**: モードに基づいてコンテキストを破棄または保持
7. **実行後**: 実行記録の更新、メトリクスの収集

## 配信ルーティング

### サポートされるチャネル

```rust
pub enum DeliveryChannel {
    Stdout,           // Print to console
    LogFile,          // Append to job-specific log file
    Webhook,          // HTTP POST to URL
    Slack,            // Slack webhook or API
    Email,            // SMTP email
    Custom(String),   // User-defined channel
}
```

### 設定例

**単一チャネル:**

```symbiont
schedule {
  name: "backup"
  agent: "backup-agent"
  cron: "0 0 3 * * *"
  delivery: ["log_file"]
}
```

**複数チャネル:**

```symbiont
schedule {
  name: "security-scan"
  agent: "scanner"
  cron: "0 0 1 * * *"

  delivery: ["log_file", "slack", "email"]

  slack_channel: "#security"
  email_recipients: ["ops@example.com", "security@example.com"]
}
```

**Webhook配信:**

```symbiont
schedule {
  name: "metrics-report"
  agent: "metrics-agent"
  cron: "0 */30 * * * *"

  delivery: ["webhook"]
  webhook_url: "https://metrics.example.com/ingest"
  webhook_headers: {
    "Authorization": "Bearer ${METRICS_API_KEY}"
    "Content-Type": "application/json"
  }
}
```

### DeliveryRouterトレイト

カスタム配信チャネルは以下を実装します:

```rust
#[async_trait]
pub trait DeliveryRouter: Send + Sync {
    async fn route(
        &self,
        channel: &DeliveryChannel,
        job: &CronJobDefinition,
        run: &JobRunRecord,
        output: &str,
    ) -> Result<(), SchedulerError>;
}
```

## ポリシー適用

### PolicyGate

`PolicyGate`は実行前にスケジュール固有のポリシーを評価します:

```rust
pub struct PolicyGate {
    policy_engine: Arc<RealPolicyParser>,
}

impl PolicyGate {
    pub fn evaluate(
        &self,
        job: &CronJobDefinition,
        context: &AgentContext,
    ) -> Result<SchedulePolicyDecision, SchedulerError>;
}
```

### ポリシー条件

```symbiont
schedule {
  name: "production-deploy"
  agent: "deploy-agent"
  cron: "0 0 0 * * 0"  # 日曜深夜

  policy {
    # 実行前に人間の承認を要求
    require_approval: true

    # 強制終了までの最大実行時間
    max_runtime: "30m"

    # 特定のケイパビリティを要求
    require_capabilities: ["deployment", "production_write"]

    # 時間枠の適用(UTC)
    allowed_hours: {
      start: "00:00"
      end: "04:00"
    }

    # 環境制限
    allowed_environments: ["staging", "production"]

    # AgentPin検証を要求
    require_agent_pin: true
  }
}
```

### SchedulePolicyDecision

```rust
pub enum SchedulePolicyDecision {
    Allow,
    Deny { reason: String },
    RequiresApproval { approver: String, reason: String, policy_id: String },
}
```

## 本番環境の堅牢化

### ジッター

複数のジョブが同じスケジュールを共有する場合のサンダリングハード問題を防止します:

```rust
pub struct CronSchedulerConfig {
    pub max_jitter_seconds: u64,  // Random delay 0-N seconds
    // ...
}
```

**例:**

```toml
[scheduler]
max_jitter_seconds = 30  # 30秒のウィンドウにジョブ開始を分散
```

### ジョブごとの同時実行制限

リソース枯渇を防ぐためにジョブごとの同時実行数を制限します:

```symbiont
schedule {
  name: "data-sync"
  agent: "sync-agent"
  cron: "0 */5 * * * *"

  max_concurrent: 2  # 最大2つの同時実行を許可
}
```

ジョブが最大同時実行数で既に実行中の場合、スケジューラーはそのティックをスキップします。

### デッドレターキュー

`max_retries`を超えたジョブは手動レビューのために`DeadLetter`ステータスに移行します:

```symbiont
schedule {
  name: "flaky-job"
  agent: "unreliable-agent"
  cron: "0 0 * * * *"

  max_retries: 3  # 3回失敗後、デッドレターに移動
}
```

**復旧:**

```bash
# デッドレター化されたジョブを一覧表示
symbi cron list --status dead_letter

# 失敗理由を確認
symbi cron history <job-id> --status failed

# 修正後にジョブをアクティブにリセット
symbi cron reset <job-id>
```

### AgentPin検証

実行前にエージェントのIDを暗号的に検証します:

```symbiont
schedule {
  name: "secure-task"
  agent: "trusted-agent"
  cron: "0 0 * * * *"

  agent_pin_jwt: "${AGENT_PIN_JWT}"  # agentpin-cliからのES256 JWT

  policy {
    require_agent_pin: true
  }
}
```

スケジューラーは以下を検証します:
1. ES256(ECDSA P-256)を使用したJWT署名
2. エージェントIDが`iss`クレームと一致
3. ドメインアンカーが期待されるオリジンと一致
4. 有効期限(`exp`)が有効

検証失敗時は`SecurityEventType::AgentPinVerificationFailed`監査イベントが発行されます。

## HTTP APIエンドポイント

### スケジュール管理

**POST /api/v1/schedule**
新しいスケジュールジョブを作成します。

```bash
curl -X POST http://localhost:8080/api/v1/schedule \
  -H "Content-Type: application/json" \
  -d '{
    "name": "hourly-report",
    "agent_id": "reporter",
    "cron_expr": "0 0 * * * *",
    "session_mode": "ephemeral_with_summary",
    "delivery": ["stdout"]
  }'
```

**GET /api/v1/schedule**
すべてのジョブを一覧表示(ステータス、エージェントIDでフィルタリング可能)。

```bash
curl "http://localhost:8080/api/v1/schedule?status=active&agent_id=reporter"
```

**GET /api/v1/schedule/{job_id}**
ジョブの詳細を取得します。

```bash
curl http://localhost:8080/api/v1/schedule/job-123
```

**PUT /api/v1/schedule/{job_id}**
ジョブを更新(cron式、配信先など)。

```bash
curl -X PUT http://localhost:8080/api/v1/schedule/job-123 \
  -H "Content-Type: application/json" \
  -d '{"cron_expr": "0 0 */2 * * *"}'
```

**DELETE /api/v1/schedule/{job_id}**
ジョブを削除します。

```bash
curl -X DELETE http://localhost:8080/api/v1/schedule/job-123
```

**POST /api/v1/schedule/{job_id}/pause**
ジョブを一時停止します。

```bash
curl -X POST http://localhost:8080/api/v1/schedule/job-123/pause
```

**POST /api/v1/schedule/{job_id}/resume**
一時停止中のジョブを再開します。

```bash
curl -X POST http://localhost:8080/api/v1/schedule/job-123/resume
```

**POST /api/v1/schedule/{job_id}/run**
即時実行をトリガーします。

```bash
curl -X POST http://localhost:8080/api/v1/schedule/job-123/run \
  -H "Content-Type: application/json" \
  -d '{"input": "Run with custom input"}'
```

**GET /api/v1/schedule/{job_id}/history**
実行履歴を取得します。

```bash
curl "http://localhost:8080/api/v1/schedule/job-123/history?limit=20&status=failed"
```

**GET /api/v1/schedule/{job_id}/next_run**
次回スケジュール実行時刻を取得します。

```bash
curl http://localhost:8080/api/v1/schedule/job-123/next_run
```

### ヘルスモニタリング

**GET /api/v1/health/scheduler**
スケジューラーのヘルスとメトリクス。

```bash
curl http://localhost:8080/api/v1/health/scheduler
```

**レスポンス:**

```json
{
  "status": "healthy",
  "active_jobs": 15,
  "paused_jobs": 3,
  "in_flight_jobs": 2,
  "metrics": {
    "runs_total": 1234,
    "runs_succeeded": 1180,
    "runs_failed": 54,
    "avg_execution_time_ms": 850
  }
}
```

## SDKの例

### JavaScript SDK

```javascript
import { SymbiontClient } from '@symbiont/sdk-js';

const client = new SymbiontClient({
  baseUrl: 'http://localhost:8080',
  apiKey: process.env.SYMBI_API_KEY
});

// スケジュールジョブを作成
const job = await client.schedule.create({
  name: 'daily-backup',
  agentId: 'backup-agent',
  cronExpr: '0 0 2 * * *',
  sessionMode: 'ephemeral_with_summary',
  delivery: ['webhook'],
  webhookUrl: 'https://backup.example.com/notify'
});

console.log(`Created job: ${job.id}`);

// アクティブなジョブを一覧表示
const activeJobs = await client.schedule.list({ status: 'active' });
console.log(`Active jobs: ${activeJobs.length}`);

// ジョブのステータスを取得
const status = await client.schedule.getStatus(job.id);
console.log(`Next run: ${status.next_run}`);

// 即時実行をトリガー
await client.schedule.runNow(job.id, { input: 'Backup database' });

// ジョブを一時停止
await client.schedule.pause(job.id);

// 履歴を表示
const history = await client.schedule.getHistory(job.id, { limit: 10 });
history.forEach(run => {
  console.log(`Run ${run.id}: ${run.status} (${run.execution_time_ms}ms)`);
});

// ジョブを再開
await client.schedule.resume(job.id);

// ジョブを削除
await client.schedule.delete(job.id);
```

### Python SDK

```python
from symbiont import SymbiontClient

client = SymbiontClient(
    base_url='http://localhost:8080',
    api_key=os.environ['SYMBI_API_KEY']
)

# スケジュールジョブを作成
job = client.schedule.create(
    name='hourly-metrics',
    agent_id='metrics-agent',
    cron_expr='0 0 * * * *',
    session_mode='ephemeral_with_summary',
    delivery=['slack', 'log_file'],
    slack_channel='#metrics'
)

print(f"Created job: {job.id}")

# 特定のエージェントのジョブを一覧表示
jobs = client.schedule.list(agent_id='metrics-agent')
print(f"Found {len(jobs)} jobs for metrics-agent")

# ジョブの詳細を取得
details = client.schedule.get(job.id)
print(f"Cron: {details.cron_expr}")
print(f"Next run: {details.next_run}")

# cron式を更新
client.schedule.update(job.id, cron_expr='0 */30 * * * *')

# 即時実行をトリガー
run = client.schedule.run_now(job.id, input='Generate metrics report')
print(f"Run ID: {run.id}")

# メンテナンス中に一時停止
client.schedule.pause(job.id)
print("Job paused for maintenance")

# 最近の失敗を表示
history = client.schedule.get_history(
    job.id,
    status='failed',
    limit=5
)
for run in history:
    print(f"Failed run {run.id}: {run.error_message}")

# メンテナンス後に再開
client.schedule.resume(job.id)

# スケジューラーのヘルスを確認
health = client.schedule.health()
print(f"Scheduler status: {health.status}")
print(f"Active jobs: {health.active_jobs}")
print(f"In-flight jobs: {health.in_flight_jobs}")
```

## 設定

### CronSchedulerConfig

```rust
pub struct CronSchedulerConfig {
    /// Tick interval (default: 1 second)
    pub tick_interval: Duration,

    /// Global concurrency limit (default: 100)
    pub max_concurrent_cron_jobs: usize,

    /// Persistent job store path (default: None)
    pub job_store_path: Option<PathBuf>,

    /// Catch up missed runs on startup (default: true)
    pub enable_missed_run_catchup: bool,
}
```

### TOML設定

```toml
[scheduler]
tick_interval_seconds = 1
max_jitter_seconds = 30
max_concurrent_jobs = 20
enable_metrics = true
default_max_retries = 3
shutdown_timeout_seconds = 60

[scheduler.delivery]
# Webhook設定
webhook_timeout_seconds = 30
webhook_retry_attempts = 3

# Slack設定
slack_api_token = "${SLACK_API_TOKEN}"
slack_default_channel = "#ops"

# メール設定
smtp_host = "smtp.example.com"
smtp_port = 587
smtp_username = "${SMTP_USER}"
smtp_password = "${SMTP_PASS}"
email_from = "symbiont@example.com"
```

### 環境変数

```bash
# スケジューラー設定
SYMBI_SCHEDULER_MAX_JITTER=30
SYMBI_SCHEDULER_MAX_CONCURRENT=20

# 配信設定
SYMBI_SLACK_TOKEN=xoxb-...
SYMBI_WEBHOOK_AUTH_HEADER="Bearer secret-token"

# AgentPin検証
SYMBI_AGENTPIN_REQUIRED=true
SYMBI_AGENTPIN_DOMAIN=agent.example.com
```

## 可観測性

### メトリクス(Prometheus互換)

```
# 合計実行数
symbiont_cron_runs_total{job_name="daily-report",status="succeeded"} 450

# 失敗した実行
symbiont_cron_runs_total{job_name="daily-report",status="failed"} 5

# 実行時間ヒストグラム
symbiont_cron_execution_duration_seconds{job_name="daily-report"} 1.234

# 実行中ジョブゲージ
symbiont_cron_in_flight_jobs 3

# デッドレター化されたジョブ
symbiont_cron_dead_letter_total{job_name="flaky-job"} 2
```

### 監査イベント

すべてのスケジューラーアクションはセキュリティイベントを発行します:

```rust
pub enum SecurityEventType {
    CronJobCreated,
    CronJobUpdated,
    CronJobDeleted,
    CronJobPaused,
    CronJobResumed,
    CronJobExecuted,
    CronJobFailed,
    CronJobDeadLettered,
    AgentPinVerificationFailed,
}
```

インタラクティブシェルから監査ログをクエリ:

```text
/audit CronJobFailed
```

またはランタイム HTTP API 経由でプログラム的に — `/api/v1/audit` エンドポイントについては [API リファレンス](/api-reference) を参照してください。

## ベストプラクティス

1. **共有スケジュールにはジッターを使用**: 複数のジョブが同時に開始するのを防止
2. **同時実行制限を設定**: リソース枯渇から保護
3. **デッドレターキューを監視**: 失敗しているジョブを定期的にレビューして修正
4. **EphemeralWithSummaryを使用**: 長時間実行されるハートビートでの無制限のメモリ増加を防止
5. **AgentPin検証を有効化**: エージェントのIDを暗号的に検証
6. **配信ルーティングを設定**: ジョブタイプに応じた適切なチャネルを使用
7. **ポリシーゲートを設定**: 時間枠、承認、ケイパビリティチェックを適用
8. **監視にはハートビートパターンを使用**: 継続的な評価-アクション-スリープサイクル
9. **ステージングでスケジュールをテスト**: 本番環境前にcron式とジョブロジックを検証
10. **メトリクスをエクスポート**: 運用の可視化のためにPrometheus/Grafanaと統合

## トラブルシューティング

### ジョブが実行されない

1. ジョブのステータスを確認: `symbi cron status <job-id>`
2. cron式を検証: [crontab.guru]https://crontab.guru/を使用
3. スケジューラーのヘルスを確認: `curl http://localhost:8080/api/v1/health/scheduler`
4. ログを確認: `symbi logs --filter scheduler --level debug`

### ジョブが繰り返し失敗する

1. 履歴を表示: `symbi cron history <job-id> --status failed`
2. 実行記録のエラーメッセージを確認
3. エージェントの設定とケイパビリティを検証
4. スケジューラー外でエージェントをテスト: `symbi run <agent-id> --input "test"`
5. ポリシーゲートを確認: 時間枠とケイパビリティが一致しているか確認

### デッドレター化されたジョブ

1. デッドレタージョブを一覧表示: `symbi cron list --status dead_letter`
2. 失敗パターンを確認: `symbi cron history <job-id>`
3. 根本原因を修正(エージェントコード、権限、外部依存関係)
4. ジョブをリセット: `symbi cron reset <job-id>`

### 高メモリ使用量

1. セッションモードを確認: `ephemeral_with_summary`または`fully_ephemeral`に切り替え
2. ハートビートのイテレーションを削減: `max_iterations`を低く設定
3. コンテキストサイズを監視: エージェントの出力の冗長性を確認
4. コンテキストのアーカイブを有効化: 保持ポリシーを設定

## v0.9.0からの移行

v1.0.0リリースでは本番環境の堅牢化機能が追加されました。ジョブ定義を更新してください:

```diff
 schedule {
   name: "my-job"
   agent: "my-agent"
   cron: "0 0 * * * *"
+
+  # 同時実行制限を追加
+  max_concurrent: 2
+
+  # ID検証のためのAgentPinを追加
+  agent_pin_jwt: "${AGENT_PIN_JWT}"
+
+  policy {
+    require_agent_pin: true
+  }
 }
```

設定を更新:

```diff
 [scheduler]
 tick_interval_seconds = 1
+ max_jitter_seconds = 30
+ default_max_retries = 3
+ shutdown_timeout_seconds = 60
```

破壊的なAPI変更はありません。すべてのv0.9.0ジョブは引き続き動作します。