运行生命周期与容器编排流程
这一章从用户视角出发,解释当前代码里一次会话、一次运行和一次容器编排到底怎样流动。
1. 从用户视角看,一次会话会经历什么
用户在产品里看到的主流程可以理解为下面几步:
- 打开一个
session(会话) - 上传文件,或选择已有文件
- 选择一个
agent package - 发送一轮 prompt
- backend 为这轮 prompt 创建一个新的
run - K3s 为这个
run拉起一个独立 Pod agentcore在 Pod 里处理本轮任务并写结果- backend 把结果状态写回
run,把主回答写回session history - 用户离开页面,稍后回来继续使用同一个
session
这个流程里,“连续的是 session”,“独立的是 run”。
2. 后端进程生命周期
main(进程入口)启动后会依次完成:
- 初始化 tracing
- 兼容旧环境变量别名
- 解析
AppConfig(进程级配置) - 调
build_application(应用装配入口) - 绑定 HTTP 地址
- 用 axum 对外提供 API
- 监听退出信号并优雅关闭
如果 backend 运行在 Apply 模式,K3sOrchestrator::new(K3s 编排器初始化)还会确保:
- namespace 存在
- service account 存在
- service account 关闭自动挂载 token
2.1 启动伪代码
async fn main() {
init_tracing(); // 初始化日志
apply_legacy_env_aliases(); // 兼容旧环境变量
let config = AppConfig::parse(); // 解析进程配置
let app = build_application(config.clone()).await?; // 装配 store/catalog/orchestrator/router
let listener = TcpListener::bind(config.bind_addr).await?; // 绑定 HTTP 地址
axum::serve(listener, app.router)
.with_graceful_shutdown(shutdown_signal())
.await?;
}
3. session 的生命周期
SessionRecord(逻辑会话记录)当前只有两个状态:
ACTIVECLOSED
生命周期如下:
POST /api/v1/sessions创建,初始为ACTIVE- 可反复用于多轮
create_run POST /api/v1/sessions/{id}/close后变成CLOSEDPOST /api/v1/sessions/{id}/reopen后回到ACTIVECLOSED状态下仍可查询历史和结果
这里要特别注意:close 表示“归档并拒绝新写入”,会话数据会继续保留。
4. run 的生命周期
RunRecord(一次运行记录)常见状态包括:
PREVIEWEDSUBMITTEDPENDINGCOMPLETEDFAILEDCANCELLED
其生命周期是:
- 用户请求创建 run
- backend 准备工作区和上下文
- backend 提交或渲染 K3s 资源
- Pod 执行
- backend 刷新状态
- run 进入完成、失败或取消态
5. create_run 的完整用户视角流程
5.1 用户看到的过程
从用户角度,一次“发送消息”背后发生的是:
- backend 读取当前会话
- backend 读取本轮所选文件和挂载授权
- backend 检查集群是否还能接收新任务
- backend 把旧回答补回会话历史
- backend 把本轮用户输入追加到会话历史
- backend 创建本轮独立工作区
- backend 把上下文写成
run-context.json - backend 构造
ConfigMap、Secret、Job - K3s 启动本轮 Pod
- Pod 把本轮结果写回工作区
5.2 当前代码的伪代码
#![allow(unused)]
fn main() {
async fn create_run(command: CreateRunCommand) -> RunRecord {
let run_id = new_uuid_v7(); // 生成本轮唯一 id
let session = store.get_session(command.user_id, command.session_id).await?; // 读取会话
ensure_session_accepts_writes(session)?; // 关闭态会话拒绝新 run
let package = packages.get(command.package_name)?; // 读取 agent package
let runtime_profile = resolve_runtime_profile(command, package)?; // 解析运行时模板
let provider_env = merge_provider_env(runtime_profile, command.provider_env); // 合并 provider 配置
let artifacts = store.get_artifacts(command.user_id, command.artifact_ids).await?; // 读取输入文件
let mount_grant = resolve_mount_grant(command.mount_grant_id).await?; // 读取可选挂载授权
let workspace_mode = resolve_workspace_mode(command, package); // 决定 copy 或 mount
ensure_cluster_accepts_runs().await?; // 检查是否有可调度节点
sync_session_conversation_history(command.user_id, command.session_id).await; // 把旧 run 的回答补回 history
append_user_message(command.session_id, run_id, command.user_prompt).await?; // 写入本轮用户消息
let workspace_root = create_workspace(run_id, artifacts).await?; // 创建 /in /out /tmp
write_run_context(workspace_root, session, package, artifacts, command.user_prompt).await?; // 写上下文文件
let plan = LaunchPlan { ... }; // 汇总 package / runtime / workspace / provider / k8s 参数
let outcome = orchestrator.submit_run(plan).await?; // 渲染并提交 ConfigMap / Secret / Job
let run = build_run_record(run_id, workspace_root, outcome); // 落盘 RunRecord
store.insert_run(run).await
}
}
6. K3s 资源怎样从业务对象变成 Pod
6.1 ApplicationService(应用服务层)负责业务装配
ApplicationService::create_run 会把会话、文件、挂载、provider 环境变量和 RuntimeProfile 组装成 LaunchPlan。
6.2 job_builder 负责渲染资源定义
job_builder::build_bundle(构建运行资源包)会在内存里构造:
- 承载
agent package文件的ConfigMap - 承载 provider 环境变量的
Secret - 真正执行 run 的
Job
6.3 K3sOrchestrator 负责和集群交互
K3sOrchestrator::submit_run(提交运行)在 apply 模式下会调用:
Api<ConfigMap>::createApi<Secret>::createApi<Job>::create
这就是当前 Rust 侧真正把业务运行提交到 K8s 的代码路径。
7. Pod 内执行生命周期
7.1 容器启动后先做什么
job_builder::build_run_script(构建容器启动脚本)会生成启动脚本,脚本大致会:
- 创建
/workspace/in、/workspace/out、/workspace/tmp - 打印 run、session、user、workspace 信息
- 如有挂载授权,列出挂载目录文件
- 根据
installStrategy决定是否做运行时安装 - 执行 verify 命令
- 输出 package 和输入文件信息
- 进入具体执行模式
7.2 当前执行模式怎样理解
当前代码里的 ExecutionMode(执行模式)主要有两种:
CliVerifyOpenAiCompatibleApi
它们表达的是容器内执行入口的行为方式。从架构角度,这仍然属于 agentcore 的执行适配层。
7.3 容器内执行伪代码
容器启动
-> 读取 /workspace/in/run-context.json
-> 读取 /opt/agent/package/*
-> 读取 provider 环境变量
-> 运行 agentcore
-> 把主回答写到 /workspace/out/final-answer.md
-> 把调试信息写到 /workspace/out/*
-> 进程退出
8. backend 如何知道 run 已经结束
ApplicationService::refresh_run_status(刷新运行状态)会做两类检查。
8.1 第一类:看输出目录
它会检查:
runtime-error.txtfinal-answer.mdprovider-response.jsonllm-run-summary.jsonexecution-mode.txt
如果这些文件已经出现,就可以直接推断 run 已完成或失败。
8.2 第二类:看 K8s Job / Pod 状态
如果输出目录还看不出来,backend 会调用 K3sOrchestrator::inspect_run_status(检查运行状态),内部通过:
Api<Job>::get_optApi<Pod>::list
读取 Job 和 Pod,再映射成 RunStatus。
8.3 状态刷新伪代码
#![allow(unused)]
fn main() {
async fn refresh_run_status(run: RunRecord) -> RunRecord {
if let Some(status) = detect_output_status(run.workspace_root.join("out")).await {
update_run_status(run, status); // 输出已经落盘,直接完成状态刷新
} else if let Some(observation) = orchestrator.inspect_run_status(&run).await? {
update_run_status(run, observation); // 读取 Kubernetes Job / Pod 状态
}
if run.status == COMPLETED {
sync_run_assistant_message(&run).await?; // 把 final-answer.md 写回 session history
}
persist_run(run).await // 保存新的 run 状态
}
}
9. 同一 session 内如何保持连续性
这是当前生命周期里最关键的一部分。
9.1 文本对话连续性
文本对话连续性由下面两个动作保证:
sync_session_conversation_history(同步会话历史)
在新 run 开始前刷新同一 session 下旧 run 的状态。sync_run_assistant_message(同步助手回答)
在 run 完成后读取final-answer.md,把它回填进SessionRecord.history。
因此当前同一 session 的下一轮一定可以拿到上一轮的主回答。
9.2 文件结果连续性
run 完成后,当前代码会保留:
RunRecordworkspace_rootworkspaces/<run-id>/out/*
因此用户下次回来时,仍能查看历史结果文件。
9.3 上传文件连续性
只要 ArtifactRecord 和 uploads/ 下的文件还在,后续 run 就可以继续引用这些文件。
9.4 agentcore checkpoint 连续性
从运行时模型看,agentcore checkpoint 连续性应采用“同一 session 挂回同一状态目录”的方式。当前代码已经支持:
- 按 session 保存历史
- 按 session reopen
- 按 run 保存结果
当前代码还没有显式支持:
session级状态卷checkpoint_root元数据- 自动把上一轮 checkpoint 挂回下一轮
因此今天已经落地的是“会话历史连续”,即将补齐的是“agentcore 内部状态连续”。
10. 用户离开后再回来,会发生什么
10.1 只是关闭前端页面
如果用户只是关闭前端页面,没有关闭 session,那么后续直接拿原来的 session_id 继续创建 run 即可。
10.2 显式关闭 session
如果用户调用了 close,会话仍保留历史和结果,但会拒绝新的写入。后续调用 reopen 后,同一个 session_id 又可以继续创建新的 run。
10.3 一个月后继续同一个 session
今天这套代码已经能保证下面这些内容在一个月后仍可恢复:
- 会话历史
- 上传文件索引
- 历史 run 记录
- 历史结果文件
如果还要恢复 agentcore 内部 checkpoint,就需要把 checkpoint 写到持久化挂载目录,并在 backend 里保存该目录索引。这是当前生命周期模型下一步最自然的扩展点。
11. run 结束后,K8s 如何处理资源
当前语义如下:
- Job / Pod:由 Job TTL 或取消逻辑处理
ConfigMap/Secret:取消时会显式删除,正常完成后的统一 GC 还需补齐workspaces/:backend 主机磁盘继续保留state.json:backend 继续保留
K8s 只负责容器对象生命周期,不负责清理对话历史和业务文件索引。