Skip to content

feat(recording): multi-select capture-point model, recording becomes a rule flag - #1644

Merged
0x0079 merged 4 commits into
claude/flag-multi-enum-v8wl8wfrom
claude/recording-point-flags-v8wl8w
Sep 10, 2026
Merged

feat(recording): multi-select capture-point model, recording becomes a rule flag#1644
0x0079 merged 4 commits into
claude/flag-multi-enum-v8wl8wfrom
claude/recording-point-flags-v8wl8w

Conversation

@0x0079

@0x0079 0x0079 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1656#1658 — merge those first; this PR's diff then shows only the recording work below.

Summary

Recording enablement was a scenario-only 3-mode enum outside the rule-flag system, and OpenAI inbound traffic was never recorded at all. This PR remodels recording as a multi-select set of capture points and integrates it into the flag registry with rule-level override, narrowing the supported surface to the two request-side points.

Key Changes

  • Capture-point model: the 3-mode enum becomes a comma-separated set of typ.RecordingPoint (client_request / upstream_request); legacy values (request, request_response, staged_request_response) parse compatibly with zero config migration.
  • recording rule flag: multi_enum, Shared/override — a rule's point set overrides the scenario-level recording_v2 default, resolved by typ.EffectiveRecording at the handler prologue.
  • OpenAI coverage (P5): Chat / Responses handlers now create the recorder at the prologue (stashed in gin ctx, downstream reads via recording.FromGin), including success/failure emits on the pure passthrough paths.
  • Response-side paused: upstream_response / client_response stay in the value domain but their UI options and emits are commented out — capture quality is not acceptable yet; configs selecting them fall back to request points only (pinned by test).
  • Sink decoupled from scenario switch: GetOrCreateScenarioSink takes the request's effective mode, so rule-enabled recording works even when the scenario default is off; depth filtering belongs to the recorder.
  • Frontend: recording renders through the registry-driven multi_enum control; RecordingV2Control becomes multi-select on the same options.

Testing

  • New recording case in the protocoltest flag suite: rule-level enablement → gzip JSONL on disk → slim record carries exactly the selected points. Tests flush via the existing exported surface (GetOrCreateScenarioSink + obs.Sink.ForceFlush) — no production API added for testing.
  • typ unit tests for parsing/normalization/legacy-value compatibility, plus a registry guard pinning the flag's options to the RecordingPoint value domain.

