菜鸟AI - 让提示词生成更简单! 全站导航 全站导航
AI工具安装 新手教程 进阶教程 辅助资源 AI提示词 热点资讯 技术资讯 产业资讯 内容生成 模型技术 AI信息库

已有账号?

首页 > AI创作与模型 > Agent注意力操作系统:Context Manager新范式评测
模型技术 Manager新范式

Agent注意力操作系统:Context Manager新范式评测

2026-06-06
阅读 0
热度 0
作者 菜鸟AI编辑部
摘要

摘要

ContextManager不应仅管理消息列表,而应作为Agent的注意力操作系统,负责重建决策所需的最

Context Manager 新范式:Agent 的注意力操作系统

来,先抛几个核心判断——这一章讨论的是更底层的问题:Context Manager不能只守着管理 messages[] 那点事儿,也别等上下文塞满了临时补一段“请总结”。它更像是Agent的Attention Operating System:负责决定Agent此刻该知道什么、为什么该知道、信息源头在哪里、是否可信、有没有过期、该不该暴露给模型、该不该写入长期记忆,以及在有限预算下如何保持推理的连贯性。

一、从第一性原理定义 Context

很多Agent demo会把上下文理解成一个不断追加的messages[]:

user message
-> assistant message
-> tool result
-> assistant message
-> tool result
-> ...

这套模型跑个最小demo没问题,但撑不起一个成熟的Agent系统。

原因在于,成熟Agent的上下文远不止聊天记录。它还包含用户目标、当前任务状态、历史决策、工具调用的证据、文件和环境状态、长期记忆、压缩后的摘要、分支会话信息、预算控制、权限边界和验证结果。messages[] 只是其中一层,可靠性也往往排在最前面。

一个更稳固的定义是这样的:

Context_t = f(
  Intent,
  State,
  Evidence,
  Memory,
  Policy,
  Tools,
  Environment,
  Recent Interaction,
  Budget
)

也就是说,上下文是这些要素共同作用的结果。

这带来一个重要的思路转变:先别急着问“历史消息怎么塞进模型”,更关键的问题是——“下一步行动需要哪些信息?”

Context Manager要持续回答的问题清单很长:Agent当前目标是什么?任务完成到哪一步了?哪些事实已被验证,哪些只是猜测?哪些工具结果是关键证据?哪些用户约束不能丢?哪些旧信息已经不相关?哪些信息虽然旧,但未来仍需保留?哪些可以压缩,哪些必须原样保留?哪些能进入模型,哪些只能留在系统内部?

这个抽象框架挺耐用的。即便未来模型上下文窗口变得极大,Agent依然要处理注意力噪音、证据溯源、权限隔离、记忆污染、状态恢复、分支探索、成本预算和可解释性这些难题。

这一章的核心信息可以浓缩成一句话:

Context Manager如果只做messages[]拼接,很快会被长任务拖垮。更稳健的形态,是一套集“事件溯源 + 状态投影 + 上下文编译 + 可审计压缩”于一体的运行时系统。

换句话说:Context Manager的核心职责,是在任何给定时刻,重建Agent做出下一步决策所需的最小充分上下文。保存对话记录只是其中一个输入来源。

我们依然沿用前文的例子:一个CLI Agent正在修复测试失败。它读文件、跑测试、改代码、再验证。任务一长,context管理就从“拼prompt”变成了“管理运行时现场”。

二、先看旧模型为什么不够

最朴素的实现通常长这样:

const messages = [
  { role: "system", content: systemPrompt },
  { role: "user", content: userGoal },
];while (true) {
  const response = await model.chat({ messages, tools });
  messages.push(response.message);  if (response.toolCall) {
    const result = await runTool(response.toolCall);
    messages.push({
      role: "tool",
      content: result.text,
    });
    continue;
  }  break;
}

这段代码最大的优点是容易理解。它最大的缺点也是太容易理解了:它把所有东西都当成同一种消息。

