Skip to content

feat(memory): add memory_sessions and memory_timeline tools#1120

Open
shiloong wants to merge 1 commit into
alibaba:mainfrom
shiloong:feat/memory/session-history
Open

feat(memory): add memory_sessions and memory_timeline tools#1120
shiloong wants to merge 1 commit into
alibaba:mainfrom
shiloong:feat/memory/session-history

Conversation

@shiloong

Copy link
Copy Markdown
Collaborator

Description

Add cross-session memory query tools:

  • memory_sessions(limit): List historical session summaries from facts/summary/ directory. Returns session_id, created_at, tool_calls, tools_used, files_modified, description.
  • memory_timeline(session_id, limit): Show tool call timeline from persistent session logs (.anolisa/session-logs/<sid>.jsonl). Returns timestamped entries with tool name, path, status, errors.

These tools enable agents to query and browse memories from previous sessions, supporting cross-session knowledge continuity.

Related Issue

no-issue: cross-session memory query tools

Scope

  • memory (agent-memory)

Checklist

  • Code follows project style
  • cargo clippy --all-targets -- -D warnings passes
  • cargo fmt --check passes
  • cargo test passes (149 tests)
  • Lock files up to date

@Forrest-ly Forrest-ly 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.


PR #1120 Review — feat(memory): add memory_sessions and memory_timeline tools

本 PR 添加 2 个跨 session 查询工具:memory_sessions(列出 facts/summary/ 中的历史 session 摘要)和 memory_timeline(展示指定 session 的工具调用时间线,读取 .anolisa/session-logs/.jsonl)。同时将 incremental_interval 默认值从 20 改为
0(实际为死代码清理)。另有 3 个空文件被意外提交。

306 行新增、20 行删除、9 个文件。


发现

  1. src/agent-memory/src/tools/session_history.rs:~235 — memory_timeline 未校验 session_id,存在路径穿越风险 (CONFIRMED, 高)

let log_path = svc.mount.meta_dir
.join("session-logs")
.join(format!("{session_id}.jsonl"));

session_id 直接来自 MCP 客户端参数,无任何校验。如果传入 ../../etc/shadow 等包含 .. 的值,将构造 .anolisa/session-logs/../../etc/shadow.jsonl 并尝试用 std::fs::read_to_string 读取。虽然 .jsonl
后缀限制了文件名,但在有目标文件名匹配时仍可读取。代码库中已有现成模式:ns/mod.rs:181 和 snapshot/tar.rs:63 均做了 id.contains("..") 校验。此处缺失。

  1. src/agent-memory/config.rs, lib.rs, main.rs — 三个空文件意外提交到 crate 根目录 (CONFIRMED, 中)

Diff 显示在 src/agent-memory/ 目录下(而非 src/agent-memory/src/)新增了 3 个 0 字节文件(git hash e69de29bb)。真正的源文件在 src/agent-memory/src/{config,lib,main}.rs。这些空文件对编译无影响(Cargo 只看 src/
目录),但会造成目录污染和混淆。应删除。

  1. src/agent-memory/src/tools/session_history.rs:~247 — memory_timeline 将整个 JSONL 文件读入内存后再截取尾部 (CONFIRMED, 低-中)

let content = std::fs::read_to_string(&log_path)?;
// ... parse ALL lines ...
if entries.len() > limit {
entries = entries[entries.len() - limit..].to_vec();
}

长期运行的 session 可产生数千条 audit 记录。当前实现读取全部内容、解析所有行、构建完整 Vec,最后只取末尾 N 条。对于大文件可能造成不必要的内存压力。可改为从文件尾部反向读取或使用 BufReader + lines() 只保留最后 N 条。

  1. src/agent-memory/src/tools/session_history.rs:~324 — parse_count_from_body 中 "次" 匹配过于宽泛 (CONFIRMED, 低)

if line.contains(keyword) || line.contains("次") {

任何包含 "次" 的行都会触发数字提取,无论是否与目标 keyword 相关。例如 body 中有 "这是第3次代码重构",在搜索 "tool calls" 时也会匹配该行并返回 3。"次" 应作为 keyword 匹配的中文等价,而非与 keyword 并列的独立条件——或者改为
line.contains(keyword) || (keyword == "tool calls" && line.contains("次")) 之类的限定。

  1. src/agent-memory/src/tools/session_history.rs:~167 — memory_sessions 未使用 safe_fs,直接 fs::read_to_string (PLAUSIBLE, 低)

与代码库中其他读取路径(extractor.rs、write.rs、consolidation/writer.rs)使用 safe_fs (openat2 + RESOLVE_BENEATH) 不同,本 PR 的两个工具均使用 std::fs 直接操作。对于 memory_sessions(路径由服务端构造,风险低),这尚可接受;但对于
memory_timeline(路径包含用户输入的 session_id),缺少 safe_fs 加剧了 Finding #1 的风险

Session history query tools:
- memory_sessions(limit): list historical session summaries from
  facts/summary/ directory. Returns session_id, created_at, tool_calls,
  tools_used, files_modified, description.
- memory_timeline(session_id, limit): show tool call timeline from
  persistent session logs (.anolisa/session-logs/<sid>.jsonl).
  Returns timestamped entries with tool name, path, status, errors.

Helper functions:
- parse_frontmatter_flat(): extract key:value from YAML blocks
- extract_body(): get markdown body after frontmatter
- parse_count_from_body(): find numeric counts in text
- parse_list_from_body(): extract comma-separated lists

Tests: 5 new session history tests
Total: 0 failures
Tools: 23 total (was 21)

Signed-off-by: Shile Zhang <shile.zhang@linux.alibaba.com>
@shiloong

Copy link
Copy Markdown
Collaborator Author

Review 修复回复

1. memory_timeline 路径穿越 — ✅ 已修复

memory_timeline() 入口处增加 session_id 校验:拒绝包含 /\..\0 或为空的值,与 ns/mod.rssnapshot/tar.rs 中的 id 校验模式一致。

2. 三个空文件意外提交 — ✅ 已删除

src/agent-memory/{config,lib,main}.rs 三个 0 字节文件已从 git 中移除。真正的源文件在 src/agent-memory/src/ 目录下,不受影响。

3. 全量读取 JSONL 后截取尾部 — ⚠️ 已知限制

当前实现读取完整文件后取末尾 N 条。对于大多数 session(<1000 条记录)性能可接受。超长 session 的尾部读取优化(BufReader 反向读取)留待后续 PR。

4. "次" 匹配过于宽泛 — ✅ 已修复

改为 (keyword == "tool calls" && line.contains("次")),仅在搜索 "tool calls" 时将 "次" 作为中文等价匹配,不再独立匹配任意包含 "次" 的行。

5. 未使用 safe_fs — ⚠️ 已知限制

memory_sessions 路径由服务端构造(扫描 facts/summary/),风险低。memory_timeline 路径包含用户输入的 session_id,但已通过 Finding #1 的输入校验阻断了路径穿越。全面切换到 safe_fs 需要重构读取路径,留待后续 PR。

CI: fmt ✅ clippy ✅ 139 tests ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants