会话与上下文

sagents/session_runtime.pysagents/context/ 一起承担“状态层”。智能体本身是无状态的处理器,所有状态都集中在这里。

1. 会话生命周期:SessionSessionManager

flowchart LR
    SAgent --> SM[SessionManager · 进程级单例]
    SM --> Map[(session_id → Session 映射)]
    SM --> S1[Session]
    SM --> S2[Session]
    SM --> S3[...]
    S1 --> Ctx1[SessionContext]
    S2 --> Ctx2[SessionContext]
    S1 --> Lock1[lock_manager 跨会话锁]
    SM --> CV[_session_id_var · ContextVar<br/>任意位置拿当前 session_id]

SessionManager 提供:

  • get_or_create(session_id, sandbox_type)
  • save_session / interrupt_session / close_session
  • get_live_session(session_id):跨模块拿到正在跑的 Session 引用
  • lock_manager 协作,保证同一 session 不被并发跑两次

会话状态机

stateDiagram-v2
    [*] --> IDLE
    IDLE --> RUNNING: run_stream_safe 加锁后启动
    RUNNING --> COMPLETED: 流程正常结束
    RUNNING --> INTERRUPTED: 用户/系统主动中断
    RUNNING --> ERROR: 异常
    COMPLETED --> [*]
    INTERRUPTED --> [*]
    ERROR --> [*]

FlowExecutor 在每个节点前后都检查 Session 状态,进入 INTERRUPTED / ERROR 时立即退出循环,避免“跑一半还在烧 token”。

2. SessionContext:黑板

flowchart TB
    subgraph SessionContext
        ID[身份<br/>session_id · user_id · agent_id · parent_session_id]
        Sys[system_context · 注入到 Prompt 的额外信息]
        WS[工作空间<br/>session_root_space · sandbox_agent_workspace · volume_mounts]
        SBX[sandbox · ISandboxHandle]
        TM[tool_manager]
        SM[skill_manager + sandbox_skill_manager]
        ST[status · audit_status]
        MM[MessageManager]
        SMM[SessionMemoryManager]
        UMM[UserMemoryManager]
        WF[WorkflowManager]
        CB[ContextBudgetManager]
    end

    Agents -->|读写| SessionContext

它是一个黑板(Blackboard):多个 Agent 通过读写它来协同,但互相之间没有直接依赖。

显式两阶段初始化

sequenceDiagram
    participant Sess as Session
    participant Ctx as SessionContext
    participant Sandbox
    participant SkillProj as SandboxSkillManager

    Sess->>Ctx: __init__(session_id, user_id, ...)
    Note right of Ctx: 仅占位,sandbox=None
    Sess->>Ctx: configure_runtime(model, sandbox cfg, agent_id)
    Sess->>Ctx: await init_more()
    Ctx->>Sandbox: SandboxProviderFactory.create(...)
    Sandbox-->>Ctx: ISandboxHandle
    Ctx->>SkillProj: 把可见技能投影进沙箱
    Ctx-->>Sess: 就绪

init_more() 把耗时资源(沙箱启动、技能投影)从构造函数里挪出来,避免“半初始化”的上下文被误用。

3. 消息:MessageChunk + MessageManager

3.1 MessageChunk 单元

flowchart LR
    Chunk[MessageChunk] --> ID[message_id · session_id]
    Chunk --> Role[role · user/assistant/tool/system]
    Chunk --> Type[message_type · text/tool_call/tool_result/token_usage/...]
    Chunk --> Content[content · tool_calls · tool_call_id]
    Chunk --> Meta[时序与扩展元数据]

整个运行时的流式输出都是 List[MessageChunk]

3.2 MessageManager:消息历史的真理之源

flowchart TB
    Inputs[chunk 流入] --> MM[MessageManager]
    MM --> Merge[合并同 message_id 的多段]
    MM --> Filter[过滤 system 消息<br/>system 由 Agent 单独拼]
    MM --> Compress[大模型持久摘要<br/>与覆盖锚点]
    MM --> Estimate[完整请求 token 投影<br/>Provider usage 动态校准]
    MM --> Out1[Agent 构造完整 Provider 请求]
    MM --> Out2[流式输出给上游]
    MM --> CB[ContextBudgetManager<br/>辅助 Agent 临时 prompt 限长]

要点:

  • system 消息不进历史,由 Agent 在调用时单独拼。
  • 主会话历史不再按规则截断,也不再创建新 artifact。完整输入超过窗口的 85% 时,只用大模型生成的持久摘要替换旧历史;Provider 真实上下文超限时直接进入同一摘要恢复链。
  • 最新 user 与近期活跃消息受保护,assistant 工具调用必须与所有对应 tool result 成对保留。多次摘要通过覆盖锚点串联,推理视图只保留最新有效摘要。
  • 完整请求 token 投影只是主动触发信号,并使用 Provider prompt usage 动态校准;不扣除最大输出 token。Provider 拒绝始终是权威结果。
  • 辅助 Agent 仍可构造临时的预算限长 prompt 视图,但不会回写主会话 ledger。

