2025 年 5 月,18 岁创始人 Arlan Rakhmetzhanov 给自家编码 Agent「Nia」拿到 85 万美元 pre-seed,核心卖点只有一句话:
真正记住整仓库,而不是在狭窄上下文里反复猜。
这句话直击当下 Coding Agent 的最大痛点 ——
- 静态规则写进 CLAUDE.md,却没人保证模型每次都看;
- 动态状态(IDE 光标、编译错误、运行时日志)靠模型自己 “再发现”,浪费 token 还易幻觉;
- 多文件改写时,左侧刚补好的类型定义右侧又重复生成,原因是两段 prompt 之间没有共享 “完整 trace”。
Nia 把「仓库级长期记忆」拆成两条管线:
- 静态结构化上下文:项目架构、风格、API 约定,在 invoke 阶段一次性注入;
- 动态状态:对话历史、本次改动 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
- 静态上下文过大 → 把「示例代码」换成「链接 + 行号」,让模型按需 @file;
- 动态 diff 包含绝对路径 → 统一用
git diff --no-prefix生成,避免 CI 与本地路径不一致; - 多 Agent 并发时忘记共享 trace → 在父线程的
StaticContext里加_trace_id,所有子节点强制追加; - 压缩摘要丢失「失败命令」→ 在摘要 prompt 里加约束:保留导致失败的命令行原文。
六、小结
Nia 的经验可浓缩成三句话:
- 把「不会变」的仓库知识提前序列化,别让模型每次重新猜;
- 把「正在变」的状态装进 LangGraph 的双寄存器,节点内随时读写;
- 给 token 数加一条 95 % 红线,超限就压缩,保证单轮 200 k 内既看得远又踩得准。
模板代码已开源在 github.com/your-org/nia-context-template,替换 .nia/ 下的三份文档即可在 10 分钟内让现有 Agent 拥有仓库级记忆。
参考资料
- AWS Prescriptive Guidance, Coding agents, 2025
- LangChain Official Docs, Context (Runtime) in LangGraph v0.6, 2025
- Cursor Docs, Working with Context, 2025