Skip to slide
Chapter 9 · Memory Outside the Weights
60 / 142

CHAPTER 09 · Memory Outside the Weights · 3 / 5

Code MVP: a memory loader

"""
chapter 09: memory outside the weights.
Load layered project memory (AGENTS.md / CLAUDE.md up the tree, plus
path-scoped rules) into the project_instructions slot of Chapter 3's
PromptBuilder. 'More specific wins': files closer to cwd come last.
"""
import os

MAX_BYTES = 32 * 1024   # Codex's default AGENTS.md size cap

class MemoryLoader:
    def __init__(self, project_root: str, instruction_names=("AGENTS.md", "CLAUDE.md")):
        self.root = os.path.abspath(project_root)
        self.names = instruction_names

    def _dirs_from_root_to(self, cwd: str) -> list:
        """Every directory from project root down to cwd, root first."""
        cwd = os.path.abspath(cwd)
        parts, cur, chain = os.path.relpath(cwd, self.root).split(os.sep), self.root, [self.root]
        for p in parts:
            if p in (".", ""):
                continue
            cur = os.path.join(cur, p)
            chain.append(cur)
        return chain

    def load_instructions(self, cwd: str = None) -> str:
        cwd = cwd or self.root
        chunks, budget = [], MAX_BYTES
        # General first (root), specific last (cwd): later overrides earlier.
        for d in self._dirs_from_root_to(cwd):
            for name in self.names:
                path = os.path.join(d, name)
                if os.path.exists(path):
                    text = open(path, encoding="utf-8").read()[:budget]
                    budget -= len(text)
                    rel = os.path.relpath(path, self.root)
                    chunks.append(f"# from {rel}\n{text}")
        return "\n\n".join(chunks)

    def load_rules(self, touched_paths: list) -> str:
        """Path-scoped rules: include a rule only if a touched file matches."""
        rules_dir = os.path.join(self.root, ".rules")
        if not os.path.isdir(rules_dir):
            return ""
        active = []
        for fn in sorted(os.listdir(rules_dir)):
            # Convention: a rule file named 'backend.md' applies to backend/*.
            scope = fn.rsplit(".", 1)[0]
            if any(scope in p for p in touched_paths):
                active.append(open(os.path.join(rules_dir, fn), encoding="utf-8").read())
        return "\n\n".join(active)

if __name__ == "__main__":
    # Build a tiny demo project tree.
    os.makedirs("/tmp/demo_proj/backend", exist_ok=True)
    open("/tmp/demo_proj/AGENTS.md", "w").write("Run tests with `pytest -q`.")
    open("/tmp/demo_proj/backend/AGENTS.md", "w").write("Backend uses async SQLAlchemy.")
    os.makedirs("/tmp/demo_proj/.rules", exist_ok=True)
    open("/tmp/demo_proj/.rules/backend.md", "w").write("Never log raw SQL with secrets.")

    loader = MemoryLoader("/tmp/demo_proj")
    print("--- instructions for backend/ ---")
    print(loader.load_instructions("/tmp/demo_proj/backend"))
    print("--- active rules when editing backend/api.py ---")
    print(loader.load_rules(["backend/api.py"]))

Run it and you will see the root instruction and the backend-specific instruction concatenated (general first, specific last), plus the backend rule activated only because a backend file was touched. Feed load_instructions() into PromptBuilder(project_instructions=...) and your agent now knows your project.

← → arrow keys work too