在修测试的任务里,messages[]很快会混进这些东西:用户目标、系统规则、项目规则、模型猜测、工具调用参数、测试日志、grep输出、文件片段、旧文件内容、新文件diff、权限拒绝、压缩摘要、长期记忆、验证结果……

这些内容的性质完全不同。有些是事实,有些是猜测。有些是高优先级指令,有些是不可信的工具输出。有些已经过期,有些必须永久保留。有些应该进入模型输入,有些只该留给审计和回放。

如果系统只有一个messages[],它很难回答这些问题:哪条信息是事实源?当前state从哪里来?压缩摘要总结了哪些事件?工具输出有没有被截断?模型这一轮到底看见了什么?这个分支和主线是什么关系?恢复时应该重放副作用,还是只重建状态?

所以,messages[]当然可以保留。但问题在于,它不能承担整个context系统的事实源。

三、新范式:Context更像编译产物

错误范式是:messages就是上下文。summary可以替换历史。模型看到什么,系统就只保存什么。

这种设计早期反赌,但后期会遇到一大堆问题:无法恢复。无法审计。无法回放。无法分支。无法解释。无法知道summary丢了什么。无法区分事实、推测和用户偏好。无法做权限与隐私治理。

更稳定的分层是这样:

Raw Event Log
  append-only,尽量不可变,记录真实发生过什么
        ↓
State Projection
  从事件流投影出当前目标、计划、事实、决策、文件和工具状态
        ↓
Context Builder
  按 token budget、优先级、相关性、信任边界编译模型输入
        ↓
Model Request
  发给模型的最终上下文快照

这四层里,只有最后一层会真正发送给模型。前面三层都属于Harness的运行时控制面。

可以用一张表固定边界:

回答的问题是否事实源是否可以压缩
Raw Event Log实际发生过什么?不应该被摘要覆盖
State Projection当前现场是什么?否,是投影可以重建
Context Builder这一轮模型该看什么?否,是编译器每轮重算
Model Request模型实际收到什么?否,是快照可以保存引用

这就是新范式里最重要的一步:LLM context是运行时编译产物,系统事实源应该放在更底层。

更完整地说:

Raw Events / Raw Messages / Tool Results / Artifacts 是事实源。
State / Summary / Memory / Trace 是语义投影。
ContextBundle / Prompt 是一次性编译产物。

可以画成图中这样:

所以,发给模型的上下文只是一次临时视图。它可以被压缩、裁剪、重排、脱敏、适配不同模型协议,但不能成为系统唯一的事实来源。

一句话总结:只要接受这件事,很多设计自然会变得清晰。原始历史尽量不丢。模型上下文可以丢、可以压缩、可以重建。summary是派生物,不能当源数据用。推理链路要保存可审计证据,不依赖隐藏的chain-of-thought。会话是树形结构,不只是线性数组。Context budget处理的是资源分配,简单截断解决不了这个问题。

四、Context Manager的模块和平面

成熟一点的Context Manager不应该只有一个buildMessages()。它至少会长成下面这种模块图:

也可以从五个平面来理解:

Policy Plane
  permission / privacy / safety / budgetSemantic Plane
  state / memory / summary / traceEvidence Plane
  events / messages / artifacts / toolsCompilation Plane
  retrieve / rank / compress / redact / fitExecution Plane
  model calls / tool calls / validators

这些模块不用一开始都实现。但它们是很棒的边界清单,因为每一个模块都在回答一个不同的问题。

EventStore问:事实发生过什么?StateProjector问:现在我们处在什么任务现场?ContextBuilder问:这轮模型该看到哪些材料?Compressor问:哪些旧信息可以被摘要替代?摘要是否保留来源?TraceManager问:这次失败到底断在哪个因果节点?

如果这些问题都塞进一个messages数组里,后面就会很难改。