@0x0079
0x0079 force-pushed the claude/recording-point-flags-v8wl8w branch from 046e756 to d02d71b Compare August 26, 2026 16:49
@0x0079
0x0079 changed the base branch from main to claude/recording-ctx-unification-v8wl8w August 26, 2026 16:56
Base automatically changed from claude/recording-ctx-unification-v8wl8w to main August 27, 2026 10:32
@0x0079
0x0079 force-pushed the claude/recording-point-flags-v8wl8w branch 2 times, most recently from 748fae1 to 7652379 Compare August 27, 2026 11:32
@0x0079
0x0079 changed the base branch from main to claude/flag-multi-enum-v8wl8w August 27, 2026 11:36
@0x0079
0x0079 force-pushed the claude/recording-point-flags-v8wl8w branch from 7652379 to dd7402a Compare August 27, 2026 14:46
@0x0079
0x0079 force-pushed the claude/recording-point-flags-v8wl8w branch from dd7402a to bf01437 Compare August 27, 2026 17:48
@0x0079
0x0079 force-pushed the claude/recording-point-flags-v8wl8w branch from bf01437 to e040f83 Compare August 29, 2026 08:56
@0x0079
0x0079 force-pushed the claude/recording-point-flags-v8wl8w branch from e040f83 to fa32641 Compare August 31, 2026 12:52
@FFengIll
FFengIll force-pushed the claude/recording-point-flags-v8wl8w branch from fa32641 to c4f87c4 Compare September 9, 2026 03:42
@0x0079
0x0079 force-pushed the claude/recording-point-flags-v8wl8w branch from c4f87c4 to 641544d Compare September 10, 2026 03:17
录制配置从三档模式枚举改为沿链路采集点的多选集合,并将启用权交给
flag 体系(scenario 默认 + rule 覆盖),详见 .design/recording.md §3.5。
构建在两块已就位的基座上:multi_enum flag 类型(上一 commit 的 flag
基建 PR)与 recorder 的 ctx 传递形态(#1649)。

点位模型(typ.RecordingPoint):
- client_request(入站请求)/ upstream_request(出站请求)/
  upstream_response(服务返回,值域内、UI 暂不放开——尚无采集实现)/
  client_response(最终返回);逗号分隔存储
- 旧三档枚举值(request/request_response/staged_request_response)由
  ParseRecordingMode 兼容展开,存量配置零迁移;写入口严格校验并存
  归一化形态

flag 融入:
- RuleFlags.Recording 进 registry(multi_enum,observability 类别,
  Shared/override 继承,scenario 侧沿用 recording_v2 key);registry
  测试补点位值域护栏
- resolveRuleFlagsWithScenario 解析继承并归一化;handler prologue 用
  typ.EffectiveRecording 决定 recorder 创建(存 gin ctx,下游经
  recording.FromGin 自取,不新增任何签名透传)
- GetOrCreateScenarioSink 改为接收请求的 effective mode:rule 开、
  scenario 关也能建 sink;GetScenarioRecordMode dep 移除

recorder/chain 按点位过滤:
- recorder 构造/绑定时归一化 mode,emit 按 Has(point) 挑字段;
  chain 的 StagePre/StagePost 分别按 client_request/upstream_request
  挂载(recorder.Wants,nil-safe);obs.NewSink 放开三档校验

覆盖补齐(修 P5):
- OpenAI Chat/Responses handler 接上 recorder(此前传 nil,OpenAI 入
  站从不录制);OpenAI→OpenAI 纯透传路径补上 emit(此前 recorder 有
  值也永不落盘)

前端:
- recording 走 registry 驱动的 multi_enum 复选控件,零 per-flag UI;
  RecordingV2Control 改为点位多选菜单(旧值归一化展示);codegen 已跑

测试:
- typ 点位模型单测;protocoltest flag 套件补 recording 行为用例
  (rule 级单独启用 → 落盘 → 断言恰好所选点位)。测试冲刷走既有
  导出面(GetOrCreateScenarioSink + obs.Sink.ForceFlush),不为测试
  新增生产 API

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7tYK69i7kpVE3d5pa4Dn2
响应侧采集质量不达标(流式靠组装/合成兜底),先保障接入链路与
client_request / upstream_request 两个 request 点位扎实有效。全部以
注释方式暂停,恢复时取消注释即可(.design/recording.md §3.5):

- recorder emit:FinalResponse 分支注释(内部采集 SetAssembledResponse
  与 AttachRecorderHooks 组装机制原样保留)
- flag registry:client_response 选项注释(与 upstream_response 并列,
  各附恢复条件);description 同步
- RecordingV2Control:client_response 勾选项注释;归一化容忍全值域,
  存量含响应点位的配置在切换时不丢值
- 既有响应录制断言(anthropic_recording_e2e / hook_external 三例)
  注释保留,改为钉住"暂停期不落 final_response";protocoltest 的
  recording 用例增补:存量选中 client_response 也只落 request 点位

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7tYK69i7kpVE3d5pa4Dn2
…sponse

client_request's "client" names the source (the client sent it). Copying
that prefix onto the response point made "client" name the destination
instead (the response sent to the client) — the same prefix meaning
opposite things depending on which side of the pair you're reading,
which reads as "a response the client produced". upstream_response is
unaffected: "upstream's response" is a source-based name on both sides.

Renamed RecordClientResponse/client_response -> RecordFinalResponse/
final_response everywhere (typ.RecordingPoint, the legacy-mode expansion
table, flag registry, RecordingV2Control, config validation error
strings, .design docs). This also aligns the point name with the layer
that already used "final" — obs.Record.FinalResponse / json key
final_response — so the naming is now consistent top to bottom.

No migration needed: the point is not yet reachable through the UI (its
option is commented out, response-side capture is paused), so no stored
rule/scenario config can contain the literal string "client_response".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7tYK69i7kpVE3d5pa4Dn2
internal/protocol/request/{openai_responses_to_anthropic,openai_responses_to_chat}_test.go
had a struct field's newline silently dropped (CallID/Output collapsed
onto one line with stray tabs) as this branch was kept up to date with
main. gofmt -l didn't flag it — the joined form is syntactically valid
Go, just visually inconsistent with the surrounding style. Unrelated to
recording; picked up while auditing this branch's formatting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7tYK69i7kpVE3d5pa4Dn2
@0x0079
0x0079 force-pushed the claude/recording-point-flags-v8wl8w branch from d51ebdd to 876cd9d Compare September 10, 2026 05:39
@0x0079
0x0079 merged commit a17398b into main Sep 10, 2026
1 check passed
0x0079 added a commit that referenced this pull request Sep 10, 2026
…1658)

> Stacked on #1656 — merge that first; this PR's diff then shows only
the work below.

## Summary

The flag registry had no multi-select value type, so any flag needing
"pick several of these options" had to invent per-flag UI.

## Key Changes

- **`FlagTypeMultiEnum`**: multi-select over the spec's Options, stored
comma-separated; the empty set is the inactive state (no
first-option-default rule, unlike enum).
- **Registry constraints**: test-enforced — ≥2 options, non-empty unique
values/labels.
- **Frontend**: `FlagCatalogDialog` renders a registry-driven checkbox
group; `multiEnumValues` / `toggleMultiEnumValue` helpers plus
`flagDefault`/`isFlagActive` cases — no per-flag UI code.

## Notes

- No flag uses the type yet; the first consumer (recording capture
points) is #1644.

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants