跳到主要内容

runtime_context:平台注入的上下文

什么时候读:技能需要知道「谁在调、从哪来、什么时候、有什么权限、会话对象」时。 子进程型从 SKILL_CONTEXT 环境变量取,键相同但少了不可序列化的对象。

目录​

  1. 键的分组
  2. 固定上下文段(恒存在)
  3. 平台注入段
  4. 管理员代填
  5. actor_department_path 格式与匹配
  6. org_id 与伪组织兜底
  7. attachments 项结构
  8. 身份不可信输入
  9. 各条执行线的差异(对话 / 页面面板 / 试运行 / 异步后台)

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_inputstr""用户本轮原文
attachmentslist[dict][]素材池(本轮 + 历史 + 工作区生成物),见 §7
actor_namestr""姓名;无档案时回落为渠道昵称,不得用于实名核验
actor_emp_nostr""工号。唯一的工号键,没有 emp_no
actor_departmentstr""部门裸文本
actor_department_pathstr""部门全路径 /总部/销售中心/销售一部/,见 §5
actor_department_idstr""部门 ID(OrgDepartment.id,改名安全)。按 ID 判部门优先于按名字
actor_positionstr""职位
actor_titlestr""职级。审批阈值判断用此字段,不用 position
actor_phonestr""电话
actor_emailstr""邮箱
actor_tagslist[str][]员工标签
actor_keystr""发起人定位键(ChatUser.contact_key)。第三方渠道 = 渠道 openid,web 登录 = ChatUser.id,匿名/代填 = 空。这就是 send_message 收件人要存的值
channelstr""web / web_admin / 第三方渠道名
triggered_atfloat0.0秒级时间戳,不是 ISO 串
triggered_at_strstr""YYYY-MM-DD HH:MM,UTC
actor_idstr""内部主键
actor_is_adminboolFalse管理员代填标记,见 §4

call_skill 透传全部 actor_* 与 actor_id / actor_is_admin;技能自造的别名键(如 emp_no)不透传。 行级过滤依赖工号的技能,经 call_skill 路径也要验一次「非管理员能看到自己的行」。

3. 平台注入段​

key类型说明
org_idstrAgent.org_id 原值,可能为空。数据表操作遇空应拒绝;自行查档案/组织表须按 §6 兜底
role_idstr当前角色
session_idstr当前会话
user_idstr当前用户,可能为空
team_idstr所属团队,可能为空
team_rolestrowner / 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_adminbool是否管理员会话
chat_modebool对话线标记。现状:pipeline.build_runtime_ctx 对对话线/定时任务/链一律写死 True,异步后台也写 True;删除确认闸各线同样生效。页面面板线不注入
allow_sqlboolSQL 通道开关,缺省 True,目前不注入(datatable ctx.get("allow_sql", True))。设了 False 会随 call_skill 下传
workspace_dirstr由 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
dbAsyncSession / None遗留预注入连接,新技能不要用。同步入口为 None;页面线是请求级会话;异步后台是懒会话。直连库一律 db_factory 现开现还,见 spec §7A / code-facts §3.7
db_factorycallable直连库的唯一入口,恒注入。收进单一 _db(),async with db_factory() as s 现开一条短会话、用完即还,不横跨慢 I/O 持有
abort_signalasyncio.Event / None用户点停止时被 set;异步后台为 None
channel_registryobject仅对话线;异步线为空
requestobjectFastAPI Request,仅对话线
call_skillcallable仅进程内 async。规范 §4.3 表未列,来源是 §9;见 call-skill.md
providerobject / None当前角色绑定模型的 provider 对象,进程内调大模型用;子进程拿不到(走 MYINC_LLM_* 等环境变量)。_NO_LLM_SKILLS(datatable、trade-* 系)不预取,恒为 None。见 llm-call.md
skill_config.sandboxdict沙箱配置

以 _ 开头的键(_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/invokeskills._build_skill_runtime_ctx有可信会话才有team_role 需 2026-10 补丁才有;无 is_admin / chat_mode;attachments 恒 [];channel 不可依赖;详见 ui-panel.md §5
试运行 /skills/test、/chain/test同上空(无会话),两者都可显式指定 emp_nochannel = 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 补