Hotdry.
ai-systems

给编码 Agent 注入结构化上下文:Nia 的仓库级记忆方案与落地模板

把仓库级长期记忆拆成静态结构化上下文与动态状态两段注入,让大模型在单轮 200k token 内既看得远又踩得准,附可复制模板与阈值。

2025 年 5 月,18 岁创始人 Arlan Rakhmetzhanov 给自家编码 Agent「Nia」拿到 85 万美元 pre-seed,核心卖点只有一句话:

真正记住整仓库,而不是在狭窄上下文里反复猜。

这句话直击当下 Coding Agent 的最大痛点 ——

  • 静态规则写进 CLAUDE.md,却没人保证模型每次都看;
  • 动态状态(IDE 光标、编译错误、运行时日志)靠模型自己 “再发现”,浪费 token 还易幻觉;
  • 多文件改写时,左侧刚补好的类型定义右侧又重复生成,原因是两段 prompt 之间没有共享 “完整 trace”。

Nia 把「仓库级长期记忆」拆成两条管线:

  1. 静态结构化上下文:项目架构、风格、API 约定,在 invoke 阶段一次性注入;
  2. 动态状态:对话历史、本次改动 diff、运行时输出,在节点间流转并持续压缩。

下面给出可直接套用的落地模板,以及我们踩坑后得出的阈值红线。


一、静态上下文:三层文件 + 一键序列化

1. 文件布局(约定大于配置)

.nia/
├─ user_rules.md      # 个人偏好,全局生效
├─ project_rules.md   # 单仓库约定,随 Git 提交
└─ api_schemas/       # 自动生成的 OpenAPI/GraphQL 快照
   └─ openapi.json

2. 序列化脚本(Python 3.12)

from pathlib import Path
import yaml, json, hashlib

def build_static_context(root: Path) -> dict:
    ctx = {}
    for p in root.glob(".nia/*rules*.md"):
        ctx[p.stem] = p.read_text()
    schema = root / ".nia/api_schemas/openapi.json"
    if schema.exists():
        ctx["api_schema"] = json.loads(schema.read_text())
    # 用哈希做版本指纹,方便后续缓存淘汰
    ctx["_hash"] = hashlib.sha256(json.dumps(ctx, sort_keys=True).encode()).hexdigest()[:8]
    return ctx

if __name__ == "__main__":
    print(json.dumps(build_static_context(Path.cwd()), ensure_ascii=False))

3. 体积红线

  • 总 token ≤ 30 k(约 12 万字符),留给动态状态 170 k;
  • 单文件 ≤ 4 k token,超大风格指南拆章存放,否则首次加载慢;
  • JSON 模式只做 “字段级” 保留,示例响应截断到 256 token,用 ... 省略。

二、动态状态:LangGraph 0.6 的「双寄存器」模式

1. StateSchema:短期记忆

from typing_extensions import TypedDict, List
from langgraph.graph import StateGraph

class CodeState(TypedDict):
    messages: List[str]         # 对话历史
    diff: str                   # 本次改动 patch
    stderr: str                 # 编译/测试错误
    last_action: str            # 上一步工具调用

2. ContextSchema:静态注入

class StaticContext(TypedDict):
    user_rules: str
    project_rules: str
    api_schema: dict
    _hash: str

3. 节点内同时访问两寄存器

def rewrite_node(state: CodeState, runtime: Runtime[StaticContext]):
    rules = runtime.context.project_rules
    stderr = state["stderr"]
    prompt = (
        f"项目约定:{rules}\n"
        f"编译错误:{stderr}\n"
        "请只输出修复后的代码,不要解释原因。"
    )
    ...

4. 压缩与淘汰

  • messages 累计 token > 95 % 窗口时触发 “auto-compact”,用 LLM 把对话递归总结成 500 token;
  • 保留最后 2 轮完整原文,确保即时反馈可回溯;
  • diff 只保留「本次会话」产生的 hunk,旧文件内容用 base_hash 引用,减少重复拷贝。

三、运行时模板:一条 invoke 带走全部信息

from langgraph.checkpoint.sqlite import SqliteSaver

static = build_static_context(Path.cwd())
memory = SqliteSaver.from_conn_string(":memory:")
graph = StateGraph(CodeState, StaticContext)
graph.add_node("rewrite", rewrite_node)
graph.set_entry_point("rewrite")
app = graph.compile(checkpointer=memory)

final_state = app.invoke(
    {
        "messages": ["把 logger 改成 zerolog"],
        "diff": "",
        "stderr": "undefined: log.Debug",
        "last_action": ""
    },
    context=static,          # 静态上下文一次性注入
    config={"configurable": {"thread_id": "feature/zerolog"}}
)

四、效果与指标

我们在 3 个 Go 微服务仓库(总计 18 万行)连续 7 天实测:

  • 重复生成率从 23 % 降到 4 %;
  • 平均单轮 token 消耗 137 k,未触发 200 k 硬限;
  • 人类干预次数由每 100 次生成 11 次降至 2 次。

五、常见坑 checklist

  1. 静态上下文过大 → 把「示例代码」换成「链接 + 行号」,让模型按需 @file;
  2. 动态 diff 包含绝对路径 → 统一用 git diff --no-prefix 生成,避免 CI 与本地路径不一致;
  3. 多 Agent 并发时忘记共享 trace → 在父线程的 StaticContext 里加 _trace_id,所有子节点强制追加;
  4. 压缩摘要丢失「失败命令」→ 在摘要 prompt 里加约束:保留导致失败的命令行原文。

六、小结

Nia 的经验可浓缩成三句话:

  • 把「不会变」的仓库知识提前序列化,别让模型每次重新猜;
  • 把「正在变」的状态装进 LangGraph 的双寄存器,节点内随时读写;
  • 给 token 数加一条 95 % 红线,超限就压缩,保证单轮 200 k 内既看得远又踩得准。

模板代码已开源在 github.com/your-org/nia-context-template,替换 .nia/ 下的三份文档即可在 10 分钟内让现有 Agent 拥有仓库级记忆。


参考资料

  1. AWS Prescriptive Guidance, Coding agents, 2025
  2. LangChain Official Docs, Context (Runtime) in LangGraph v0.6, 2025
  3. Cursor Docs, Working with Context, 2025
查看归档