機能セッション

開始

セッション作成リクエストのフィールド、レスポンス、Idempotency-Key、一覧・共有範囲・タイトル、エラー

セッションは、プロファイル 1 つで動く会話である。プロファイルがリポジトリを使うなら、リポジトリも 1 つ選ぶ。セッションを作ると最初のメッセージがターン 1 として受け付けられ、scheduler がそのセッションの worker コンテナを起動する。scope は sessions:write である。SDK の例は SDK のインストールで作った werft クライアントを、HTTP API の例はサーバーのアドレス API とキー KEY のシェル変数を使う。

Web コンソールでは、エージェント詳細の「このエージェントで新しいセッション」を押して最初のメッセージを送る。セッションは有効な release で始まり(GitHub 設定があるエージェントはリポジトリなしで、設定がない古いエージェントは release のリポジトリで)、同じ POST /v1/sessions をログイン中のユーザーの権限で呼ぶ。画面で事前に分かる状態(有効な release がない、一時停止中、権限がないなど)ではボタンがオフになり、理由が表示される。資格情報の連携漏れのように送信後に分かる拒否は、ダイアログに理由が表示される。

例の GATE-SPEC {…} メッセージは、開発用サーバーの fake モデルが読むスクリプトである。fake モデルはスクリプトどおりにツールを呼んで応答するので、出力はいつも同じになる。実際のモデルで起動したサーバーでは同じツール呼び出しと出力にはならないので、普段の指示を送る。

作成する

const created = await werft.sessions.create({
  profile_id: "claude-coding-local",
  repository_id: "sample-app",
  message: 'GATE-SPEC {"id":"start","steps":[],"final":"started"}',
});
console.log(created);
const sessionId = created.session_id;
{
  session_id: '…',
  turn_id: '1',
  receipt_id: '…',
  receipt_status: 'accepted',
  status: 'queued'
}
フィールド必須内容
profile_idagent_release_id がなければ必須プロファイルの id(プロファイルを選ぶ)
repository_idプロファイルによるカタログ上のリポジトリ id。送れば worker はこのリポジトリを clone する。プロファイルの repository_mode が none なら送らず、required のプロファイルは必ず送る。ただし GitHub 設定があるエージェントのセッションと、ワークスペースの上限が決めるプロファイルは、required でも省いてリポジトリなしで始められる。エージェントのリリースで作ったセッションで省くと、GitHub 設定があるエージェントはリポジトリなしで始まり、必要なリポジトリだけを付け、設定がない古いエージェントはリリースのリポジトリで始まる(リポジトリなしのセッション)
messageはい最初のターンの入力。UTF-8 で 32,768 バイトまで
modelプロファイルが許可したモデルのうち 1 つ。省略するとデフォルトのモデル
titleセッションの名前。省略すると最初の message から作る(タイトル)
agent_release_idこのセッションをエージェントのリリースに紐づける(セッションに紐付ける)。このとき profile_id は、省略するとリリースの値になる。repository_id を省略すると、GitHub 設定があるエージェントはリポジトリなしで始まり、設定がない古いエージェントはリリースのリポジトリで始まる

レスポンスは 201 である。受け付けただけで、結果はターンの取得で確認する(状態とライフサイクル)。

Idempotency-Key

セッション API のすべての POST には Idempotency-Key ヘッダーが必要である。レスポンスを受け取れなかったときは、同じキーと同じ本文でもう一度送る。サーバーはコマンドを 1 回だけ実行する。セッション作成とメッセージは最初のレスポンスをそのまま返し、interrupt などの制御はその receipt の現在の状態を返す。同じキーで本文が違うと 409 IDEMPOTENCY_CONFLICT になる。

SDK は呼び出しごとにキーを 1 つ作り、その呼び出しの再試行に同じキーを送る。プロセスを再起動したあとも同じコマンドを続けたいときは、キーを自分で作って idempotencyKey として渡す。

