diff --git a/src/command_system/workflows_integration.py b/src/command_system/workflows_integration.py index 53c64eee1..ef9a84aff 100644 --- a/src/command_system/workflows_integration.py +++ b/src/command_system/workflows_integration.py @@ -63,28 +63,40 @@ def _workflow_to_command(path: Path, loaded_from: str) -> Optional[PromptCommand _ULTRACODE_DIRECTIVE = ( - "The user invoked /ultracode — AUTHOR a reusable multi-agent workflow (a " - '"pipeline") for the task below and SAVE it as a slash command. Do NOT run it ' - "now, and do NOT call the Workflow tool — the user will run it themselves with " - "`/` (the same way `/deep-research` runs).\n\n" + "Your job right now: WRITE a new multi-agent workflow script (a reusable " + '"pipeline") for the task at the bottom of this message, and save it to a file ' + "with your Write tool.\n\n" + "IMPORTANT — this is a writing task you do YOURSELF with the tools you already " + "have. There is NO \"ultracode\" tool, skill, or command to invoke or look up — " + "do NOT use ToolSearch, do NOT search for a skill, and do NOT call any " + "\"Workflow\" tool. You are not running anything; you are authoring a `.py` file " + "and stopping.\n\n" "Steps:\n" - "1. Read `src/workflow/bundled/deep_research.py` first and mirror its exact " - "format: a top-level `meta = {\"name\": ..., \"description\": ..., \"phases\": " - "[{\"title\": ...}, ...]}` dict, then an async body that uses ONLY the injected " - "primitives — `await agent(prompt, schema=...)`, `parallel`, `pipeline`, " - "`phase`, `log`, `budget` — and ends with `return `. It is sandboxed " - "Python: no `import`, no `open`, no `Date.now()`/`random` (the runtime withholds " - "them).\n" - "2. Design the workflow for the task: decompose into phases, fan out subagents " + "1. Read `src/workflow/bundled/deep_research.py` for a complete, real example of " + "the format. A workflow is sandboxed async Python shaped like:\n\n" + " meta = {\n" + " \"name\": \"\",\n" + " \"description\": \"\",\n" + " \"phases\": [{\"title\": \"Search\"}, {\"title\": \"Research\"}, {\"title\": \"Write\"}],\n" + " }\n" + " phase(\"Search\")\n" + " items = await agent(\"\", schema={...}) # one subagent, returns data\n" + " researched = await parallel([agent(f\"\") for x in items]) # fan out\n" + " phase(\"Write\")\n" + " return await agent(\"\")\n\n" + "Use ONLY the injected primitives — `await agent(prompt, schema=...)`, " + "`parallel`, `pipeline`, `phase`, `log`, `budget` — and end with `return " + "`. No `import`, no `open`, no clock/random (the sandbox withholds them).\n" + "2. Design the pipeline for the task: decompose into phases, fan out subagents " "for the independent parts, verify, then synthesize. Give `meta.description` a " - "clear one-line summary — it becomes the slash command's description.\n" - "3. Choose a short kebab-case name (e.g. `hn-scraper`) and Write the script to " - "`.claude/workflows/.py` with the Write tool (create the directory if " - "needed). The filename stem IS the command name.\n" - "4. Reply in two or three lines: confirm the workflow is saved and tell the user " - "to run it with `/ ` (runs in the background like /deep-research). " - "If the task below is empty, ask what the workflow should do instead of guessing.\n\n" - "Author and SAVE only — do not launch it.\n\n" + "clear one-line summary — it becomes the command's description.\n" + "3. Pick a short kebab-case name (e.g. `wc26-watch-guide`) and use the Write " + "tool to create `.claude/workflows/.py` with the script. The filename stem " + "becomes the command name.\n" + "4. Then STOP. Reply in two or three lines: confirm the file you wrote and tell " + "the user to run it with `/` (it runs in the background, like " + "/deep-research). If the task below is empty, ask what the workflow should do.\n\n" + "Write the file and stop — do NOT run the workflow.\n\n" "Task:\n$ARGUMENTS" ) @@ -93,7 +105,12 @@ def _ultracode_command() -> PromptCommand: """The ``/ultracode`` command: author a fresh workflow and SAVE it as a reusable ``/`` slash command (it does **not** run — the user launches it later with ``/``, exactly like ``/deep-research``). ``/deep-research`` and saved - ``/`` run an *existing* script; ``/ultracode`` is the *generator*.""" + ``/`` run an *existing* script; ``/ultracode`` is the *generator*. + + The directive is written as a direct imperative ("WRITE a script… with your Write + tool") and explicitly forbids hunting for an "ultracode" tool/skill — an earlier + phrasing led models to ToolSearch for a non-existent ultracode tool instead of + authoring the file themselves.""" return PromptCommand( name="ultracode", description="Author a multi-agent workflow (pipeline) and save it as a / command", diff --git a/tests/test_ultracode.py b/tests/test_ultracode.py index 5ddcd912b..78ad39141 100644 --- a/tests/test_ultracode.py +++ b/tests/test_ultracode.py @@ -167,12 +167,16 @@ def test_ultracode_command_directive_authors_and_saves(): cmd = _ultracode_command() directive = cmd.markdown_content or "" - assert "AUTHOR" in directive # author a fresh workflow… + assert "write" in directive.lower() # write a script yourself… assert ".claude/workflows" in directive # …saved as a / command - assert "Write" in directive # via the Write tool - assert "do NOT call the Workflow tool" in directive # NOT run immediately + assert "Write tool" in directive # via the Write tool assert "deep_research.py" in directive # format template referenced assert "$ARGUMENTS" in directive # the task is substituted in + assert "do NOT run the workflow" in directive # author only, no auto-launch + # Forbid the tool/skill hunt that broke a live session (model ToolSearch'd for + # a non-existent "ultracode" tool instead of authoring the file itself). + assert "ToolSearch" in directive + assert "skill" in directive # description reflects "save as a / command", not "run" desc = (cmd.description or "").lower() assert "save" in desc and "/" in desc