3.3 持久摘要的输出预算与失败语义

  • 压缩请求继承当前模型配置的 max_tokens;o1/o3/o4/GPT-5 等需要 max_completion_tokens 的模型由统一请求兼容层完成映射。预算阶段先解析唯一的 provider-facing 输出字段:其他模型显式配置 max_completion_tokens 时同样计入预算,同时配置两个互相冲突的输出字段则直接失败。压缩工具不设置独立生成上限,也不会在配置缺失时臆造一个上限。压缩工具同时记录兼容层最终成功请求实际采用的输出字段,并要求字段和值与预算配置完全一致;若兼容回退移除了已配置的硬上限,整次压缩失败。
  • 七字段语义摘要的窗口目标为 clamp(floor(max_model_len × 8%), 4000, 8192);模型已配置输出上限时,再收口到该上限的 80%,为 JSON 闭合保留余量。Prompt 和服务端结构化裁剪使用同一个目标。
  • 单批历史的名义上限仍为模型窗口的 35%,但实际请求还会计入压缩 Prompt、上一批滚动摘要和模型配置的输出预留;完整请求必须留在窗口的安全区内。完整 user turn 始终作为来源原子单元,超限时只拆分其临时压缩文本;安全预算触发再次拆分时使用稳定的层级 lineage(如 1/3 > 2/2),不改写 ledger 或 coverage。
  • 压缩流保留 finish_reason 和 provider usage,并在正常完成、校验失败或流式迭代异常后显式关闭。length 或明显未闭合 JSON(包括未闭合对象、数组和 fenced JSON)会以 75% 目标重试一次;显式长度截断会在解析与裁剪前进入重试,且正常、重试两种 Prompt 都必须预先通过窗口预算检查。再次截断则整次压缩失败。除 stopend_turneoscomplete/completed 和缺失原因外,content_filtersafetyblockederrorcancelled 等非正常结束同样直接失败,拒绝文本不得包装成摘要;原历史继续保留且不生成有效 anchor。
  • Provider usage 总量只有在每次压缩请求都返回对应 usage 字段时才统计;部分请求缺失时总量记为 null,并用 observed count 标明实际覆盖请求数,避免把不完整采样误报为总量。
  • 正常结束且无法按 JSON 解析的文本仍可作为 fallback_text 兼容旧 provider,但合法 JSON 若不是 object 则属于 invalid_json_schema,不能降级为文本摘要。结构化结果必须包含非空字符串 summary。裁剪优先保留用户硬性要求、未完成任务和最高优先级关键错误;持久化前永不直接截断 JSON 字符串。
  • compressed_tokenscompression_ratiosummary_characters 使用同一个可复算的非自引用口径:序列化时保留 compression_metrics_basis 声明,但暂时移除这三个会改变自身长度的统计字段,再统一计算三项指标,避免浮点或位数变化造成循环定义。

4. 记忆:会话级 + 用户级

flowchart LR
    subgraph 短期
        SMM[SessionMemoryManager<br/>会话生命周期内有效]
    end

    subgraph 长期
        UMM[UserMemoryManager · 以 user_id 索引]
        UMM --> Iface[IMemoryDriver 抽象]
        Iface --> Drv[ToolMemoryDriver / 其他]
        UMM --> Ext[Extractor · 用 LLM 抽取持久条目]
        UMM --> Path[(MEMORY_ROOT_PATH<br/>或 workspace/user_memory)]
    end

    Recall[memory_recall Agent] -->|召回| SMM
    Recall -->|召回| UMM
    Tool[memory_tool] -->|读写| UMM
    Obs -. 事件 .-> UMM

记忆系统对 Agent 是“可有可无”的能力,由 MemoryRecallAgent 等显式调用,不强制塞进每一次会话。

5. Workflow:context/workflows/

flowchart LR
    AvailWF[run_stream 入参 available_workflows] --> WM[WorkflowManager]
    WM --> WfList[(已注册 Workflow 模板)]
    WM -->|当前可用步骤| Agent

Workflow 是“可重用任务模板”——一段预定义的步骤说明。注意它和 flow/ 名字相近但定位完全不同:

  • flow/:跑哪些 Agent(编排)
  • workflows/:某一类业务任务的固定操作步骤说明(业务模板)

6. 一次会话中状态如何演进

sequenceDiagram
    participant Caller
    participant SAgent
    participant SM as SessionManager
    participant Sess as Session
    participant Ctx as SessionContext
    participant MM as MessageManager
    participant Mem as Memory Managers

    Caller->>SAgent: run_stream(messages, ...)
    SAgent->>SM: get_or_create(session_id)
    SM-->>SAgent: Session
    SAgent->>Sess: configure_runtime(...)
    Sess->>Ctx: 构造或复用 SessionContext
    Ctx->>Ctx: init_more() 初始化沙箱/技能投影
    Sess->>Ctx: 写入 audit_status (agent_mode, deep_thinking ...)
    Sess->>MM: 写入输入 messages
    loop FlowExecutor 每步
        Sess->>MM: 读取符合预算的历史
        Sess->>Mem: 召回会话/用户记忆
        Sess-->>Caller: yield 流式 chunk
        Sess->>MM: 增量写入 assistant/tool chunk
    end
    SAgent->>SM: close_session(session_id)
    SM->>Mem: 触发用户记忆抽取(按需)

7. 与外部存储的关系

flowchart LR
    Ctx[SessionContext · 纯运行时内存] --> AppLayer[应用层]
    AppLayer --> Server[(app/server: 多租户 DB / 对象存储)]
    AppLayer --> Desktop[(app/desktop: 本地 SQLite / 用户目录)]
    AppLayer --> CLI[(app/cli / examples: 文件或纯内存)]

SessionContext 自身不直接绑定任何具体数据库。“运行时纯内存 + 应用层负责持久化”,正是 sagents 能被多种应用形态复用的关键之一。


Sage documentation for the current repository layout. Source available under the MIT license.

This site uses Just the Docs, a documentation theme for Jekyll.