const params = {
  profile_id: "claude-coding-local",
  repository_id: "sample-app",
  message: 'GATE-SPEC {"id":"retry","steps":[],"final":"once"}',
};
const idempotencyKey = crypto.randomUUID();
const first = await werft.sessions.create(params, { idempotencyKey });
const second = await werft.sessions.create(params, { idempotencyKey });
console.log(first.session_id === second.session_id ? "same session" : "two sessions");
same session

新しい意図には新しいキーを使う。timeout の後に新しいキーで送ると、セッションが 2 つできることがある。

一覧を見る

const page = await werft.sessions.list({ limit: 5 });
for (const session of page.items) {
  console.log(session.id, session.title, session.status);
}

// for await walks every page.
for await (const session of werft.sessions.list({ status: "idle" })) {
  console.log(session.id);
}

await すると最初のページ(items、next_cursor、nextPage())を、for await するとすべてのページの項目を返す。

limit、status で絞り込む。キーが見られるセッションだけが返る。

relation は見られるセッションの中をさらに絞るだけで、広げることはない。started は自分が始めたセッション(キーならそのキーかキーを作った人が始めたもの)、shared は他の人が始めて自分を名指しするか自分の属するチームを指定して共有したセッション、administered は自分が(直接またはチームで)管理するエージェントのセッションだ。ワークスペース全体に見える他人のセッションは all(既定値)にだけ出る。q はタイトルにその文字列を含む(大文字小文字を区別しない)か、id がその文字列で始まるセッションを探す。agent_id はそのエージェントのリリース(どのリリースでも)で作られたセッションだけを残す。

各項目の agent(id、name、release_version_no、release_label)はセッションが結び付いたエージェントと release で、エージェントの release なしに作ったセッションでは null だ。release_version_no はその release が使うカードのバージョン、release_label は release の名前(作成時刻。例: 2026-10-30_14:30:28)だ。created_by(user_id、display_name)はセッションを始めた人で、キーで作ったセッションではキーを作った人になる。引き継ぎの後も変わらない。引き継いだ人はそのセッションを読めるが、started には始めた人にだけ出る。channel はセッションを作ったリクエストの入口(web、api、slack)だ。始めた人や入口が記録からわからない古いセッションは null になる。slack_permalink は Slack で始めたセッションの最初のメッセージへのリンクだ(元の Slack メッセージへのリンク)。ほかの経路で作ったセッションとリンクを残す前のセッションは null になる。

ステータスごとの件数は SDK にないので、HTTP API で読む。GET /v1/sessions/counts は、同じ relation・q で一覧を最後まで読んだときのステータスごとの件数と total を返す。見られないセッションは数えない。

curl -sS "$API/v1/sessions/counts?relation=started" -H "Authorization: Bearer $KEY" | jq -c

リファレンス: GET /v1/sessions/counts

一覧と詳細の repository は、セッションを作ったときのリポジトリだ。id はカタログのキー(repository_id と同じ)、full_name は画面に出す名前で、リポジトリ URL から host・認証情報・query・.git を除いたパス(owner/name、グループがあれば group/sub/name)、そう読めない URL ならカタログのキーになる。branch は worker が clone したブランチで、push が書き込む agent/<session-id> ではない。三つとも作成時に記録した値なので、運用者があとでカタログを変えたりリポジトリを外したりしても変わらない。リポジトリなしで作ったセッションは repository が null、カタログができる前に作ったセッションは id が null だ。リポジトリ URL そのものはどのレスポンスにも出ない。

const session = await werft.sessions.get(sessionId);
console.log(session.repository);
{ id: 'sample-app', full_name: 'agent/sample-app', branch: 'main' }

共有範囲

セッションを誰が見られるかは、共有範囲で読む。セッションを見られる人なら誰でも読める。scope は sessions:read である。

const share = await werft.sessions.share(sessionId);
console.log(share.scope, share.revision);
workspace 0