这部分可以直接抽成十二条长期稳定的设计原则,其中最后一条尤其重要:不要把核心数据结构绑定到某个模型厂商的message格式、tool calling格式、tokenizer或prompt模板。更稳健的做法是:

Canonical Internal Model
  ↓ adapter
Provider-specific Payload

五、稳定的 Context Ontology

一个成熟Agent的上下文系统,至少应该区分这些对象:

Session        工作容器
Event          发生过的事实
Message        用户、模型、工具之间的通信记录
State          当前任务状态投影
Stats          token、成本、延迟、压缩率等指标
Memory         可跨时间复用的知识
Artifact       大对象、文件、diff、日志、截图、工具输出
Trace          可解释的决策与行动链路
Policy         权限、安全、隐私、预算、工具边界
ContextBundle  一次模型调用的上下文编译结果

它们的职责不同,不能混在一个messages[]里。

Session:工作身份和生命周期

session管的是这次Agent工作的身份、边界和分支关系。

最小字段可以先这样设计:

type AgentSession = {
  sessionId: string;
  rootSessionId?: string;
  parentSessionId?: string;
  branchId?: string;
  leafEventId?: string;  status: "active" | "paused" | "completed" | "failed" | "archived";
  cwd?: string;
  repoRoot?: string;  modelConfig: {
    provider: string;
    model: string;
    maxOutputTokens?: number;
  };  contextConfig: {
    contextWindowTokens: number;
    reservedOutputTokens: number;
    maxRecentTokens: number;
    compressionPolicyId: string;
  };  permissionConfig: {
    sandboxMode?: "read_only" | "workspace_write" | "danger_full_access";
    approvalPolicy?: "never" | "on_request" | "on_failure" | "always";
  };  createdAt: string;
  updatedAt: string;
};

重点不在字段数量。核心是session_id不只代表“一段聊天”。它还挂住工作目录、模型配置、权限配置、context budget、branch关系和恢复位置。如果没有这些信息,所谓的resume就只能恢复一段聊天,而无法恢复一个工作现场。

Event:事实源

event是最容易被低估的对象。成熟Agent不应该只保存message log,而应该保存append-only event log。

type AgentEvent = {
  eventId: string;
  sessionId: string;
  runId?: string;
  parentEventId?: string;
  seq: number;
  timestamp: string;  type:
    | "SessionStarted"
    | "UserPromptSubmitted"
    | "ContextBuilt"
    | "ModelRequestStarted"
    | "ModelResponseReceived"
    | "ToolCallRequested"
    | "ToolCallStarted"
    | "ToolCallFinished"
    | "ToolCallFailed"
    | "ApprovalRequested"
    | "ApprovalGranted"
    | "ApprovalDenied"
    | "FileRead"
    | "FileWritten"
    | "DiffApplied"
    | "StateUpdated"
    | "CompactionStarted"
    | "CompactionCompleted"
    | "VerifierFinished";  actor: "user" | "agent" | "model" | "tool" | "system" | "subagent";
  payload: Record<string, unknown>;  causality: {
    messageId?: string;
    toolCallId?: string;
    artifactId?: string;
    compactionId?: string;
  };
};

为什么event比message更底层?因为很多关键事实根本不会出现在message里。上下文构建时选了哪些片段,也不会出现在message里。哪次工具调用被权限拒绝,不一定能在message中体现。某个文件被修改、某个diff被应用、某次验证失败、某次压缩触发——这些都应该是事件。如果它们不进event log,后面做replay、trace、eval、audit都会失去事实基础。

Message:模型交互记录

message仍然重要。但它应该采用typed message blocks,避免把provider原始格式当作内部数据结构一路透传。

