align: 修正技能播种路径:codex-home/skills/ 才是用户技能目录

2026-10-30 主机实测(临时 codex-home,生产未动)纠正:
先播 .agents/skills/ 时 skills/list 只返回 5 个系统技能,
3 个自有技能一个没出现;改播 skills/ 后返回 8 个,
3 个自有技能以 scope=user 出现。系统技能本身就在
skills/.system/ 下。
- skill_seed DEST_SUBDIR 由 .agents/skills 改为 skills,
  模块文档与 worker 注释同步修正。
- 测试期望路径同步改为 skills/。
- 对照表补实测结论与附带发现:Codex 自带系统技能
  imagegen,图片生成在 Codex 本体里不是无技能。

验证:skill_seed + chatgpt skills + websearch worker
共 14 项全过。主机探针文件已全部清理,生产未动。
This commit is contained in:
host committed 2026-10-30 01:16:49 +08:00
1 parent cf9d17f035
commit 378cddfb1e
4 files changed
+50 -39

No files matched your search

+29 -22
View File
@@ -4,16 +4,22 @@ Mirrors the websearch MCP seeding pattern in cloud/websearch.py,
but for skills, not MCP config: but for skills, not MCP config:
- Source is cloud/skills/<name>/SKILL.md (the batch-1 skill pack). - Source is cloud/skills/<name>/SKILL.md (the batch-1 skill pack).
- Destination is <codex-home>/.agents/skills/<name>/SKILL.md. - Destination is <codex-home>/skills/<name>/SKILL.md.
Why this path: Codex documents personal skills at Why this path: verified live on 2026-10-30 with the real
$HOME/.agents/skills and project skills at .agents/skills Codex binary (host.py skills-list against a temp codex-home
(scanned from the working directory up to the repo root). on the production host, production untouched). The first
A Docker tenant task must not pollute its task repo/worktree attempt -- <codex-home>/.agents/skills/ -- was NOT discovered
(seeding into the worktree would show up in the task diff), (skills/list returned only the 5 system skills). Copying the
so the tenant codex-home -- the per-project persistent home same SKILL.md files to <codex-home>/skills/ made them appear
that is mounted into the container at as scope=user (5 system + 3 ours, count 8). System skills
/var/lib/codex-app-server/codex-home -- is the personal-level themselves live at <codex-home>/skills/.system/, which is
analogue. This is the same channel websearch MCP seeding uses. the tell that this is the user-skills directory for a
CODEX_HOME. A Docker tenant task must not pollute its task
repo/worktree (seeding into the worktree would show up in
the task diff), so the tenant codex-home -- the per-project
persistent home that is mounted into the container at
/var/lib/codex-app-server/codex-home -- is the right channel.
This is the same channel websearch MCP seeding uses.
- Idempotent and non-destructive by design: - Idempotent and non-destructive by design:
- a missing destination file is copied; - a missing destination file is copied;
- an identical destination file is left alone; - an identical destination file is left alone;
@@ -24,16 +30,16 @@ but for skills, not MCP config:
- No config.toml change, no key, no network: skills are plain - No config.toml change, no key, no network: skills are plain
instruction files, and the helper never writes secrets. instruction files, and the helper never writes secrets.
Honest boundary (see docs/chatgpt-codex-alignment.md): the Honest boundary (see docs/chatgpt-codex-alignment.md): discovery
destination path follows the documented personal-skills pattern itself is now live-verified (skills/list, scope=user, on a temp
mapped onto the tenant home. Whether a given Codex build picks home with the real Codex binary). What is NOT yet verified is
these skills up in a Docker tenant task still needs a live model invocation inside a real Docker tenant task (an explicit
verification task (skills/list + an explicit $live-info call), $live-info call that actually runs and returns a result, and
the same kind of e2e probe websearch MCP got after its seeding whether the relay model honours it). Until a task-level probe
PR. Until that probe passes, do not claim the skills are live passes, do not claim the skills are live in production tasks --
in production tasks -- this helper only guarantees the files this helper only guarantees the files land in the tenant home,
land in the tenant home, idempotently, without clobbering idempotently, without clobbering tenant edits, and that
tenant edits. skills/list sees them there.
""" """
import os import os
@@ -42,8 +48,9 @@ from pathlib import Path
# cloud/skills/<name>/SKILL.md, next to this module. # cloud/skills/<name>/SKILL.md, next to this module.
SKILLS_DIR = Path(__file__).resolve().parent / "skills" SKILLS_DIR = Path(__file__).resolve().parent / "skills"
# Personal-level skills inside a codex-home. # User-level skills inside a codex-home (live-verified 2026-10-30:
DEST_SUBDIR = Path(".agents") / "skills" # .agents/skills inside a codex-home is NOT discovered).
DEST_SUBDIR = Path("skills")
def skill_source_dir(skills_dir=None): def skill_source_dir(skills_dir=None):
+3 -3
View File
@@ -56,7 +56,7 @@ class SkillSeedHelperTestCase(unittest.TestCase):
home = os.path.join(self.tmp.name, "home") home = os.path.join(self.tmp.name, "home")
src = self._src() src = self._src()
ensure_codex_home_skills(home, src) ensure_codex_home_skills(home, src)
dest = os.path.join(home, ".agents", "skills", dest = os.path.join(home, "skills",
"live-info", "SKILL.md") "live-info", "SKILL.md")
open(dest, "w").write("tenant edited version\n") open(dest, "w").write("tenant edited version\n")
out = ensure_codex_home_skills(home, src) out = ensure_codex_home_skills(home, src)
@@ -98,7 +98,7 @@ class WorkerSkillSeedTestCase(unittest.TestCase):
return Worker(self.db_path, "/fake/host.py", **kwargs) return Worker(self.db_path, "/fake/host.py", **kwargs)
def _skill_path(self, dirs, name): def _skill_path(self, dirs, name):
return os.path.join(dirs["codex_home"], ".agents", "skills", return os.path.join(dirs["codex_home"], "skills",
name, "SKILL.md") name, "SKILL.md")
def test_flag_off_is_old_behaviour(self): def test_flag_off_is_old_behaviour(self):
@@ -106,7 +106,7 @@ class WorkerSkillSeedTestCase(unittest.TestCase):
self.assertFalse(worker.seed_skills) self.assertFalse(worker.seed_skills)
dirs = worker._tenant_dirs("projA") dirs = worker._tenant_dirs("projA")
self.assertFalse(os.path.exists( self.assertFalse(os.path.exists(
os.path.join(dirs["codex_home"], ".agents", "skills"))) os.path.join(dirs["codex_home"], "skills")))
def test_flag_on_seeds_real_pack(self): def test_flag_on_seeds_real_pack(self):
worker = self._worker(seed_skills=True) worker = self._worker(seed_skills=True)
+2 -2
View File
@@ -570,7 +570,7 @@ class Worker:
# config.toml + 可选的 websearch MCP)。开启后 # config.toml + 可选的 websearch MCP)。开启后
# _tenant_dirs 在 websearch 播种之后调用 skill_seed # _tenant_dirs 在 websearch 播种之后调用 skill_seed
# 的播种助手,把 cloud/skills/ 的 SKILL.md 拷进租户 # 的播种助手,把 cloud/skills/ 的 SKILL.md 拷进租户
# codex-home/.agents/skills/(个人级技能路径,见 # codex-home/skills/(用户级技能路径,实测见
# skill_seed.py 的路径说明)。老租户下一次任务自动 # skill_seed.py 的路径说明)。老租户下一次任务自动
# 合并;租户改过的技能文件绝不覆盖,只上报 differs。 # 合并;租户改过的技能文件绝不覆盖,只上报 differs。
self.seed_skills = bool(seed_skills) self.seed_skills = bool(seed_skills)
@@ -5702,7 +5702,7 @@ def main(argv=None):
parser.add_argument("--seed-skills", action="store_true", parser.add_argument("--seed-skills", action="store_true",
help="租户 codex-home 自动播种对齐技能包:" help="租户 codex-home 自动播种对齐技能包:"
"_tenant_dirs 把 cloud/skills/ 的 SKILL.md" "_tenant_dirs 把 cloud/skills/ 的 SKILL.md"
" 拷进租户 codex-home/.agents/skills/;" " 拷进租户 codex-home/skills/;"
"默认关,老租户不受影响;租户改过的" "默认关,老租户不受影响;租户改过的"
"技能文件绝不覆盖") "技能文件绝不覆盖")
parser.add_argument("--task-timeout", type=float, default=1800, parser.add_argument("--task-timeout", type=float, default=1800,
+16 -12
View File
@@ -46,12 +46,17 @@
- `cloud/skill_seed.py`:仿 websearch MCP 播种,把 - `cloud/skill_seed.py`:仿 websearch MCP 播种,把
`cloud/skills/` 的 SKILL.md 拷进租户 `cloud/skills/` 的 SKILL.md 拷进租户
`codex-home/.agents/skills/<名>/SKILL.md`。路径依据: `codex-home/skills/<名>/SKILL.md`。路径是实测纠正过的:
Codex 个人技能在 `$HOME/.agents/skills`,项目技能在 2026-10-30 主机实测,临时 codex-home(生产未动)先播
`.agents/skills`(从工作目录向上扫到仓库根);租户任务 `.agents/skills/`,skills/list 只返回 5 个系统技能、3 个
绝不能往任务 worktree 里播种(会污染任务 diff),所以 自有技能一个没出现;改播 `skills/` 后返回 8 个,3 个自有
用租户 codex-home 这个个人级类比位置——这也是 技能以 scope=user 出现。系统技能本身就在
websearch MCP 播种用的通道。 `skills/.system/` 下,这就是用户技能目录的证据。租户
任务绝不能往任务 worktree 里播种(会污染任务 diff),
所以用租户 codex-home——这也是 websearch MCP 播种用的
通道。附带发现:Codex 自带系统技能里已有 imagegen
(生成/编辑位图),所以图片生成在 Codex 本体里不是
“无技能”,只是中转模型是否支持图片工具另待实测。
- `cloud/worker.py`:新增 `--seed-skills`(默认关,仿 - `cloud/worker.py`:新增 `--seed-skills`(默认关,仿
`--websearch-mcp`),只在 `_tenant_dirs` 里调用播种; `--websearch-mcp`),只在 `_tenant_dirs` 里调用播种;
老租户下一次任务自动合并;租户改过的技能文件绝不 老租户下一次任务自动合并;租户改过的技能文件绝不
@@ -60,12 +65,11 @@
- `cloud/tests/test_skill_seed_worker.py`:播种助手 - `cloud/tests/test_skill_seed_worker.py`:播种助手
(拷、幂等、缺包、租户改动不覆盖)+ worker 开关 (拷、幂等、缺包、租户改动不覆盖)+ worker 开关
(关 = 老行为、开 = 播真实包、老租户合并 + 改动存活)。 (关 = 老行为、开 = 播真实包、老租户合并 + 改动存活)。
- 诚实边界:播种只保证文件落进租户 home、幂等、不覆盖 - 诚实边界:发现已实测(skills/list,scope=user),
租户改动。某个 Codex 版本在 Docker 租户任务里是否真 模型在真实 Docker 租户任务里是否真调用 `$live-info`
从这个位置发现技能,仍需一次真实任务实测 并返回结果仍未实测——那需要带 `--seed-skills` 的
(skills/list + 显式 `$live-info` 调用,仿 websearch worker(部署或临时 worker,另等用户定),不是本批
e2e 探针的做法),实测通过前不要宣称“生产任务已能用 能宣称的。本批不改生产配置、不部署,autoscaler 的
这些技能”。本批不改生产配置、不部署,autoscaler 的
worker 参数里加 `--seed-skills` 是部署时另行应用的 worker 参数里加 `--seed-skills` 是部署时另行应用的
主机步骤(unit 文件不在 deploy tar 里,仿 websearch)。 主机步骤(unit 文件不在 deploy tar 里,仿 websearch)。