状態とライフサイクル
セッションの status と admission_state、ターンの状態、interrupt、attention
セッション詳細(GET /v1/sessions/{id})には状態が 2 つある。status は現在の実行状況、admission_state はセッションが新しい入力を受け付けるかどうかである。ターンは別に状態を持ち、終わったかどうかはターンの状態だけで判断する。開始で作ったセッション(SDK は created・sessionId、HTTP API は SID)で確認する。
import { waitForTurn } from "@werft-ai/sdk";
await waitForTurn(werft, sessionId, created.turn_id);
const { status, admission_state, current_turn_id, revision, attention } =
await werft.sessions.get(sessionId);
console.log({ status, admission_state, current_turn_id, revision, attention });{
status: 'idle',
admission_state: 'active',
current_turn_id: null,
revision: 0,
attention: null
}revision は、pause・resume・close などの制御でセッションが変わったときに上がる。ターンが終わっても上がらない。制御リクエストには現在の値を expected_revision として送る。
status
| 値 | 意味 |
|---|---|
queued | 入力があり、worker を待っている |
running | worker がターンを実行している |
needs_input | 実行中のターンが、権限リクエストまたは質問に対する人の回答を待っている |
idle | 実行するターンがない。次のメッセージを受け付ける |
stopped | 実行が止まっている(interrupt、terminate、pause、close の後) |
failed | 実行が失敗した。理由は attention にある |
admission_state
| 値 | 意味 |
|---|---|
active | メッセージと制御を受け付ける |
pausing → paused | pause 中、および checkpoint を残して worker が停止した状態。メッセージは 409 SESSION_PAUSED |
resuming | resume 中。新しい worker が checkpoint から復元する |
stopping → stopped | terminate 中、および停止した状態 |
recovery_required | 結果が不明なターンが残っており、人が復旧判断をする必要がある(クローズ) |
closing → closed | close 中、および閉じた状態。元に戻せない |
ターンの状態
for await (const turn of werft.sessions.turns.list(sessionId)) {
console.log(turn.turn_id, turn.status);
}queued・running・needs_input は進行中、completed・failed・interrupted・cancelled・outcome_unknown は終了した状態である。completed はエンジンがターンを正常に終了したという意味であり、依頼した作業をすべて終えたという意味ではない。作業が終わったかどうかは、イベントの assistant・tool_result を読んで判断する。イベントの result も完了の判定ではない。
interrupt
実行中のターン 1 つだけを止める。worker は残り、次のメッセージを受け付ける。下のスクリプトは応答を 5 分遅らせるので、その間ターンは running のままになる。この遅延は開発用サーバーの fake モデルでだけ効く。実際のモデルでは、時間のかかる作業をさせたターンで試す。
const sent = await werft.sessions.messages.send(sessionId, {
message:
'GATE-SPEC {"id":"slow","steps":[],"final":"too late","finalDelayMs":300000}',
});
while ((await werft.sessions.turns.get(sessionId, sent.turn_id)).status !== "running") {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
const accepted = await werft.sessions.interrupt(sessionId, {
target_turn_id: sent.turn_id,
});
console.log(accepted.receipt_status);
const turn = await waitForTurn(werft, sessionId, sent.turn_id);
console.log(`turn ${turn.turn_id}: ${turn.status}`);accepted
turn 2: interruptedまだ開始していないターンは 409 TURN_NOT_STARTED、すでに終了したターンは何もしない成功(no_op: true)になる。scope は sessions:control である。
attention
セッションが人の対応を待っているとき、attention に code と理由が付く。通常は null である。
| code | 意味 |
|---|---|
PAUSE_BLOCKED, CLOSE_BLOCKED | pause・close が 60 秒を超えても drain できなかった。理由は long_turn、pending_request など |
BUDGET_EXCEEDED | セッションのコストまたは token の上限を超えた |
TRANSCRIPT_LIMIT | 会話履歴が上限に近づいた、または超えた。新しいセッションに移るタイミングである |
STARTUP_FAILED, RESTORE_FAILED | worker が起動できない、または checkpoint を復元できない |
CATALOG_MISMATCH | セッションを作った後に、運用者がプロファイルまたはリポジトリの設定を変更した |
PROFILE_BLOCKED | オーナーがセッションのプロファイル(reason_code: blocked)またはそのベース(base_blocked)をブロックした。profile_id がそのプロファイル。解除されるまで、新しいメッセージは 409 PROFILE_BLOCKED で拒否される |
CONTEXT_GAP | 復旧判断により、以前の会話の一部が欠けている |
使用量
const usage = await werft.sessions.usage(sessionId);
console.log(usage.cost.amount_usd, usage.cost.complete, usage.budget_exceeded);0.000063 true falseコストは請求額ではなく、価格表で計算した推定値である。complete が false なら、合計はまだ確定していない。開いているターンがある(open_turn_count)か、終了したターンのうちコストが報告されていないものがある(unreported_turn_count)。後者の場合は、すべてのターンが終了しても false のままである。
期間中に自分が始めたセッションをすべて合計するには GET /v1/usage/summary を使う。この合計と GET /v1/limits?scope=me は SDK のメソッドがないので、HTTP API で読む。SDK の limits.get() はインストール全体の上限(GET /v1/limits)を読むが、これはログインした workspace owner だけが読めるため、API キーで呼ぶと 403 になる。リファレンス: GET /v1/usage/summary, GET /v1/limits since は日付(その日の 00:00 UTC)か offset 付きの時刻で、until を省くと現在までになる。
curl -sS "$API/v1/usage/summary?scope=me&since=2026-10-01" -H "Authorization: Bearer $KEY" \
| jq '{session_count, cost_usd, cost_complete, token_count, run_seconds}'対象は期間内に作成したセッションで、cost_usd はそれらの /usage の cost.amount_usd の合計である。そのうち一つでも complete: false なら cost_complete も false になる。個人キーは作成した人のセッションを合計する。scope=installation(すべての owner のセッション)と GET /v1/limits はログインした workspace owner だけが読め、ほかのメンバーとすべての API キーは 403 になる。scope=agent&agent_id=… は、誰が始めたかにかかわらず、そのエージェントのいずれかの release から始まったセッションを合計する。workspace の owner・admin とそのエージェントの管理者が読め、ほかのメンバーとすべての API キーは 403、別の workspace のエージェントや存在しないエージェントは誰に対しても 404 になる。group_by とは併用できない。
GET /v1/limits の usage.outputs は、セッションの成果物(out/ 以下のファイル)が object store で占めるバイト数である(used_bytes)。後の checkpoint が前の checkpoint の成果物をそのまま引き継いだ場合は一度だけ数え、大きすぎてアップロードしなかったファイルは数えず、checkpoint の整理がオブジェクトを削除するとその分だけ減る。updated_at は値が最後に変わった時刻で、変わったことがなければ null である。この値は usage.storage(入力メッセージ)とは別で、storage_limit_bytes の適用には含まれない。成果物を記録する前に作られた checkpoint が残っている間は、その成果物は含まれず complete が false になる。
エージェント別・ユーザー別に分けて見るには group_by=agent または group_by=user を加える。応答に groups[{id, name, session_count, run_seconds, cost_usd, cost_complete}] が付き、グループの session_count・cost_usd・run_seconds を足すと上の合計と一致する。エージェントのないセッションと、人が作成していないキーが始めたセッションは id が null のグループになる。name はエージェント名かユーザーの表示名で、メールアドレスは載せない。scope=me では呼び出し元以外のユーザーの name は null になる。プロファイル別に見るには group_by=profile を使う。セッションの現在のプロファイル(sessions.profile_id)でまとめるため、途中でプロファイルを変えたセッションは費用全体が今のプロファイルに計上される。プロファイルには表示名がないので id と name はどちらもプロファイル id で、プロファイルのないセッションは id が null のグループになる。
GET /v1/limits?scope=me はログインしたメンバーなら誰でも読める。セッション単位の上限 4 つ(queued_input_limit_per_session、max_turn_seconds、session_cost_limit_usd、session_token_limit)だけを返し、usage はない。API キーは 403 になる。
curl -sS "$API/v1/usage/summary?scope=me&since=2026-10-01&group_by=agent" -H "Authorization: Bearer $KEY" \
| jq '.groups[] | {name, session_count, cost_usd}'