type Message = {
  messageId: string;
  sessionId: string;
  runId?: string;
  role: "system" | "developer" | "user" | "assistant" | "tool" | "summary" | "custom";
  content: MessageBlock[];  toolCall?: {
    toolCallId: string;
    toolName: string;
    args: unknown;
    status: "pending" | "running" | "success" | "error";
  };  toolResult?: {
    toolCallId: string;
    outputRef?: string;
    outputPreview?: string;
    truncated: boolean;
  };  provenance: {
    source: "user" | "model" | "tool" | "memory" | "summary" | "system";
    sourceRefs?: string[];
  };  contextPolicy: {
    includeInContext: boolean;
    priority: number;
    maxTokens?: number;
  };
};type MessageBlock =
  | { type: "text"; text: string }
  | { type: "file_ref"; uri: string; summary?: string }
  | { type: "tool_call"; toolCallId: string; name: string; args: unknown }
  | { type: "tool_result"; toolCallId: string; outputRef?: string; preview?: string }
  | { type: "summary"; summaryId: string; text: string };

这里有几个硬不变量需要记住:tool call和tool result必须成对保留。不能从中间截断一个未完成的tool call。大型tool output应该进ArtifactStore,message只放preview和ref。summary message必须知道自己总结了哪些message或event。custom/internal message要区分是否进入LLM context。

State:当前现场

state是从event log投影出来的当前工作态。它不等于历史消息。

type AgentState = {
  sessionId: string;
  sourceEventId: string;
  version: number;  goal: {
    userGoal: string;
    currentTask?: string;
    acceptanceCriteria?: string[];
  };  plan: {
    steps: PlanStep[];
    currentStepId?: string;
    status: "planning" | "executing" | "blocked" | "verifying" | "done";
  };  constraints: {
    hard: string[];
    soft: string[];
    userPreferences: string[];
  };  facts: Array<{
    factId: string;
    content: string;
    confidence: number;
    sourceRefs: string[];
  }>;  decisions: Array<{
    decisionId: string;
    decision: string;
    rationaleSummary: string;
    evidenceRefs: string[];
  }>;  artifacts: Array<{
    artifactId: string;
    kind: "file" | "diff" | "command_output" | "url" | "image";
    pathOrUri: string;
    summary?: string;
  }>;  openQuestions: string[];
  lastCompactionId?: string;
};

state是压缩后仍能继续工作的核心。即使早期自然语言对话被压成summary,下面这些东西也不能含糊:用户最终目标、当前执行到哪一步、已做决定、已验证事实、已读和已改文件、未完成事项、工具失败和重试策略、用户偏好和硬约束。如果这些只藏在旧的messages里,compaction之后Agent就会失忆。一旦它们进入state projection,context就可以随时重建。

Stats:观测指标,别和State混在一起

Stats管的是运行指标,任务语义应该留给State:

type AgentStats = {
  sessionId: string;
  runId?: string;  tokenUsage: {
    inputTokens: number;
    outputTokens: number;
    cachedInputTokens?: number;
    reasoningTokens?: number;
    contextWindowTokens: number;
    contextUsedTokens: number;
    reservedOutputTokens: number;
  };  latencyMs: {
    contextBuild?: number;
    retrieval?: number;
    modelCall?: number;
    toolExecution?: number;
    endToEnd?: number;
  };  compression: {
    compactionCount: number;
    tokensBeforeLastCompaction?: number;
    tokensAfterLastCompaction?: number;
    compressionRatio?: number;
  };  toolMetrics: {
    toolCallCount: number;
    failedToolCallCount: number;
    approvalRequestedCount: number;
    approvalDeniedCount: number;
  };  qualityMetrics?: {
    verifierPassed?: boolean;
    contextMissingRisk?: "low" | "medium" | "high";
    hallucinationRisk?: "low" | "medium" | "high";
  };
};

没有stats,你很难回答:为什么某类任务总是在compaction后失败?哪个tool output最占token?哪些memory经常被召回但没用?context builder的不同策略哪个更省钱?

Memory:治理后的知识,别当缓存池

Memory回答的是哪些知识值得未来复用。“把所有summary丢进向量库”只是在堆材料,离真正的记忆治理还很远。

