CMA:探索陌生代码库

让 Agent 快速理解不熟悉的代码

🌐 查看英文原文 | 源码 Notebook


探索:在不熟悉的代码库中建立基础认知

本 notebook 将 Agent 放入一个它从未见过的仓库,并要求它弄清楚真实的架构。文件系统是 Agent 唯一的工作空间,只有它选择读取的文件才会进入其上下文窗口,因此使用 ls、grep 和 read 进行探索是它建立心智模型的方式。

有趣的部分是我们在 fixture 中植入的一个陷阱。ARCHITECTURE.md 描述了代码不再遵循的布局,因此一个信任文档而不检查代码的 Agent 会自信地给出错误的答案。在这种情况下,基础认知意味着将你阅读的内容与实际存在的内容进行验证,而不是将文档视为权威。

除了 iterate notebook 之外,这还教会你:

  • 行动之前先探索。 一个好的 Agent 会读取足够的树结构来理解它,然后回答,而不是反过来。
  • 在 session 中途添加资源。 末尾的侧边栏展示了如何通过 sessions.resources.add 将更多文件推送到运行中的 session 中,而不是重新创建 session。当探索发现 Agent 接下来应该查看的内容时很有用。
import io
import os

from anthropic import Anthropic
from utilities import (
    make_unfamiliar_repo_zip,
    stream_until_end_turn,
    wait_for_idle_status,
)

MODEL = os.environ.get("COOKBOOK_MODEL", "claude-sonnet-4-6")

client = Anthropic()

1. 生成仓库 fixture

仓库足够小,我们用辅助函数在内存中构建它,而不是在 notebook 旁边保留一个磁盘 fixture。辅助函数植入了一个 services/ 微服务布局和一个仍然描述旧的单体架构布局的过期 ARCHITECTURE.md。

buf = make_unfamiliar_repo_zip()
fixture_zip = client.beta.files.upload(file=("repo.zip", buf, "application/zip"))
print(f"fixture: {fixture_zip.id}")

2. Agent + environment + session

agent = client.beta.agents.create(
    name="cookbook-explore",
    model=MODEL,
    system=(
        "You are onboarding to an unfamiliar codebase. Explore before "
        "answering, docs can be stale. Verify what you read against "
        "actual code structure. Write notes to /tmp/NOTES.md as you go."
    ),
    tools=[
        {
            "type": "agent_toolset_20260401",
            "default_config": {
                "enabled": True,
                "permission_policy": {"type": "always_allow"},
            },
        }
    ],
)

env = client.beta.environments.create(
    name="cookbook-explore-env",
    config={"type": "cloud", "networking": {"type": "limited"}},
)

session = client.beta.sessions.create(
    environment_id=env.id,
    agent={"type": "agent", "id": agent.id, "version": agent.version},
    resources=[{"type": "file", "file_id": fixture_zip.id, "mount_path": "repo.zip"}],
    title="Onboard to repo",
)
print(f"session: {session.id}")

3. 探索并注意过期文档陷阱

一个有根据的答案会提到真实的 services/ 布局,并将 ARCHITECTURE.md 标记为过时。一个没有根据的答案会鹦鹉学舌地重复过期文档描述的单体架构布局。

client.beta.sessions.events.send(
    session_id=session.id,
    events=[
        {
            "type": "user.message",
            "content": [
                {
                    "type": "text",
                    "text": (
                        "Unzip /mnt/session/uploads/repo.zip to /tmp/repo/. "
                        "Then: what is the actual architecture of this "
                        "codebase? Be specific about directory structure. "
                        "Check if the docs are accurate."
                    ),
                }
            ],
        }
    ],
)

print("=== exploration ===")
stream_until_end_turn(client, session.id)

4. 读取 Agent 的笔记

Agent 被告知在工作时将笔记保存在 /tmp/NOTES.md 中。打印该文件是查看它在探索过程中对代码库理解如何发展的有用方式。

client.beta.sessions.events.send(
    session_id=session.id,
    events=[
        {
            "type": "user.message",
            "content": [{"type": "text", "text": "cat /tmp/NOTES.md"}],
        }
    ],
)
stream_until_end_turn(client, session.id)

侧边栏:向运行中的 session 添加更多上下文

sessions.create 上的 resources= 参数是挂载文件最常见的方式,但 API 也暴露了一个 /v1/sessions/<id>/resources 子资源用于管理现有 session 上的挂载。这在这里很有用:如果探索发现了一个需要额外上下文的问题(配置文件、变更日志、外部 schema),你可以在不拆除 session 的情况下将其放入。

这个模式是你已经知道的上传然后附加的循环,只是分成两个调用而不是一个:

hints = b"# DEPLOY HISTORY\n2026-03-01: monolith -> microservices migration complete\n"
hints_file = client.beta.files.upload(
    file=("DEPLOY_HISTORY.md", io.BytesIO(hints), "text/markdown")
)

added = client.beta.sessions.resources.add(
    session_id=session.id,
    type="file",
    file_id=hints_file.id,
    mount_path="DEPLOY_HISTORY.md",
)
print(f"added resource {added.id} to session {session.id}")

attached = client.beta.sessions.resources.list(session_id=session.id)
print(f"{len(attached.data)} resources attached now")

client.beta.sessions.events.send(
    session_id=session.id,
    events=[
        {
            "type": "user.message",
            "content": [
                {
                    "type": "text",
                    "text": (
                        "There's a DEPLOY_HISTORY.md in your workspace now. "
                        "Read it and tell me whether it changes anything in "
                        "your earlier answer."
                    ),
                }
            ],
        }
    ],
)
print("\n--- follow-up with deploy history ---")
stream_until_end_turn(client, session.id)

# Detach the file now that the agent is done with it. `delete` here
# is the resource-detach verb, not the cookbook-wide archive.
client.beta.sessions.resources.delete(session_id=session.id, resource_id=added.id)
print("detached follow-up resource")

清理

wait_for_idle_status(client, session.id)
client.beta.sessions.archive(session.id)
client.beta.environments.archive(env.id)
client.beta.agents.archive(agent.id)
print("archived")

Tip

中文读者提示:本章节的代码和输出为英文原文。如需理解具体实现细节,可参考上方中文导读和代码注释。如有疑问,欢迎在 GitHub Issues 讨论。