Skip to content

feat(sight): add Codex CLI adaptation and cross-chunk SSE recovery#1133

Merged
chengshuyi merged 6 commits into
alibaba:mainfrom
Daydreamer-Li:feature/sight/codex-cli-adaptation
Jun 26, 2026
Merged

feat(sight): add Codex CLI adaptation and cross-chunk SSE recovery#1133
chengshuyi merged 6 commits into
alibaba:mainfrom
Daydreamer-Li:feature/sight/codex-cli-adaptation

Conversation

@Daydreamer-Li

Copy link
Copy Markdown
Collaborator

Description

Add Codex CLI adaptation and cross-chunk SSE recovery for AgentSight.

Codex CLI links aws-lc statically (BoringSSL-compatible) and is shipped as a stripped musl static-pie binary, so AgentSight could not attach any uprobe. This change adds a three-tier fallback (symbol / byte-pattern / offset table), handles the SSL_*_ex ABI variants, and recovers token usage when codex's oversized response.completed event spans multiple TLS records.

Also fixes codex's session ID by extending ResponseSessionMapper to parse its rollout--.jsonl filenames (gated by BPF on tgid pid for cross-probe correlation), and classifies Codex without listening ports as AgentRole::Client so the healthcheck dashboard hides idle instances.

Includes the extract-codex-offsets.py helper plus a doc covering the official symbols package (0.140+), self-built non-stripped binary (0.139-), and fingerprint workflow.

Related Issue

closes #1036

Type of Change

  • New feature

Scope

  • sight (agentsight)

Checklist

  • I have read the Contributing Guide
  • My code follows the project's code style
  • For sight: cargo clippy -- -D warnings and cargo fmt --check pass
  • Lock files are up to date (Cargo.lock)

Testing

cargo fmt --check, cargo clippy -- -D warnings, and cargo test all pass in src/agentsight.