type Memory = {
  memoryId: string;
  scope: "user" | "project" | "workspace" | "session" | "global";
  kind: "preference" | "fact" | "instruction" | "skill" | "artifact_summary" | "decision";  content: string;
  sourceRefs: string[];
  confidence: number;  createdAt: string;
  updatedAt: string;
  expiresAt?: string;  accessPolicy: {
    sensitive: boolean;
    readableByAgents: string[];
    redactionPolicy?: string;
  };  retrieval: {
    embeddingId?: string;
    keywords?: string[];
    priority: number;
  };
};

Memory设计最怕污染。更稳定的原则是:长期记忆必须有scope、source、confidence、ttl、sensitivity、priority和usage记录。当前任务里的猜测不应该污染长期用户记忆。某个项目的测试命令也不应该污染另一个项目。

Artifact:大对象和证据实体

Artifact用来保存完整证据,prompt里只放真正需要被模型看到的摘要、引用和片段。

type Artifact = {
  artifactId: string;
  sessionId: string;  kind:
    | "file"
    | "diff"
    | "tool_output"
    | "command_log"
    | "screenshot"
    | "dataset"
    | "url_snapshot";  uri: string;
  summary?: string;
  contentHash?: string;  sourceEventId: string;
  createdAt: string;  accessPolicy?: {
    sensitive: boolean;
    allowedScopes: string[];
  };
};

正确模式是:context中放摘要、引用、关键片段。artifact store中放完整证据。这对coding agent尤其重要,文件全文、测试日志、命令输出、截图、diff都可能很大,不应该直接进入message history。

Trace:可解释链路,避开隐藏思维链

Trace回答的是Agent为什么这么做、依据是什么、验证过什么、下一步是什么。不要把“保持推理链路”理解为保存模型隐藏的chain-of-thought。工程上更稳的做法是保存一条可公开、可审计、可回放的Accountable Reasoning Trace:

Goal
Assumptions
Evidence
Decisions
Actions
Observations
Validation
Next Steps

这比保存自然语言CoT更稳、更安全,也更适合审计和恢复。

六、Context Builder:真正的模型输入编译器

有了session、event、message、state的基础,才轮到Context Builder上场。Context Builder的输入不该是一整个巨大的messages[]。它的输入是一组有来源、有优先级、有信任等级的材料:

type ContextBundle = {
  system: PromptSegment[];
  developerInstructions: PromptSegment[];
  projectInstructions: PromptSegment[];  currentRequest: {
    userMessageId: string;
    text: string;
    acceptanceCriteria?: string[];
  };  sessionState: AgentState;
  summaries: SummaryBlock[];
  recentMessages: Message[];
  retrievedMemories: RetrievedMemory[];
  retrievedArtifacts: RetrievedArtifact[];  toolContext: {
    a vailableTools: ToolSpec[];
    pendingToolPairs: Message[];
    recentToolResults: Message[];
  };  traceContext: {
    runId: string;
    recentDecisions: string[];
    evidenceRefs: string[];
  };  budget: {
    maxContextTokens: number;
    reservedOutputTokens: number;
    usedTokens: number;
  };
};

这条编译链路可以画成图中所示。然后它会按优先级裁剪:

优先级内容是否可丢
P0system / developer / safety / 必需 tool schema不可丢
P0当前用户请求不可丢
P0未完成 tool call / tool result pair不可丢
P1当前 goal、acceptance criteria、plan、open questions基本不可丢
P1当前工作文件、diff、测试结果、错误信息可摘要
P1最近 N 轮完整对话可裁剪但不能破坏 turn
P2历史决策、关键事实、用户偏好可摘要
P2检索出的 memory / artifact可裁剪
P3旧闲聊、重复解释、冗长工具输出优先丢弃或摘要

伪代码大概是这样:

