11"""Render a workflow's :class:`~panopticon.core.models.Skill` specs to an agent CLI's command surface.
22
3- The Skill spec is agent-CLI-agnostic (core, ADR 0004); the rendered **body** (frontmatter + the
4- agent procedure) is CLI-agnostic too — claude and codex both read a ``---\\ ndescription: …\\ n---``
5- markdown file. Only the destination dir differs: claude's ``.claude/commands/<name>.md`` slash-command
6- vs codex's ``.codex/prompts/<name>.md`` custom prompt. So the writers take the ``subdir`` under the
7- config home (defaulting to claude's), letting both adapters share the text. Pure — no LLM; it just
8- writes files. The in-container harness fetches the active workflow's skills (over REST) and renders
9- them before launching the agent (Slice 6c).
3+ The Skill spec is agent-CLI-agnostic (core, ADR 0004). The rendered body (frontmatter + the agent
4+ procedure) is shared across CLIs; only the destination and frontmatter format differ:
5+
6+ - **claude** — ``.claude/commands/<name>.md``, ``---\\ ndescription: …\\ n---`` frontmatter.
7+ - **codex** — ``~/.agents/skills/<name>/SKILL.md``, ``---\\ nname: …\\ ndescription: …\\ n---``
8+ frontmatter (codex's model-discoverable skills mechanism; written to user scope so nothing reaches
9+ the task's working tree).
10+
11+ Pure — no LLM; it just writes files. The in-container harness fetches the active workflow's skills
12+ (over REST) and renders them before launching the agent (Slice 6c).
1013"""
1114
1215from __future__ import annotations
1619
1720from panopticon .core .models import Skill
1821
19- #: The default destination (relative to the config home) — claude's slash-command dir. Codex passes
20- #: its own (``(".codex", "prompts")``).
22+ #: The default destination (relative to the config home) — claude's slash-command dir.
2123CLAUDE_COMMANDS_SUBDIR : tuple [str , ...] = (".claude" , "commands" )
2224
2325
@@ -81,9 +83,7 @@ def write_operation_commands(
8183 task_id : str ,
8284 subdir : Sequence [str ] = CLAUDE_COMMANDS_SUBDIR ,
8385) -> list [Path ]:
84- """Write each core operation (verb → target state) to ``<root>/<subdir>/<verb>.md``.
85-
86- ``subdir`` defaults to claude's ``.claude/commands``; codex passes ``(".codex", "prompts")``."""
86+ """Write each core operation (verb → target state) to ``<root>/<subdir>/<verb>.md``."""
8787 commands_dir = root .joinpath (* subdir )
8888 commands_dir .mkdir (parents = True , exist_ok = True )
8989 written = []
@@ -92,3 +92,59 @@ def write_operation_commands(
9292 path .write_text (render_operation (name , target_state , task_id ))
9393 written .append (path )
9494 return written
95+
96+
97+ # -- codex skills surface (model-discoverable; ~/.agents/skills/<name>/SKILL.md) ----------------
98+
99+
100+ def render_agent_skill (skill : Skill , task_id : str ) -> str :
101+ """The rendered ``SKILL.md`` body for a skill on the codex agent-skills surface.
102+
103+ Adds ``name:`` to the frontmatter (required by the skills mechanism) alongside ``description:``.
104+ The instructions body and task-id note are the same as :func:`render_command`.
105+ """
106+ return (
107+ f"---\n name: { skill .name } \n description: { skill .description } \n ---\n "
108+ f"{ skill .instructions } \n { _task_id_note (task_id )} "
109+ )
110+
111+
112+ def render_agent_operation (name : str , target_state : str , task_id : str ) -> str :
113+ """The rendered ``SKILL.md`` body for a core operation on the codex agent-skills surface."""
114+ return (
115+ f"---\n name: { name } \n description: Apply the workflow's '{ name } ' operation.\n ---\n "
116+ f"Apply this workflow's `{ name } ` operation — it moves the task to **{ target_state } **. "
117+ f'Invoke it with the `apply_operation` tool (`operation="{ name } "`, `task_id="{ task_id } "`); '
118+ f"don't edit the state directly. It's gated on the current state's responsibilities and "
119+ f"starts a new turn.\n "
120+ )
121+
122+
123+ def write_agent_skills (skills : Iterable [Skill ], root : Path , task_id : str ) -> list [Path ]:
124+ """Write each skill to ``<root>/.agents/skills/<name>/SKILL.md``; return the paths written.
125+
126+ Uses codex's model-discoverable skills mechanism. Written to user scope (``<root>`` is
127+ ``~``), so nothing reaches the task's working tree.
128+ """
129+ written = []
130+ for skill in skills :
131+ skill_dir = root / ".agents" / "skills" / skill .name
132+ skill_dir .mkdir (parents = True , exist_ok = True )
133+ path = skill_dir / "SKILL.md"
134+ path .write_text (render_agent_skill (skill , task_id ))
135+ written .append (path )
136+ return written
137+
138+
139+ def write_agent_operation_skills (
140+ operations : Mapping [str , str ], root : Path , task_id : str
141+ ) -> list [Path ]:
142+ """Write each core operation to ``<root>/.agents/skills/<name>/SKILL.md``."""
143+ written = []
144+ for name , target_state in operations .items ():
145+ skill_dir = root / ".agents" / "skills" / name
146+ skill_dir .mkdir (parents = True , exist_ok = True )
147+ path = skill_dir / "SKILL.md"
148+ path .write_text (render_agent_operation (name , target_state , task_id ))
149+ written .append (path )
150+ return written
0 commit comments