runtime_context:平台注入的上下文
什么时候读:技能需要知道「谁在调、从哪来、什么时候、有什么权限、会话对象」时。 子进程型从
SKILL_CONTEXT环境变量取,键相同但少了不可序列化的对象。
目录
- 键的分组
- 固定上下文段(恒存在)
- 平台注入段
- 管理员代填
- actor_department_path 格式与匹配
- org_id 与伪组织兜底
- attachments 项结构
- 身份不可信输入
- 各条执行线的差异(对话 / 页面面板 / 试运行 / 异步后台)
1. 键的分组
runtime_context
├── 固定上下文段 —— 谁、从哪来、什么时候、说了什么(恒存在)
├── 平台注入段 —— 组织/角色/会话/权限/会话对象
└── 配置段 —— skill_configs / skill_args 及扁平副本(见 config-secrets.md)
2. 固定上下文段
契约:下列键恒存在(唯一事实源是平台的 actor_ctx.FIXED_CTX_FIELDS)。无值时给对应类型的空值,不缺键。直接判空,不要 .get(k, default)。
当前共 18 项:词表 CTX_KEYS 16 项(含十一个 actor_*)+ 词表外 actor_id actor_is_admin(actor_ctx.FIXED_CTX_FIELDS / EXTRA_KEYS),与下表逐行对应。
| key | 类型 | 空值 | 说明 |
|---|---|---|---|
raw_input | str | "" | 用户本轮原文 |
attachments | list[dict] | [] | 素材池(本轮 + 历史 + 工作区生成物),见 §7 |
actor_name | str | "" | 姓名;无档案时回落为渠道昵称,不得用于实名核验 |
actor_emp_no | str | "" | 工号。唯一的工号键,没有 emp_no |
actor_department | str | "" | 部门裸文本 |
actor_department_path | str | "" | 部门全路径 /总部/销售中心/销售一部/,见 §5 |
actor_department_id | str | "" | 部门 ID(OrgDepartment.id,改名安全)。按 ID 判部门优先于按名字 |
actor_position | str | "" | 职位 |
actor_title | str | "" | 职级。审批阈值判断用此字段,不用 position |
actor_phone | str | "" | 电话 |
actor_email | str | "" | 邮箱 |
actor_tags | list[str] | [] | 员工标签 |
actor_key | str | "" | 发起人定位键(ChatUser.contact_key)。第三方渠道 = 渠道 openid,web 登录 = ChatUser.id,匿名/代填 = 空。这就是 send_message 收件人要存的值 |
channel | str | "" | web / web_admin / 第三方渠道名 |
triggered_at | float | 0.0 | 秒级时间戳,不是 ISO 串 |
triggered_at_str | str | "" | YYYY-MM-DD HH:MM,UTC |
actor_id | str | "" | 内部主键 |
actor_is_admin | bool | False | 管理员代填标记,见 §4 |
call_skill 透传全部 actor_* 与 actor_id / actor_is_admin;技能自造的别名键(如 emp_no)不透传。
行级过滤依赖工号的技能,经 call_skill 路径也要验一次「非管理员能看到自己的行」。
3. 平台注入段
| key | 类型 | 说明 |
|---|---|---|
org_id | str | Agent.org_id 原值,可能为空。数据表操作遇空应拒绝;自行查档案/组织表须按 §6 兜底 |
role_id | str | 当前角色 |
session_id | str | 当前会话 |
user_id | str | 当前用户,可能为空 |
team_id | str | 所属团队,可能为空 |
team_role | str | owner / admin / member。对话线由 roles_chat_helpers 解析(Team.owner_id == user_id → owner,否则 TeamMember.role);call_skill 原样透传;链 / 定时任务 / 工作流在 2026-10 pipeline 补丁后按 user_id 同口径解析;页面面板 / 试运行线由 _build_skill_runtime_ctx 注入(同批补丁);异步后台没有。缺失一律按 member 处理 |
is_admin | bool | 是否管理员会话 |
chat_mode | bool | 对话线标记。现状:pipeline.build_runtime_ctx 对对话线/定时任务/链一律写死 True,异步后台也写 True;删除确认闸各线同样生效。页面面板线不注入 |
allow_sql | bool | SQL 通道开关,缺省 True,目前不注入(datatable ctx.get("allow_sql", True))。设了 False 会随 call_skill 下传 |
workspace_dir | str | 由 executor.execute 统一注入(merged_ctx.setdefault("workspace_dir", compat.workspace_dir(...)),= $AGENT_WORKSPACE_DIR/<role_id>/<session_id 或 shared>)。对话、call_skill、页面、试运行、异步后台、链都经 executor,都有。只有最终 role_id 为空时缺失——写 ctx.get 并兜底(code-facts §4)。子进程在 SKILL_CONTEXT 里也有,另有环境变量 SKILL_WORKSPACE |
db | AsyncSession / None | 遗留预注入连接,新技能不要用。同步入口为 None;页面线是请求级会话;异步后台是懒会话。直连库一律 db_factory 现开现还,见 spec §7A / code-facts §3.7 |
db_factory | callable | 直连库的唯一入口,恒注入。收进单一 _db(),async with db_factory() as s 现开一条短会话、用完即还,不横跨慢 I/O 持有 |
abort_signal | asyncio.Event / None | 用户点停止时被 set;异步后台为 None |
channel_registry | object | 仅对话线;异步线为空 |
request | object | FastAPI Request,仅对话线 |
call_skill | callable | 仅进程内 async。规范 §4.3 表未列,来源是 §9;见 call-skill.md |
provider | object / None | 当前角色绑定模型的 provider 对象,进程内调大模型用;子进程拿不到(走 MYINC_LLM_* 等环境变量)。_NO_LLM_SKILLS(datatable、trade-* 系)不预取,恒为 None。见 llm-call.md |
skill_config.sandbox | dict | 沙箱配置 |
以 _ 开头的键(_meta 等)为平台内部字段,不注入子进程,技能不得依赖(_meta.skill_name 仅可用于日志)。
_llm_conn(带 key 的 LLM 中间 dict)同为 _ 开头内部字段,仅 executor 内部用来生成 LLM 环境变量,技能不得读。
external_id 不在 runtime_context 里。 它是 SkillRunContext 的内部字段,build_runtime_ctx 不会把它放进 rc。渠道地址一律用 channel + actor_key(见 call-skill.md §9.2),不要指望读 external_id。
平台实际注入的完整键集合见 code-facts.md §3.1。
4. 管理员代填
actor_is_admin=True 时,平台在注入前清空身份键,防止按管理员本人的部门/职级判断申请人:
清空:actor_title, actor_department, actor_department_id, actor_department_path, actor_position,
actor_tags, actor_key, actor_id, actor_emp_no (actor_ctx.ADMIN_SKIP_ACTOR_KEYS)
保留:actor_name, actor_phone, actor_email(审计「谁填的」)
凡用 actor_title / actor_department 做判断的分支,必须显式处理空值,不得把空值当最低档位放行:
if ctx.get("actor_is_admin") or not ctx["actor_title"]:
return {"success": False, "need_param": "actor_title", "error_type": "recoverable",
"error": "actor_title 为空(管理员代填或档案缺失)",
"user_message": "取不到申请人职级,请补充申请人工号后重试。"}
5. actor_department_path
/根部门/…/上级部门/本级部门/ 例:/总部/销售中心/销售一部/
| 项 | 定义 |
|---|---|
| 分隔符 | /,首尾也带 |
| 顺序 | 顶层在前,本级在末 |
| 含本级 | 含。末段即 actor_department |
| 含组织根 | 不含组织名。首段是组织树顶层部门 |
| 空值 | "":无部门、无 org_id、或解析失败 |
| 部门不在组织树 | 退化成 /<部门名>/,与「本就只有一级」不可区分 |
| 停用部门 | 保留在路径中 |
| 深度上限 | 20 层,超出从顶端截断 |
| 代填时 | 被清空,部门类判断一律走不到 |
匹配必须带首尾斜杠:
if "/销售中心/" in ctx["actor_department_path"]: # 正确
if "销售中心" in ctx["actor_department_path"]: # 错误:误命中「销售中心部」
判本级用末段:
segs = [x for x in ctx["actor_department_path"].split("/") if x]
current = segs[-1] if segs else ""
待核对:工作流线的该字段由 bridge 独立注入,与对话线是否同源未确认。同一规则两条线结果可能不同。
6. org_id 与伪组织兜底
org_id 是原值,可能空串。但平台身份/组织解析在其为空时兜底成伪 org r_<role_id>,
ChatUser / OrgDepartment 的行就是按那个值存的。技能自行按 org 查档案表/组织表必须用同一兜底:
org_id = (ctx.get("org_id") or "").strip() or f"r_{ctx.get('role_id', '')}"
数据表操作不适用本兜底:org_id 为空即配置异常,直接拒绝,返回 error_type=config + non_retriable=true。不得用伪 org 拼新 schema。
7. attachments 项结构
固定上下文段由 actor_ctx.build_ctx 产出,attachments 经 norm_attachments 归一,每项只有这五个键:
{"type": "image", # 新技能只读此字段(image / video / doc / audio / archive)
"att_type": "image", # 过渡期别名,与 type 同值,两版本后移除
"filename": "报销单.pdf",
"local_path": "/abs/...", # 可能为空
"url": "https://..."} # 可能为空
- 清单包含本轮上传、历史上传(最近 10 个)、以及工作区顶层的生成物(最近 10 个,仅图片/视频/音频)。
对话层原始清单里的
source/turn/mime_type等字段在归一时被丢弃,技能拿不到。 - 页面面板 / 试运行线不传附件,
attachments恒为[]。
8. 身份不可信输入
工号、角色、组织、团队角色一律取 runtime_context。input_data 中的同名字段视为伪造,忽略。
(页面网关会主动剥掉 input 里的 emp_no actor_emp_no actor_id is_admin actor_is_admin team_role actor_title actor_department actor_department_path org_id;
但 /options-query 会把服务端算出的 org_id 塞进 input——技能仍只认 ctx。)
9. 各条执行线的差异
| 线 | 组装处 | 身份(actor_*) | 差异要点 |
|---|---|---|---|
| 对话线 | roles_chat_helpers → SkillRunContext → pipeline.build_runtime_ctx | 有 | 键最全;team_role 在此解析 |
页面面板 /skills/invoke | skills._build_skill_runtime_ctx | 有可信会话才有 | team_role 需 2026-10 补丁才有;无 is_admin / chat_mode;attachments 恒 [];channel 不可依赖;详见 ui-panel.md §5 |
试运行 /skills/test、/chain/test | 同上 | 空(无会话),两者都可显式指定 emp_no | channel = web_admin |
下拉选项 /skills/options-query | 同上 | 空 | 只允许 schema 里 x-options-from / x-keys-from 声明过的 call |
异步后台(async: true,非 web 渠道) | roles_chat_helpers._run_async_skill | 用发起时的固定上下文 | 无 team_role / request / channel_registry;user_id / team_id 为空;abort_signal=None;db 懒会话 + db_factory;配置已预取平铺;workspace_dir / provider 由 executor 补 |