async function buildContext(
  sessionId: string,
  userMessageId: string
): Promise<ContextBundle> {
  const session = await sessionStore.get(sessionId);
  const state = await stateProjector.project(sessionId);
  const latestSummary = await compressor.getLatestSummary(sessionId);  const recentMessages = await messageStore.selectRecentCoherentTurns({
    sessionId,
    maxTokens: session.contextConfig.maxRecentTokens,
    preserveToolPairs: true,
  });  const memories = await memoryStore.retrieve({
    query: state.goal.currentTask ?? state.goal.userGoal,
    session,
    state,
  });  const artifacts = await artifactStore.retrieveRelevant({
    sessionId,
    state,
  });  const bundle = assembleByPriority({
    session,
    state,
    latestSummary,
    recentMessages,
    memories,
    artifacts,
    currentUserMessage: await messageStore.get(userMessageId),
  });  return fitToBudget(bundle, {
    preserve: ["system", "current_request", "pending_tool_pairs", "state.goal"],
    evictionOrder: [
      "verbose_tool_outputs",
      "low_relevance_artifacts",
      "old_assistant_chatter",
      "old_user_messages",
      "retrieved_memories",
    ],
  });
}

这就是00-15那篇文章的位置。00-15讲的是Context Builder内部的选择、排序、压缩、隔离、预算和决策记录。本章则讲的是Context Builder上游还有事实源、状态投影、压缩账本、会话树和恢复机制。

七、Compaction:做语义蒸馏,别只缩短文本

长任务一定会遇到上下文窗口。压缩阶段不能只做“请总结上面对话”。压缩应该是:从旧上下文中提取未来行动仍然需要的信息,并保留其来源关系。

一次好的compaction至少应该产出三类东西:

1. Summary
   给模型看的压缩上下文2. State Patch
   更新 Agent 当前状态3. Memory Candidate
   候选长期记忆,经过审核后才写入 memory

不要把这三者混成一坨自然语言summary。

最危险的压缩设计是:把旧messages总结一下,然后删掉旧历史。这会把系统事实源从可检查事件变成一段有损的自然语言。

更稳的设计要分两层:

Lossless layer:
  raw messages / raw events / raw tool outputs / artifactsLossy layer:
  summaries / state snapshots / branch summaries / memory notes

也可以把它理解成一条有审计边界的语义蒸馏链:

原则很简单:原始事件尽量保留。发给模型的上下文可以压缩。summary必须有provenance。summary不能覆盖raw transcript。summary不能拆断tool pair。summary之后必须能继续执行任务。

一个合格的compaction record至少应该有这些字段:

compaction_id: cmp_123
session_id: sess_456
summarized_range:
  from_message_id: msg_001
  to_message_id: msg_120
first_kept_message_id: msg_121
tokens_before: 82000
tokens_after: 18000goal:
  user_goal: "修复测试失败,并验证。"
  current_task: "定位 serializer.test.ts 的失败断言。"constraints:
  hard:
    - "不要修改 public API。"
    - "修改后必须运行相关测试。"progress:
  done:
    - "parser 测试已经通过。"
  in_progress:
    - "serializer 测试仍然失败。"files:
  read:
    - path: "src/parser.ts"
  modified:
    - path: "src/parser.ts"
      change_summary: "修复空格 token 处理。"tools:
  important_results:
    - tool_call_id: "tool_123"
      summary: "pnpm test --filter parser 通过。"
      artifact_ref: "artifact_789"open_questions:
  - "serializer 是否需要保留加号两侧空格?"next_steps:
  - "读取 src/serializer.ts 和失败测试。"provenance:
  source_event_ids: ["event_1", "event_2"]
  source_message_ids: ["msg_001", "msg_120"]
  summary_prompt_version: "v3"

压缩之后还应该跑轻量检查:

type CompressionCheck = {
  preservesGoal: boolean;
  preservesConstraints: boolean;
  preservesCurrentPlan: boolean;
  preservesOpenToolPairs: boolean;
  preservesFileState: boolean;
  hasSourceRefs: boolean;
  estimatedInformationLoss: "low" | "medium" | "high";
};

