@@ -13,9 +13,11 @@ class SkillSynthesisAgentError(RuntimeError):
1313
1414
1515AgentInvoker = Callable [[list [str ], float ], subprocess .CompletedProcess [str ]]
16+ _EVIDENCE_KINDS = {"workflow" , "fact" , "rule" , "preference" , "duplicate" , "transient" }
1617
1718
1819def build_skill_synthesis_prompt (* , run_input_dir : Path , run_output_dir : Path , skills_root : Path ) -> str :
20+ """Build the bounded SOP and result contract for the synthesis agent."""
1921 result_path = run_output_dir / "result.json"
2022 return (
2123 "You are the codex-self-evolution skill synthesis agent.\n \n "
@@ -29,6 +31,52 @@ def build_skill_synthesis_prompt(*, run_input_dir: Path, run_output_dir: Path, s
2931 "4. Create, edit, or retire skills only for repeated reusable workflows.\n "
3032 "5. Do not create skills for one-off facts, temporary state, secrets, or account identifiers.\n "
3133 "6. Retired skills must use description \" Retired generated skill. Do not use.\" and csep_status: retired.\n \n "
34+ "Classify every candidate before writing anything: fact|rule|preference|workflow|duplicate|transient.\n "
35+ "Only workflow candidates may become active skills. Facts belong in memory, behavior rules belong in AGENTS/memory,\n "
36+ "preferences belong in USER.md, transient state should be skipped, and duplicate workflows should edit or retire an existing csep-synth skill.\n \n "
37+ "Active SKILL.md frontmatter must be simple single-line YAML:\n "
38+ "---\n "
39+ "name: csep-synth-example\n "
40+ "description: Use when repeated example workflow needs concrete command guidance.\n "
41+ "---\n "
42+ "Do not use block scalar descriptions such as `description: |`.\n "
43+ "The frontmatter name must match the csep-synth-* directory name exactly.\n "
44+ "Only retired skills may contain the retired body line.\n \n "
45+ "Active SKILL.md bodies must use this contract:\n "
46+ "# <Skill title>\n \n "
47+ "## Skill Decision\n \n "
48+ "- Why this is a skill: <why the evidence is a reusable workflow>\n "
49+ "- Why not memory: <why this is not a static fact, preference, or rule>\n "
50+ "- Existing skill boundary: <nearest existing skill and why this is not duplicate>\n \n "
51+ "## When to Use\n \n "
52+ "<concrete trigger conditions>\n \n "
53+ "## Inputs\n \n "
54+ "<required ids, paths, links, env vars, or prerequisites>\n \n "
55+ "## Workflow\n \n "
56+ "<ordered tool/command steps>\n \n "
57+ "## Verification\n \n "
58+ "<evidence that proves success>\n \n "
59+ "## Failure Handling\n \n "
60+ "<when to stop, ask, skip, retire, or report uncertainty>\n \n "
61+ "Write result.json with exactly this schema:\n "
62+ "{\n "
63+ " \" schema_version\" : 1,\n "
64+ " \" run_id\" : \" <current run id if known>\" ,\n "
65+ " \" actions\" : [\n "
66+ " {\n "
67+ " \" action\" : \" create|edit|retire|skip\" ,\n "
68+ " \" skill_id\" : \" csep-synth-example\" ,\n "
69+ " \" path\" : \" <absolute path to SKILL.md, or empty for skip>\" ,\n "
70+ " \" evidence_keys\" : [\" <evidence key>\" ],\n "
71+ " \" reason\" : \" why this action is justified\" ,\n "
72+ " \" evidence_kind\" : \" workflow|fact|rule|preference|duplicate|transient\" ,\n "
73+ " \" why_skill_not_memory\" : \" required for create/edit\" ,\n "
74+ " \" existing_skill_overlap\" : \" nearest existing skill or no direct overlap\" \n "
75+ " }\n "
76+ " ],\n "
77+ " \" notes\" : \" optional\" \n "
78+ "}\n "
79+ "Do not write legacy result shapes such as synthesized, valid_skills, invalid, retired, or skipped as top-level substitutes for actions.\n \n "
3280 "After writing files, return exactly DONE."
3381 )
3482
@@ -86,6 +134,7 @@ def _subprocess_run(cmd: list[str], timeout_seconds: float) -> subprocess.Comple
86134
87135
88136def parse_agent_result (path : Path ) -> dict [str , Any ]:
137+ """Parse and validate the synthesis agent's result.json contract."""
89138 if not path .is_file ():
90139 return {"valid" : False , "actions" : [], "result_missing" : True , "result_invalid" : False }
91140 try :
@@ -107,12 +156,25 @@ def parse_agent_result(path: Path) -> dict[str, Any]:
107156 return {"valid" : False , "actions" : [], "result_missing" : False , "result_invalid" : True , "error" : "bad_action" }
108157 if skill_id and not re .fullmatch (r"csep-synth-[a-z0-9-]+" , skill_id ):
109158 return {"valid" : False , "actions" : [], "result_missing" : False , "result_invalid" : True , "error" : "bad_skill_id" }
159+ evidence_kind = str (item .get ("evidence_kind" ) or "" )
160+ why_skill_not_memory = str (item .get ("why_skill_not_memory" ) or "" ).strip ()
161+ existing_skill_overlap = str (item .get ("existing_skill_overlap" ) or "" ).strip ()
162+ if evidence_kind and evidence_kind not in _EVIDENCE_KINDS :
163+ return {"valid" : False , "actions" : [], "result_missing" : False , "result_invalid" : True , "error" : "bad_evidence_kind" }
164+ if action in {"create" , "edit" }:
165+ if not evidence_kind or not why_skill_not_memory or not existing_skill_overlap :
166+ return {"valid" : False , "actions" : [], "result_missing" : False , "result_invalid" : True , "error" : "missing_skill_decision" }
167+ if evidence_kind != "workflow" :
168+ return {"valid" : False , "actions" : [], "result_missing" : False , "result_invalid" : True , "error" : "non_workflow_skill" }
110169 parsed .append ({
111170 "action" : action ,
112171 "skill_id" : skill_id ,
113172 "path" : str (item .get ("path" ) or "" ),
114173 "evidence_keys" : [str (v ) for v in item .get ("evidence_keys" , []) if isinstance (v , str )],
115174 "reason" : str (item .get ("reason" ) or "" ),
175+ "evidence_kind" : evidence_kind ,
176+ "why_skill_not_memory" : why_skill_not_memory ,
177+ "existing_skill_overlap" : existing_skill_overlap ,
116178 })
117179 return {
118180 "valid" : True ,
0 commit comments