Chapter 13 · Capstone 2: read-only codebase audit assistant
Fuse chapters 3, 5, and 6 into a real scenario: the agent performs a read-only audit of a temporary repository. AGENTS.md carries repo rules, the audit skill carries the checklist, and FilesystemBackend(virtual_mode=True) confines file tools to root_dir. A key-free structural check runs first; the model step follows.
Mechanics and design choices
Memory loads AGENTS.md at startup; skills load fully on demand, so the audit skill only enters context when relevant. The tools_by_name check guarantees all seven default file tools are visible. Read-only here is a system-prompt convention, not a tool-layer enforcement; hard enforcement comes from chapter 3 permissions and the middleware allowlist.
Example
import os, pathlib, tempfile, shutil
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend
root = tempfile.mkdtemp(prefix="deepagents-audit-")
try:
repo = pathlib.Path(root) / "repo"
repo.mkdir()
(repo / "AGENTS.md").write_text("# Repo rules\n- All CLI writes must be idempotent\n- Tests live in tests/\n")
(repo / "main.py").write_text("import os\nos.system('rm -rf build') # risky shell use\n")
skill = pathlib.Path(root) / "skills" / "audit"
skill.mkdir(parents=True)
(skill / "SKILL.md").write_text(
"---\nname: audit\ndescription: Audit a codebase for risky shell and missing tests\n---\n"
"Flag: (1) os.system/subprocess without validation, (2) missing tests for public functions.\n"
)
model = ChatOpenAI(
model=os.environ["MODEL_ID"],
api_key=os.environ["DEEPAGENT_API_KEY"],
base_url=os.environ.get("DEEPAGENT_BASE_URL", "https://api.openai.com/v1"),
)
agent = create_deep_agent(
model=model,
backend=FilesystemBackend(root_dir=str(repo), virtual_mode=True),
memory=[str(repo / "AGENTS.md")],
skills=[str(skill.parent)],
system_prompt="Audit this repository. Inspect files with read_file/ls/grep; do not modify files.",
)
# Structural self-check (no model call needed):
available = sorted(agent.nodes["tools"].bound.tools_by_name)
for required in ["ls", "read_file", "write_file", "edit_file", "delete", "glob", "grep"]:
assert required in available, (required, available)
print("file tools visible to the model:", available)
# Model step (skipped when no key is configured):
if os.environ.get("DEEPAGENT_API_KEY") not in ("", "sk-fake"):
result = agent.invoke(
{"messages": [{"role": "user", "content": "Run the audit skill on this repo and report findings."}]},
{"configurable": {"thread_id": "ch13"}},
)
print(result["messages"][-1].content)
else:
print("model step skipped: DEEPAGENT_API_KEY not configured; structural check passed.")
finally:
shutil.rmtree(root, ignore_errors=True)Run and verify
Run without a key: it prints the file tool list and model step skipped, and the structural assertions pass. With a key: the final message should flag the os.system risk in main.py and the missing tests, and it should not modify any repository file (the temp directory is cleaned up in finally).
Pitfalls
A system-prompt read-only rule can be violated by the model; audit tasks should restrict write tools at the code layer or deny write paths. A temp directory is isolation, not a permission boundary; production repositories belong on StoreBackend or a remote sandbox, not a local temp dir.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. When does memory load AGENTS.md?
2. How do skills enter context?
3. What does the tools_by_name check prove?
4. How is read-only enforced?
Hands-on exercises
- Swap the audit skill for your own project standards.
- Use chapter 3 permission rules to truly forbid writing .env.
- Add a test for main.py and observe the second audit.
Official references
Verified on 2026-09-17; examples were run locally without network access.