几个硬规则可以直接写成测试:summary没有current_task,压缩失败。summary没有next_steps,压缩失败。压缩范围里有file write,但summary没有modified files,压缩失败。压缩范围里有tool error,但summary没有errors/retries,压缩失败。tool call/result pair被拆,压缩失败。

Compaction如果只追求“让上下文变短”,那是不够的。它的目标是让上下文变短之后,Agent仍然知道自己是谁、在做什么、做过什么、下一步该往哪走。

八、推理链路:保存证据,不保存隐藏 CoT

说到“保持推理链路”时,要避免一个误区:不要试图保存模型隐藏的chain-of-thought。工程上真正需要保存的是可审计的reasoning trace:

type ReasoningTrace = {
  traceId: string;
  sessionId: string;
  runId: string;  userGoal: string;  assumptions: Array<{
    assumption: string;
    sourceRefs?: string[];
    confidence: number;
  }>;  decisionLog: Array<{
    decisionId: string;
    decision: string;
    rationaleSummary: string;
    alternatives?: string[];
    evidenceRefs: string[];
  }>;  evidenceLog: Array<{
    evidenceId: string;
    kind: "tool_result" | "file" | "user_message" | "memory" | "test" | "observation";
    ref: string;
    summary: string;
  }>;  actionLog: Array<{
    actionId: string;
    actionType: "message" | "tool_call" | "file_edit" | "memory_write" | "branch" | "compact";
    eventId: string;
    resultEventId?: string;
  }>;  validationLog: Array<{
    check: string;
    result: "pass" | "fail" | "skipped";
    evidenceRefs?: string[];
  }>;
};

这样你能回答的是:Agent为什么调用这个工具?这个结论来自哪个文件?哪次压缩后丢了什么?哪个分支引入了错误?哪个用户约束被违反了?最终回答有没有验证证据?这些问题不需要隐藏CoT,它们需要的是事件、证据、决策摘要、操作记录和验证结果。

九、Branch、Memory、Retrieval 与 Subagent 都不能绕过 Context Manager

很多上下文系统后期失控,是因为外部能力绕开了Context Manager。长期记忆一旦可以直接进入prompt,就会把未验证的经验变成当前事实。检索结果一旦可以直接进入prompt,就会把相似文本变成证据。子Agent一旦可以直接把长报告塞回主上下文,就会把隔离的上下文重新污染回来。

所以这三类能力都应该按同一条路进入模型输入:

Memory / Retrieval / Subagent output
-> source refs
-> scope / trust / freshness check
-> artifact or state update
-> Context Builder
-> Model Input

Memory要有scope、confidence、TTL、source refs。Retrieval要有query plan、citation、permission boundary、audit snapshot。Subagent要返回summary、evidence refs、artifacts、risks和next steps——只返回一段“我完成了”,主线Agent很难判断这个结果能不能继续用。

所以前面的Memory Governance、Scoped Retrieval、Delegation Runtime都不只是额外专题。它们更像是Context Manager的外围血管,决定外部材料怎样进入主线工作现场。它们最终都要回到同一个问题:这些材料能不能进入本轮模型上下文?如果能,以什么优先级、什么信任等级、什么预算进入?如果不能,是否保存为artifact或audit event?

复杂任务也天然很少是线性的。Agent经常会:尝试方案A,失败,回退,尝试方案B,开subagent做研究,从旧状态fork,比较两个结果。所以session应该天然支持树结构。

推荐抽象:

type SessionNode = {
  nodeId: string;
  sessionId: string;
  parentNodeId?: string;  eventId: string;
  messageId?: string;  branchType: "main" | "fork" | "subagent" | "what_if";
  branchSummary?: string;  createdAt: string;
};

一句话:分支不能破坏会话树。

十、完整生命周期:一次请求怎样流过 Context Manager

