AWSのAIエージェント開発ツール「Kiro」のセッションログを外部から扱うために内部構造を調査した記録です。2026年8月時点の「v1形式」(ローカルJSONファイル保存)の構造と、同月の大型アップデートで切り替わった「v2形式」(サーバー側管理)への移行の様子、そして新しいエクスポート形式(ZIP+JSONL)をMarkdownに変換するロジックまでをまとめます。
v1形式:ローカルJSONの構造
セッションデータは以下の場所に保存されています(2026年8月時点)。
%APPDATA%\Kiro\User\globalStorage\kiro.kiroagent\
workspace-sessions\
{Base64Urlエンコードされたワークスペースパス}\
sessions.json # セッション一覧: [{sessionId, title, dateCreated, workspaceDirectory}, ...]
{sessionId}.json # 会話本体
ワークスペースパスのBase64Url変換規則は、標準のBase64エンコード後に+→-、/→_、パディングの=→_に置換したものです。
{sessionId}.jsonの構造はおおよそ次の形です。
{
"title": "...",
"sessionId": "...",
"workspaceDirectory": "c:\\path\\to\\workspace",
"history": [
{ "message": { "role": "user", "content": [{ "type": "text", "text": "..." }] } },
{ "message": { "role": "assistant", "content": "On it." }, "executionId": "..." }
// ...
]
}
ここでの重要な落とし穴は、assistant側のcontentが常に固定文字列"On it."であるという点です。実際の応答本文はここには含まれません。
アシスタント応答本文の在り処
実際の応答テキストは、history[i].executionIdをキーに、ワークスペースに紐づかない別のハッシュフォルダ配下の実行ログ領域から探す必要があります。
globalStorage\kiro.kiroagent\{任意のハッシュ}\414d1636299d2b9e4ce7e17fb11f63e9\{実行ログファイル}
この実行ログファイル自体の名前は、ワークスペースパスやexecutionIdから演算で求められる規則性がなく(MD5/SHA1等の単純ハッシュでもない)、executionIdに対する全文検索でしか特定できません。この領域は会話が増えるたびに単調増加し続け、調査時点で454ファイル・約1.68GBありました。
実行ログファイルの中身はactions[]配列を持ち、actionType: "say"のエントリのoutput.messageに実際のアシスタント応答テキストが入っています。また、startTime/endTime(Unixミリ秒)を持つ軽量インデックスファイル(全ワークスペース共通のファイル名、内容だけがワークスペースごとに異なる)を経由すれば、各executionIdのタイムスタンプも取得できます。
Hookでのリアルタイム取得を諦めた理由
agentStopイベントで発火するHookを使い、会話終了のたびにログを更新する仕組みを試みましたが、2つの理由で断念しました。
1. 実行ログ領域の規模と発火頻度が噛み合わない
agentStopは会話1回ごとに毎回発火します。一方で実行ログ領域は数百MB〜GB規模かつ増加し続けるため、毎ターン全文検索するのは非現実的です。
2. セッション永続化のタイムラグがHookの待機ロジックで吸収しきれない
会話完了(agentStop発火)から、実際に{sessionId}.jsonへアシスタント側のデータが書き込まれるまでに、時に1分以上のラグが発生することが実測で確認できました。以下の対策を順に試しましたが、いずれも決定打にはなりませんでした。
# 対策1: 固定時間の待機(最大24秒→効果薄い)
Start-Sleep -Milliseconds 700 # ×最大24回
# 対策2: FileSystemWatcherでの変更検知
# → Kiroの書き込み方式(一時ファイル→リネームの可能性)と噛み合わず、
# Changedイベントが発火しないケースがあった
# 対策3: 短間隔ポーリング(300ms)、最大55秒
# → それでも間に合わないケースが実測で発生
最終的に「直前1ターンは次の会話まで反映が遅延する」という制約を受け入れ、待機ロジックを撤去して即時書き込みに戻しました。
また、.kiro.hook定義ファイル(JSON)を直接編集すると、Kiro側の内部インデックスが変更を検知できず、UIのAgent HooksパネルからHookの存在自体が消えることがありました。ファイルのタイムスタンプ更新だけでは復活せず、Kiro自身のHook作成機能経由で再登録することでのみ回復しました。.kiro.hookの追加・変更はKiroの機能経由で行うことを推奨します。
バッチエクスポート方式への転換
Hookでのリアルタイム記録を諦め、依頼時に実行ログ全体をスキャンして完全なMarkdownを生成する方式に切り替えました。ポイントはexecutionId → 応答テキストのマッピングをローカルにキャッシュし、差分スキャンのみ行うことです。
# 初回: 全件スキャン(454ファイル・1.68GBで約43秒)
# 2回目以降: 新規追加ファイルのみ差分スキャン(数秒)
$cache = Get-Content "~/.kiro/kiro-export-cache.json" | ConvertFrom-Json
$newFiles = Get-ChildItem $execLogDir | Where-Object { $_.Name -notin $cache.Keys }
この方式なら20件以上のセッションを一括変換しても1分未満で完了し、標準エクスポートで発生していた長大セッションでの「On it.のまま復元できない」問題も解消しました。
v2移行:セッション保存がサーバー側管理に変わった
ある日のアップデート以降、workspace-sessionsフォルダへの新規書き込みが止まりました。調査の結果、以下が判明しました。
// workspace-sessions配下に残っていた移行マーカー
// ._migration-{sessionId}.json
{
"migratedAt": "2026-08-19T01:43:56.222Z",
"v2SessionId": "...",
"workspaceHash": "8844602fd1b1de9a",
"markerVersion": 2
}
workspaceStorage\{hash}\state.vscdb(VS Code系IDE共通のSQLite形式KVストア、テーブルはItemTable(key TEXT, value BLOB)のみ)を確認しても、sessionPanels.entriesキーにセッションのタイトル・IDが入っているだけで、会話履歴本体(agentSessions.*.cache等)は空でした。globalStorage配下、state.vscdb、History(エディタのファイル変更履歴、会話とは無関係)を含め、ローカルファイルシステム上のどこにも会話履歴データは存在しないことを確認しています。つまり、v2形式では会話履歴がKiro運営側のサーバーでのみ管理されるということです。
Kiro UI側には、v1のまま残っているセッションに対して「Migrate」ボタンが表示され、これを押すとローカルのJSONデータがサーバーにアップロードされてv2形式に切り替わる、という設計になっていました(UIバナー: "We changed how sessions are stored. Sessions from the previous version need to be migrated to be accessible.")。
新エクスポート形式(JSONL)とMarkdown変換
v2移行と同時に、標準エクスポート機能もMarkdown直接出力から、kiro-session-{id}.zip(session.json + messages.jsonl)のダウンロード形式に変わりました。
messages.jsonlは1行1メッセージのJSONL形式で、主なイベントタイプは次の通りです。
| type | 内容 |
|---|---|
user |
ユーザー発言。content(文字列)、images[]({data: 生base64, mimeType})、documents[]
|
assistant |
アシスタント応答テキスト。content、operationType、executionId
|
tool_call / tool_result
|
ツール呼び出しと結果 |
turn_start / turn_end
|
ターン境界(executionIdでグルーピング) |
これを読み、既存のMarkdown生成ロジック(タイムスタンプ整形・画像埋め込み・改行処理)を流用して変換するスクリプトを作成しました。
# ZIPを展開してsession.json/messages.jsonlを取得
Expand-Archive -Path $zipPath -DestinationPath $tempDir
# messages.jsonlを1行ずつパースし、typeごとに時系列で処理
Get-Content "$tempDir\messages.jsonl" | ForEach-Object {
$event = $_ | ConvertFrom-Json
switch ($event.type) {
"user" { # USERターン開始、images[]があればbase64デコードして保存 }
"assistant" { # 同一executionId内の複数say要素を結合してASSISTANT本文に }
"turn_end" { # タイムスタンプ確定 }
}
}
画像はimages[].data(プレフィックスなしの生base64)を[Convert]::FromBase64String()でデコードし、mimeTypeから拡張子を判定して保存します。この方式で、旧v1形式のエクスポートと完全に同じ見た目のMarkdownを再現できることを確認済みです。
まとめ
- v1形式は
workspace-sessions配下にローカルJSONとして保存されるが、アシスタント応答本文だけはexecutionId経由で別の巨大な実行ログ領域を検索する必要がある - Hookでのリアルタイム記録は、実行ログ領域の規模と最大1分以上の永続化ラグにより実用的でなく、バッチ方式(キャッシュ付き差分スキャン)に転換した
- ある時点のアップデートでセッション保存がサーバー側管理(v2)に変わり、ローカルには会話履歴が一切残らなくなった
- 標準エクスポートもMarkdownからJSONL+ZIP形式に変わったが、情報量は十分にあるため、旧形式互換のMarkdownへ変換するスクリプトで運用を継続している
Kiroはまだアップデート頻度の高いツールのため、本記事の内部構造に関する記述は執筆時点のスナップショットです。導入の背景や運用面での試行錯誤(Hookのタイムアウト調整、タスクスケジューラが権限で使えなかった代替策など)は、こちらの記事に詳しくまとめています。