s09: select_relevant_memories 的 "recent" 拼接顺序与切片不合理,不是真正的最近 2k 上下文
メンテナーはふだん 15 日以内に返信
評価
調査の方向性
s09_memory/code.py の recent_user_text から始め、特に 251-261 行を確認し、ユーザーメッセージがどのように収集され、結合され、切り出されているかを調べます。短い場合と長い場合の直近のターンで、選択した挙動を検証します。返されるコンテキストが時系列順を維持し、直近 4000 文字の末尾を含んでいれば完了です。
索引モデルが issue の本文から書いたものです。
説明
问题
s09_memory/code.py:251-261 recent_user_text returns a string claiming to be the most recent user context, but the join + slice order produces an unintuitive result.
def recent_user_text(messages: list, max_turns: int = 3) -> str:
turns = []
for message in reversed(messages):
if message.get("role") != "user":
continue
text = message_text(message).strip()
if text:
turns.append(text)
if len(turns) == max_turns:
break
return "\n".join(reversed(turns))[:4000]
Actual behavior
reversed(messages)walks backwards;turnsaccumulates up tomax_turns=3user texts inturns = [latest, prev, prev2]order.reversed(turns)then flips it back to[prev2, prev, latest].- Joined with
\n, sliced to head 4000 chars.
Consequences:
- If the latest user turn is short (e.g. "ok"), the head-4000 fills with older turns and the latest one is pushed past the slice window — the most relevant signal is dropped.
- If the latest turn is long, older turns are pushed out entirely and the function degrades to "use only the latest turn" — but that intent is not expressed in the code.
- The function is named
recent_user_textbut the implementation is "oldest of the recent few, then head-slice". Order is non-obvious.
Expected behavior
recent_user_text should mean "the last N characters of the most recent user context, in chronological order". A minimal fix:
def recent_user_text(messages: list, max_chars: int = 4000) -> str:
chunks = []
for message in messages:
if message.get("role") != "user":
continue
text = message_text(message).strip()
if text:
chunks.append(text)
joined = "\n".join(chunks)
return joined[-max_chars:] if len(joined) > max_chars else joined
Or, if preserving the per-turn walk is desired, take only the tail of the result rather than the head:
result = "\n".join(reversed(turns))
return result[-4000:] if len(result) > 4000 else result
Either is fine — the important thing is that the slice direction matches the name: "recent" means tail, not head.
中文版本
问题
s09_memory/code.py:251-261 recent_user_text 的字面意思虽然是"最近的用户文本",但 join + 切片的顺序让它产出一个反直觉的结果。
```python
def recent_user_text(messages: list, max_turns: int = 3) -> str:
turns = []
for message in reversed(messages):
if message.get("role") != "user":
continue
text = message_text(message).strip()
if text:
turns.append(text)
if len(turns) == max_turns:
break
return "\n".join(reversed(turns))[:4000]
```
实际行为
- `reversed(messages)` 从末尾向前扫描,收集最多 3 条 user 文本,此时 `turns` 顺序为 `[最新, 次新, 更旧]`。
- `reversed(turns)` 又翻回 `[更旧, 次新, 最新]`。
- 用 `\n` 拼接后取头 4000 字符。
带来的问题:
- 如果最新那条很短(比如 "ok"),头 4000 字符会被更旧的消息灌满,最新那条反而被切掉——最该被命中的信号反而丢
- 如果最新那条够长,前面的消息直接被挤出窗口,函数退化成"只用最新一条"——这个意图代码里没体现
- 函数名叫 `recent_user_text`,实现却是"最近几条里最旧的先出现 + 头切",顺序含义不明
期望行为
`recent_user_text` 应该是"按时间正序排列后,取末尾 N 个字符"。最小修复:
```python
def recent_user_text(messages: list, max_chars: int = 4000) -> str:
chunks = []
for message in messages:
if message.get("role") != "user":
continue
text = message_text(message).strip()
if text:
chunks.append(text)
joined = "\n".join(chunks)
return joined[-max_chars:] if len(joined) > max_chars else joined
```
或者保留逐条遍历的写法,但把切片方向反过来:
```python
result = "\n".join(reversed(turns))
return result[-4000:] if len(result) > 4000 else result
```
任选其一即可,关键是切片方向跟函数名一致——"recent" 是尾部,不是头部。
- 主要言語
- Python
- スター
- 79.5k
- フォーク
- 12.7k
- 平均マージ
- 11日 19時間
- マージ済み PR(30日)
- 5
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
shareAI-lab/learn-claude-code のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
shareAI-lab/learn-claude-code#583 ·
メンテナーはふだん 15 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
shareAI-lab/learn-claude-code#528 ·
メンテナーはふだん 15 日以内に返信
-
s17: IDLE teammates cannot resume their own in-progress tasks対応中かも @hichipli が 61 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
shareAI-lab/learn-claude-code#519 ·
メンテナーはふだん 15 日以内に返信
-
plan_approval_response 不会唤醒 idle teammate再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
shareAI-lab/learn-claude-code#455 ·
メンテナーはふだん 15 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 52/100
shareAI-lab/learn-claude-code#605 ·
メンテナーはふだん 15 日以内に返信
shareAI-lab/learn-claude-code の issue をすべて見る
似ている issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
BCDA-APS/polar-bits#93 ·
-
infer_audio_type only recognises one MPEG Layer III frame header, so most MP3 files are rejected対応中かも @Steve-20-ai が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
[BUG] - ssh_config `Match tagged` statement always applies対応中かも @AtharvRG が今日担当しました。 オープンBug
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
paramiko/paramiko#2715 · コメント 1 件 ·
メンテナーはふだん 9 日以内に返信
-
auto-rejected intake mcp-intake needs-salvage priority:high type:feature
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
Ikalus1988/MisakaNet#4221 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信