把这些层合起来,一次请求的生命周期大概是这样:

这个流程里,模型只是其中一个节点。模型只负责其中一部分状态判断。决策记录也要落回Harness的运行时系统。Harness负责把模型的判断放回一个可恢复、可审计、可验证的工程系统里。

十一、MVP 应该先做什么

不要一开始就做完所有模块。本地CLI Agent的MVP可以先做这些:sessions、messages、events、state_snapshots、artifacts、compactions。

暂时可以不做的有:复杂向量memory、多agent协作、自动branch pruning、高级eval、跨项目长期记忆。

但MVP里最好一开始就保留几个能力:每次用户输入写入message + event。每次工具调用写入event。大型工具输出写artifact,message只放摘要和引用。每次模型调用前构建ContextBundle。超过token阈值时生成structured summary。保证tool call/result pair不被截断。支持resume。支持导出JSONL。支持/context查看当前上下文构成。支持/compact手动压缩。

这样即使系统还很小,边界也不会错。第一版ContextManager可以只有这个接口:

interface ContextManager {
  appendEvent(event: AgentEvent): Promise<void>;
  projectState(sessionId: string): Promise<AgentState>;
  buildContext(sessionId: string, input: BuildContextInput): Promise<ContextBundle>;
  compact(sessionId: string, policy?: CompressionPolicy): Promise<CompactionResult>;
  resume(sessionId: string, branchId?: string): Promise<AgentSession>;
}

复杂性藏在三个pipeline里:Event Pipeline(input / tool / model / system events -> append-only log),Projection Pipeline(events / messages / artifacts -> state snapshot),Context Pipeline(instructions + state + summary + recent messages + retrieval -> model context)。这已经足够支撑一个可演进的Harness了。

十二、关键工程不变量

这套范式最后要落到测试。下面这些不变量应该直接写进单元测试或replay verifier:

Invariant 1:
  Every tool_result must ha ve a preceding tool_call.Invariant 2:
  No pending tool_call can be removed by compaction.Invariant 3:
  Every compaction summary must include source message range.Invariant 4:
  Every file write event must be represented in state.artifacts or summary.files.modified.Invariant 5:
  Current user request, active goal, and open questions must always be included in context.Invariant 6:
  ContextBuilder output must be deterministic given same session state and retrieval result.Invariant 7:
  Raw event log is append-only.Invariant 8:
  Summary cannot overwrite raw transcript.Invariant 9:
  Branch must not mutate ancestor branch.Invariant 10:
  Memory writes require source refs.Invariant 11:
  Long-term memory must ha ve scope and expiry/update policy.Invariant 12:
  ContextBundle must be explainable: every included segment should ha ve reason and source.

这些规则比“prompt写好一点”更重要。因为它们把Agent的可靠性从模型感觉,拉回了工程约束的层面。

十三、常见反模式

反模式一:messages[] 即世界

所有历史都存在messages里。问题是难恢复、难压缩、难审计、难分支、难解释,也很难做权限控制。

反模式二:summary 覆盖历史

压缩后只保留summary,不保留原始记录。问题是无法回放、无法纠错、无法验证,也不知道summary丢了什么。

反模式三:memory 无作用域

所有记忆都进一个向量库。问题是项目污染、用户污染、临时事实长期化、旧知识误用和敏感信息扩散。

反模式四:工具结果直接塞上下文

命令输出、文件全文、日志全部塞进prompt。问题是token爆炸、噪声过多、重点丢失,安全风险也会上升。

反模式五:压缩只做自然语言总结

“请总结上面对话。”问题是目标、约束、文件状态、错误、决策和下一步都可能被压没。

反模式六:把 prompt 当 policy

只在system prompt里说不要做危险事。问题是不可验证、不可强制、不可审计、不可测试。确定性规则应该进入policy、permission、hook、validator,而不只是prompt。

来源:互联网

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

同类文章推荐

相关文章推荐

更多