scope は private(作った人だけ)、people(user_ids の人)、team(team_ids のチームの現在のメンバー)、workspace(すべてのメンバー)のいずれかである。一度も変えていないセッションは workspace、revision 0 になる。エージェントのセッションは、ワークスペースの設定によって workspace でもエージェントの内側だけが見られ、そのとき audience_limited が true になる(エージェントの外の member ができること)。コンソールのセッション画面は 運用 欄の 共有範囲 にこの値を読み取り専用で示し、audience_limited なら ワークスペース(担当チーム外の人は見られません。ワークスペース設定) と書く。範囲を変える POST /v1/sessions/{id}/share は Web ログインでしか呼べないため、SDK のメソッドはない(setSessionShare)。

タイトル

title を送ると、連続する空白・改行を空白 1 つにまとめ、前後を切って保存する。その結果が空か 80 文字(絵文字や結合文字は 1 つで 1 文字)を超えると 400 になる。省略すると最初の message から同じ規則で作り、80 文字を超えると先頭 79 文字に … を付ける。モデルは呼ばないので、同じメッセージからは必ず同じタイトルになる。空白だけのメッセージと、この機能より前に作ったセッションは title が null になる。

名前の変更は SDK にないので、HTTP API で呼ぶ。

curl -sS -X PATCH "$API/v1/sessions/$SID" -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' -d '{"title":"결제 테스트 고치기"}' | jq '{id, title}'

名前を変えられるのは、作った人(キーが作ったセッションならそのキー)、エージェントの管理者、owner・admin だ。共有で見ているだけの人は 403、見られない人は 404 になる。Idempotency-Key は要らない。変わると SSE に title を載せた status イベントが届く。リファレンス: PATCH /v1/sessions/{id}

エラー

サーバーのエラーは SDK では APIError として届く。isApiError(error, code) で判別し、retryable が true のエラーは SDK が自分で再送する(リトライと Idempotency-Key)。

import { isApiError } from "@werft-ai/sdk";

try {
  await werft.sessions.create({
    profile_id: "claude-coding-local",
    repository_id: "sample-app",
    model: "claude-opus-9",
    message: "hello",
  });
} catch (error) {
  if (isApiError(error, "UNSUPPORTED_CAPABILITY")) {
    console.log(error.status, error.code, error.retryable, error.message);
  } else throw error;
}
422 UNSUPPORTED_CAPABILITY false Model claude-opus-9 is not on the allowed list of profile claude-coding-local; name a model the profile offers
レスポンス意味
400本文が schema と異なる、または Idempotency-Key がない
401キーがない、誤っている、または失効している
403キーに sessions:write がない。または呼び出した人(個人キーなら作った人)がエージェントの外側で、ワークスペースの agent_outsider_access が use でない(details.reason_code が role)
409 IDEMPOTENCY_CONFLICT同じキーで本文が異なる
409 PROFILE_BLOCKEDオーナーが運用担当者のプロファイルをブロックした。ワークスペースプロファイルなら、そのベースがブロックされている(details.reason_code が base_blocked)。送り直しても同じになる
413メッセージが 32,768 バイトを超えている。ストレージ容量の上限なら STORAGE_LIMIT_EXCEEDED
403 GITHUB_REFUSED指定した repository_id がエージェントの GitHub 設定の一覧またはワークスペースの上限の外にある(details.reason_code が repository_not_allowed)
422 UNSUPPORTED_CAPABILITYプロファイルまたはリポジトリが存在しない、その組み合わせが許可されていない、またはプロファイルがそのモデルを許可していない
429 RATE_LIMITED少し待ってからもう一度送る

実行 slot が足りなくても拒否はしない。セッションは queued で待機する。

SDK のエラークラス(APIError、ConnectionError、TimeoutError など)とリトライ回数・timeout はすべての呼び出しで共通なので、クライアントの設定とエラーにまとめた。

次のステップ

  • 状態とライフサイクル: セッションとターンの状態
  • 送信: 同じセッションにメッセージをさらに送る
  • SDK サンプル: キーとカタログの確認、セッション一覧、イベントの受信再開、リトライ、質問・権限リクエストへの回答、制御と復旧、push の承認と PR をユースケース別の SDK サンプルで

このページ