0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

coding agent のログを全部保存する前に、session stream を review できる単位に切る

0
Posted at

coding agent を一晩走らせると、ログはちゃんと残る。問題は、そのログを翌朝だれも読まないことだ。正確には、読めない。

prompt、response、tool call、approval 待ち、runtime error、token usage。全部を一つの transcript として保存すると、「何が起きたか」は残る。でも「どこを見ればいいか」が消える。これは地味にきつい。

この記事では model 比較はしない。agent の作業をあとから review できるようにするために、session stream をどういう粒度で切るかだけを書く。

chat 履歴と review 用 event は別物

まず、ID を分ける。

sessionId   : ひとまとまりの agent 作業
turnId      : user / agent の一往復
toolCallId  : file、network、external API など tool 実行の単位

message ID だけで頑張ると、途中でつらくなる。たとえば npm test が timeout した turn と、その前に agent が書き換えた file diff と、その後の retry を同じ message 履歴から探すことになる。

GitHub Copilot の agent session streaming は、prompt / response / tool call などの session activity を streaming endpoint や REST API で見られる方向に寄っている。Vercel eve の Agent Runs も、run の中で turn、model call、tool call、runtime error を追える。

見る単位は「長い chat」ではなく「session の中で起きた event」になってきている。

最初の schema は小さくていい

最初から observability 基盤を作ろうとすると重い。まずは自分の team で normalize する event を決めるくらいでいい。

type AgentReviewEvent = {
  sessionId: string
  turnId: string
  toolCallId?: string
  actor: 'user' | 'agent' | 'tool'
  eventType:
    | 'prompt'
    | 'response'
    | 'tool_call_started'
    | 'tool_call_completed'
    | 'tool_call_failed'
    | 'approval_required'
  toolName?: string
  durationMs?: number
  errorKind?: string
  createdAt: string
}

ここで tool_call_failed をただの response に混ぜない。approval 待ちも error ではない。review で見たいものは、chat UI で自然に見えるものとは少し違う。

token usage も欲しくなる。developer が見るなら step ごとの input / output token は役に立つ。ただ、最初の schema に全部を詰めるより、用途が見えてから足すほうが失敗しにくい。

type AgentReviewCost = {
  sessionId: string
  turnId: string
  toolCallId?: string
  inputTokens: number
  outputTokens: number
  model?: string
}

cost attribution はこれで完璧、とは言わない。少なくとも「どの session がやたら長かったか」を見つける入口にはなる。

developer view と operator view を同じ stream から作る

review UI を作るとき、raw log 画面だけを用意するとだいたい読まれない。

developer が欲しいのは raw tool name、input / output JSON、stack trace、per-step token count。実装の調査には必要だ。

一方で、operator や非エンジニア寄りの reviewer が見たいのは別のものになる。人間向けの tool 名、agent が何をしたかの短い要約、承認待ちか失敗か、見るべき session だけの list。JSON を全部見せても判断は速くならない。

Vercel eve の Agent Runs に Developer mode と Business mode があるのは、この分け方の参考になる。元の event は同じでも、表示の粒度は変えたほうがいい。

雑に作るなら、こんな感じで分ける。

type ReviewVisibility = 'developer' | 'operator'

type ReviewEventView = AgentReviewEvent & {
  visibility: ReviewVisibility
  displayName: string
  summary?: string
  tokenUsage?: {
    input: number
    output: number
  }
}

summary は監査証跡の代わりではない。入口だ。raw JSON を保存しない理由にしてしまうと、失敗したときに何も追えなくなる。

retention は後回しにしない

agent log は、残そうと思えばいくらでも残る。だから先に保存期間を決めておく。

たとえば、こう分ける。

agent_review_retention:
  recent_full_events: 48h
  flagged_sessions: 30d
  summaries: 90d

直近 window は full event を残す。問題がありそうな session は flag を立てて長めに残す。operator 向け summary は検索用に残してもいい。ただし、secret や private data を summary に入れない。

GitHub の REST API は直近 48 時間の usage records を pull する形として説明されている。Vercel eve の retention は plan によって 12 hours、1 day、3 days、Observability Plus で 30 days という差がある。

どちらかを正解として真似る必要はない。「全部保存する」の前に recent と flagged を分ける。

transcript viewer ではなく inbox にする

僕なら review UI は chat transcript から始めない。inbox から始める。

Session list
  -> flagged turns
  -> tool call detail
  -> reviewer decision

session list では、duration、token usage、error count、approval count で sort する。長い会話を上から読む画面ではなく、怪しい session を先に見つける画面にする。

flagged turns では、失敗した turn、approval で止まった turn、token が急に増えた turn だけを見る。tool call detail では input / output / duration / error を確認する。

最後に reviewer decision を残す。

type ReviewDecision =
  | 'reviewed'
  | 'needs_followup'
  | 'ignored'

type ReviewDecisionEvent = {
  sessionId: string
  turnId?: string
  toolCallId?: string
  decision: ReviewDecision
  reviewerId: string
  note?: string
  createdAt: string
}

これがないと、「あとで確認した」は口頭運用になる。小さい team でも、ここは event として残したほうがいい。

streaming と REST pull は ingestion の違い

near real-time に見たいなら streaming。後追い batch なら REST pull で足りる。frontend の review model では、どちらも AgentReviewEvent に落とす。

WebSocket や previous_response_id は agent UI の turn state を扱ううえで便利だが、review UI の主役ではない。connection の持ち方と、あとから読む event model は分けて考える。

async function ingest(raw: unknown): Promise<AgentReviewEvent[]> {
  const providerEvents = normalizeProviderEvent(raw)

  return providerEvents.map((event) => ({
    sessionId: event.sessionId,
    turnId: event.turnId,
    toolCallId: event.toolCallId,
    actor: event.actor,
    eventType: event.eventType,
    toolName: event.toolName,
    durationMs: event.durationMs,
    errorKind: event.errorKind,
    createdAt: event.createdAt,
  }))
}

provider 固有の shape を画面に直接流すと、あとで別 agent runtime を足すときに UI が壊れる。自分たちの review event に一度落とす。小さくてもここは分ける。

最後に repo に置く checklist

実装を始める前に、これくらいを repo に置いておくと会話が速い。

agent_review:
  ids:
    - sessionId
    - turnId
    - toolCallId
  event_types:
    - prompt
    - response
    - tool_call_started
    - tool_call_completed
    - tool_call_failed
    - approval_required
  views:
    - developer
    - operator
  retention:
    recent_full_events: 48h
    flagged_sessions: longer
  decisions:
    - reviewed
    - needs_followup
    - ignored

coding agent のログは、残すだけなら簡単になってきた。難しいのは、翌朝の人間が読める粒度に切ることだと思う。

chat 履歴を全部保存する前に、session、turn、tool call、decision を分ける。地味だけど、agent を team workflow に入れるならこの順番のほうが後で困らない。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?