@github-actions github-actions Bot added component:sight src/agentsight/ scope:documentation ./docs/|./*.md|./NOTICE labels Jun 25, 2026
Codex CLI links aws-lc statically (BoringSSL-compatible) and is shipped as
a stripped musl static-pie binary, so AgentSight could not attach any
uprobe. Add a three-tier fallback (symbol / byte-pattern / offset table),
handle the SSL_*_ex ABI variants, and recover token usage when codex's
oversized response.completed event spans multiple TLS records.

Also fix codex's session ID by extending ResponseSessionMapper to parse
its rollout-<ts>-<UUID>.jsonl filenames (gated by BPF on tgid pid for
cross-probe correlation), and classify Codex without listening ports as
AgentRole::Client so the healthcheck dashboard hides idle instances.

Includes the extract-codex-offsets.py helper plus a doc covering the
official symbols package (0.140+), self-built non-stripped binary
(0.139-), and fingerprint workflow.

Closes alibaba#1036
@Daydreamer-Li Daydreamer-Li force-pushed the feature/sight/codex-cli-adaptation branch from 071056f to 8022b8a Compare June 25, 2026 07:51
Add tests for SSE continuation buffer, Responses API parsing,

and role inference paths from the Codex CLI adaptation.

Relates to alibaba#1036
@Daydreamer-Li Daydreamer-Li force-pushed the feature/sight/codex-cli-adaptation branch from 74705c2 to 2412714 Compare June 25, 2026 08:48
jfeng18
jfeng18 previously approved these changes Jun 25, 2026
@chengshuyi

Copy link
Copy Markdown
Collaborator

AgentSight Code Review — PR #1133

变更规模: +2285 / -488, 24 files, 3 commits

按 6 维度审查(硬性规则合规、eBPF 安全、FFI 边界、Footprint Ladder、流水线测试覆盖、文档同步),发现以下问题:


1. [硬性规则] PR diff 总行数超限

总计 2773 行变更,远超 AGENTS.md 规定的 800 行上限。建议拆分为 3-4 个独立 PR:

  • (a) offset 表 + 提取脚本 + 文档
  • (b) SSE continuation buffer
  • (c) session mapper + filewrite BPF 扩展
  • (d) unified/genai 集成

拆分后每个 PR 可独立 review、revert 和 bisect。

2. [eBPF 安全] src/bpf/filewrite.bpf.c:1467-1475 — rollout 匹配过宽

matched_rollout 仅检查前 8 字符 rollout-,未验证 .jsonl 后缀或 UUID 格式。虽然用户空间 extract_session_id 会做精确过滤,但 BPF 层宽匹配意味着所有以 rollout- 开头的文件写入都会产生 ring buffer 事件,在高写入负载下可能造成不必要的开销。

建议:在 BPF 代码注释中明确说明"宽匹配 + 用户空间精确过滤"的设计意图;或增加 .jsonl 后缀检查(固定偏移,verifier 友好)。

3. [eBPF 安全] src/probes/sslsniff.rs — 移除 canonicalize 可能影响容器场景

移除了 std::fs::canonicalize(&path) 调用及对应测试 uprobe_path_canonicalize_resolves_real_path(约 40 行),改为直接将 /proc/{pid}/root/... 路径传给 uprobe attach。这改变了容器场景下的行为——之前 canonicalize 会将 overlay rootfs 路径解析为宿主路径。如果 libbpf-rs 或内核不支持 /proc/{pid}/root/ 路径作为 uprobe target,容器内的 SSL probe 将静默失败。

建议:确认 libbpf-rs 版本是否支持 /proc/{pid}/root/ 路径直接 attach;补充容器环境下的集成测试或至少添加注释说明为何 safe。

4. [eBPF 安全] src/probes/sslsniff.rs:2884-2886 — detach_process inode 清理可能导致重复 attach

detach_process 现在从 traced_files 中移除 inode,允许新进程重新 attach。原设计故意保留 inode(因为 uprobes 是全局 pid=-1 attach)。新逻辑下,如果进程 A 和 B 使用同一库,A 退出后 B 的 link 仍然有效,但 traced_files 已移除该 inode,下次 scan 可能对 B 重复 attach 产生 duplicate fd。

建议:确认 attach_process 中的去重逻辑是否能覆盖此 race;或在 detach 时检查是否有其他 pid 仍引用同一 inode。

5. [流水线测试覆盖] src/unified.rs — 删除 drained_sse_events 可能回归 #973

删除了 drained_sse_events 函数及其 3 个测试(drained_sse_events_decodes_unfinalized_compressed_stream 等),这些测试覆盖了 fix(#973) 中压缩 SSE 流在进程死亡时的恢复逻辑。SseActive drain 分支也从解码 compressed_buffer 简化为直接返回 sse_events。如果 #973 的场景仍然存在(压缩 SSE 流未完成时进程退出),token usage 数据将丢失。

建议:确认 #973 是否已通过其他机制修复(如 aggregator 层提前解码);若未修复,保留 drain 解码逻辑和测试。

6. [硬性规则] scripts/extract-codex-offsets.py:82-87 — detect_codex_version 全量读取二进制

detect_codex_version() 将整个二进制读入内存(f.read()),Codex binary 约 276MB,内存受限环境可能 OOM。

建议:改用分块读取(每次 1MB),仅搜索版本字符串模式:

with open(path, "rb") as f:
    for chunk in iter(lambda: f.read(1 << 20), b""):
        text = chunk.decode("utf-8", errors="ignore")
        m = re.search(pat, text)
        if m: return m.group(1)

7. [硬性规则] src/aggregator/http/aggregator.rs — needs_sse_continuation_buffer 路径匹配过宽

path.contains("/v1/responses") 可能误匹配子路径(如 /v1/responses/abc/items),导致不必要的 continuation buffer 开销。

建议:改为 path == "/v1/responses" || path.starts_with("/v1/responses?")

8. [流水线测试覆盖] src/aggregator/http/aggregator.rs — continuation buffer 缺少边界测试

新增 4 个测试覆盖基本场景,但缺少:

  • MAX_CONTINUATION_BYTES (1MB) 溢出截断行为
  • last_appended_src_ptr 去重逻辑(同一 SSL_read 产生多个 SSE event)
  • 非 Responses API 路径下 continuation buffer 不被激活的负向测试

建议:补充上述边界测试用例。

9. [文档同步] AGENTS.md / CLAUDE.md 未更新

新增了 Codex CLI 适配(三级回退、offset 表、elf_buildid 解析器)和 SSE continuation buffer 两个重要能力,但导航文档未同步。

建议:AGENTS.md 的 eBPF Probes / Module Map 章节补充 Codex 条目;CLAUDE.md 补充 extract-codex-offsets.py 用法。

10. [Footprint Ladder] src/probes/elf_buildid.rs — 仅支持 ELF64

手动解析 ELF PT_NOTE 段实现正确,但未处理 32-bit ELF(class != 2 直接返回 None)。

建议:在模块顶部注释说明仅支持 ELF64 的限制;确认 Codex CLI 不会发布 32-bit 构建。

11. [硬性规则] src/aggregator/http/aggregator.rs:691 — 日志级别从 trace 提升为 debug

高并发 SSE 场景下 debug 日志量显著增加,可能影响生产环境性能。

建议:确认是否为临时调试提升;合入前应改回 trace 或添加采样逻辑。

12. [硬性规则] src/agentsight/agentsight.json — 文件末尾缺少换行符

\ No newline at end of file

建议:添加尾部换行,符合 POSIX 规范且避免 git diff 噪声。


未覆盖维度说明

  • 维度 3 (FFI 边界):未发现 src/ffi.rscbindgen.toml 变更,不适用。

亮点

  • 三级回退机制(符号 → 字节模式 → offset 表)设计合理,fingerprint 匹配(file_size + head_64k_sha256 + BuildID)避免了误挂
  • write_is_ex / read_is_ex 标志正确处理了 aws-lc SSL_*_ex ABI 差异
  • codex-adaptation.md 文档详实,覆盖了新版本偏移提取的完整工作流
  • ResponseSessionMapper 的 pid fallback 设计巧妙解决了 Codex rollout 文件无 response_id 的问题

Narrow path matching, revert log level to trace,

chunked binary read, ELF64 doc, trailing newline,

BPF comment, and continuation buffer tests.

Relates to alibaba#1036
Add comment explaining /proc/pid/root uprobe safety,

fix detach_process inode cleanup to avoid duplicate

attach, and sync AGENTS.md with Codex adaptation docs.

Relates to alibaba#1036
@Daydreamer-Li

Copy link
Copy Markdown
Collaborator Author

感谢详细的 review!以下是逐条回复:


#1 PR diff 超限

同意行数较多,但本次变更构成一个完整的 Codex CLI 适配功能(offset 表 + BPF 扩展 + SSE continuation buffer + genai 集成),拆分后各部分无法独立验证端到端效果,因此保持单 PR。

#2 filewrite.bpf.c rollout 匹配过宽

已补充注释说明设计意图:BPF 层使用前缀匹配(rollout-)是 eBPF verifier 对变长后缀检查的限制所致,用户空间 extract_session_id 做精确过滤。非 .jsonlrollout-* 文件在 Codex session 目录外极少出现,ring buffer 开销可忽略。

#3 移除 canonicalize 可能影响容器场景

/proc/{pid}/root/<path> 是更正确的做法:内核 uprobe 机制原生支持通过进程 mount namespace 解析路径,而 canonicalize() 会将 overlayfs 路径解析为宿主机 lower/upper 目录,libbpf 反而无法映射回 inode。已补充注释说明。

#4 detach_process inode 清理可能导致重复 attach

已修复。detach_process 现在在移除 inode 前检查 pid_inodes 中是否有其他 pid 仍引用同一 inode,仅当无引用时才从 traced_files 移除,避免对仍存活的进程重复 attach。

#5 删除 drained_sse_events 可能回归 #973

确认非回归。#973 的修复逻辑(压缩 SSE 流在进程死亡时的恢复)已迁移到 aggregator 层:

  • compressed_completion_ignores_embedded_terminatorcompressed_buffer_cap_finalizes_when_exceeded 测试仍在 aggregator.rs
  • 压缩流通过 finish_compressed_sse 在完成时解码
  • drain_connections_for_pid / drain_dead_pid_connections 仍在 unified.rs

#6 extract-codex-offsets.py 全量读取二进制

已改为 1 MiB 分块读取,避免 276 MB 二进制导致 OOM。

#7 needs_sse_continuation_buffer 路径匹配过宽

已收窄:删除 path.contains("/v1/responses"),仅保留 path.ends_with("/responses")path.contains("/responses?"),避免误匹配 /v1/responses/{id}/items 等子路径。

#8 continuation buffer 缺少边界测试

已补充 3 个测试:

  • test_needs_sse_continuation_buffer_non_responses_path:负向测试(chat/completions、子路径不触发)
  • test_sse_continuation_buffer_max_bytes_truncation:1 MiB 溢出截断验证
  • test_sse_continuation_buffer_dedup_same_src_ptr:同源 SSL event 去重验证

#9 文档同步

已更新 AGENTS.md:Module Map 补充 codex_offsets / elf_buildid,eBPF Probes 章节补充三级回退和 SSE continuation buffer 说明,Design Docs 补充 codex-adaptation.md 链接。CLAUDE.md 已在原始 PR 中同步。

#10 elf_buildid.rs 仅支持 ELF64

已补充 doc comment 说明 ELF64 限制及原因(Codex CLI 为 x86-64 musl 静态二进制)。

#11 日志级别 trace→debug

已改回 trace!

#12 agentsight.json 缺尾部换行

已补 \n


所有修改已推送(commit e8a1eef6),941 tests 通过、clippy、fmt、arch boundary 均通过。

@chengshuyi

Copy link
Copy Markdown
Collaborator

Re-Review: PR #1133 (v2)

针对上一轮 review 的 12 条 findings,逐一验证修复情况:

✅ 已修复(10/12)

# Finding 状态 验证
2 BPF rollout 匹配过宽 filewrite.bpf.c 新增详细注释说明"intentionally prefix match"及原因(verifier 限制 + 用户空间精确过滤)
4 detach_process inode race 新增 still_used 检查:仅当无其他 pid 引用同一 inode 时才从 traced_files 移除,避免 duplicate attach
6 detect_codex_version OOM 改为 1 MiB 分块读取 (f.read(1 << 20)),不再全量加载二进制
7 needs_sse_continuation_buffer 路径匹配过宽 改为 ends_with("/responses") || contains("/responses?"),并附注释说明为何不用 broad contains
8 continuation buffer 缺少边界测试 新增 3 个测试:test_needs_sse_continuation_buffer_non_responses_path(负向)、test_sse_continuation_buffer_max_bytes_truncation(1MB 截断)、test_sse_continuation_buffer_dedup_same_src_ptr(去重)
9 AGENTS.md 未更新 新增 Codex CLI 适配(三级回退)和 SSE Continuation Buffer 两个章节,Module Map 表格同步更新,Related Docs 补充链接
10 elf_buildid 仅支持 ELF64 未文档化 read_buildid doc comment 明确标注 "ELF64 only." 并说明 32-bit 扩展方式
11 日志级别从 trace 提升为 debug 改回 log::trace!,保留新增的 eventdata_len 字段
12 agentsight.json 缺少尾部换行 已添加换行符,\ No newline at end of file 消除
3 canonicalize 移除影响容器场景 commit message "address review #3" 表明已处理;sslsniff.rs 中直接使用 /proc/{pid}/root/ 路径,libbpf-rs 支持此路径作为 uprobe target

⚠️ 仍需关注(2/12)

#1 PR diff 总行数超限 — 当前 +2452/-490(25 files),仍远超 800 行上限。理解 Codex 适配是一个完整特性难以完全拆分,但建议后续类似 PR 尽量按模块拆分。此项不阻塞合入。

#5 drained_sse_events 删除可能回归 #973 — 该函数及其 3 个测试仍被删除。从最新 diff 看,SseActive drain 分支简化为直接返回 sse_events,不再有 compressed_buffer 解码逻辑。请确认 #973 场景(压缩 SSE 流未完成时进程退出)是否已通过其他机制覆盖(如 aggregator 层在 stream 结束前已完成解码)。若未覆盖,建议恢复 drain 解码或补充替代测试。

🆕 新发现

13. [eBPF 安全] sslsniff.rs:detach_process still_used 检查复杂度 — 新增的 inode 引用计数检查遍历 self.pid_inodes.values()(O(n×m)),在大量并发进程场景下可能有性能影响。当前 pid_inodes 规模通常较小(活跃 agent 进程数),可接受。但若未来扩展到数千进程,建议改用 HashMap<Inode, usize> 引用计数。
建议:添加注释说明当前 O(n×m) 复杂度的预期上界;或在后续优化中引入引用计数 map。

总结

10/12 条 findings 已妥善修复,修复质量高(代码+注释+测试三位一体)。剩余 2 条中 #1 不阻塞合入,#5 需要作者确认 #973 覆盖情况。新增 #13 为低优先级优化建议。

整体评价:LGTM,待确认 #5 后可合入。 👍

Drain path now decodes compressed_buffer when sse_events

is empty, preventing token usage loss on process exit.

Relates to alibaba#1036
@Daydreamer-Li

Copy link
Copy Markdown
Collaborator Author

感谢 re-review!

#5 drained_sse_events 删除可能回归 #973

经确认,这确实是潜在回归。已修复(commit bdb02396)。

问题根因:原 drained_sse_events 函数在 drain 路径中解码未完成的压缩 SSE buffer,PR 中删除该函数后,drain_and_persist_dead_connections 的 SseActive 解构使用 .. 忽略了 compressed_buffer,导致进程死亡时未完成的压缩 SSE 流丢失 token 数据。

修复方式:在 drain 路径的 SseActive 解构中新增一个 match arm,当 compressed_buffer 非空且 sse_events 为空时,调用 HttpConnectionAggregator::decode_compressed_sse 解码 buffer:

ConnectionState::SseActive {
    request: Some(req),
    sse_events,
    compressed_buffer: Some(buf),
    content_encoding,
    response_headers,
} if sse_events.is_empty() && !buf.is_empty() => {
    // fix(#973): decode the unfinalized compressed buffer
    let is_chunked = HttpConnectionAggregator::is_chunked_response(&response_headers);
    let decoded = HttpConnectionAggregator::decode_compressed_sse(
        &buf, content_encoding.as_deref(), is_chunked, &response_headers.source_event,
    );
    ("SseActive", req, decoded)
}

这与原始 drained_sse_events 的逻辑等价,复用了 decode_compressed_sse(同一函数也被 live completion 的 finish_compressed_sse 使用)。

#13 still_used 检查复杂度

同意当前 O(n×m) 在大规模场景下需优化。当前 pid_inodes 规模为活跃 agent 进程数(通常 < 100),可接受。后续如需扩展,可引入 HashMap<Inode, usize> 引用计数。暂不阻塞合入。


全部 12 条 findings 已处理,941 tests 通过、clippy/fmt/arch boundary 均通过。

@chengshuyi

Copy link
Copy Markdown
Collaborator

Re-Review v3: PR #1133

验证上轮 2 条待确认项 + 1 条新发现的修复情况:

#5 drained_sse_events / #973 — 逻辑已恢复

commit fix(sight): restore compressed SSE drain decode for #973 将压缩 SSE drain 解码逻辑内联回 unified.rs 的 match arm 中:

ConnectionState::SseActive {
    request: Some(req),
    sse_events,
    compressed_buffer: Some(buf),
    content_encoding,
    response_headers,
} if sse_events.is_empty() && !buf.is_empty() => {
    // fix(#973): decode the unfinalized compressed buffer
    let decoded = HttpConnectionAggregator::decode_compressed_sse(...);
    ("SseActive", req, decoded)
}

使用 guard 条件精确匹配"events 为空 + buffer 非空"的场景,逻辑等价于原来的 drained_sse_events 函数且更紧凑。#973 回归风险已消除。

Nit: 原来的 3 个单元测试(drained_sse_events_decodes_unfinalized_compressed_stream 等)仍被删除,内联后的 drain 解码路径缺少直接测试保护。建议后续补一个针对 drain 路径压缩解码的集成测试,不阻塞本次合入。

#1 PR 行数 — 已知悉

+2476/-489,作者选择作为完整特性提交,理解。

ℹ️ #13 detach_process still_used 复杂度 — 无变化

仍为 O(n×m) 遍历,当前规模可接受,作为后续优化项记录。


最终评价

全部 12 条原始 findings + 1 条新增 finding 均已妥善处理。代码质量高,review 响应迅速。

LGTM 🚀

@chengshuyi chengshuyi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. All 13 review findings addressed. #973 drain decode restored.

@chengshuyi chengshuyi merged commit 924c2ac into alibaba:main Jun 26, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component:sight src/agentsight/ scope:documentation ./docs/|./*.md|./NOTICE

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(sight): support Codex LLM call capture

3 participants