* feat: guidance for building standalone ai agents in ai sessions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: agent guidance for resource-only sessions, no flow tools Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: tell the user to deploy a drafted tool script with its agent Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: scope plain agent values, document tool_inputs, list agents in get_instructions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: test and run ai agents from ai sessions, open agents in their editor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: full-bleed agent editor tabs, back to the run form for agent tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: describe read-and-run as running agents too, pin agent run links Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: host the agent editor natively in session tabs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: switch the agent editor to tabs below 900px Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: name an agent's session tab after its summary Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: show deployed agents in session tabs, open and run them from the chat Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: deploy agents from the chat, report agent tabs as the active editor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: pick agents from the tab picker, name their tabs, scope the agent page to its workspace Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: leave open editors alone on agent writes, read the agent page's workspace user Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refresh an agent's deployed page when the chat deploys or deletes it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: name agent tabs before they load, mark agents in the changes drawer, stay on a deleted agent's tab Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep a cleared agent summary out of its tab name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the resource drawer's json option that the resources list still passes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: open an agent in an AI session from its pages, fix review findings on agent tabs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: name the reserved agent tool names in the agent guidance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: reload an agent's editor on a discard, open a new agent in a session Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: show agents as agents on compare and deploy, name a never-deployed one by its path Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep a deployed agent's type in its draft diff Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: link a fork row to the agent editor only on the side that holds the agent Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: guard agent page paths, mark drafted agents in the picker, side-aware agent rows on fork compare Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
System Prompts
The single source of the AI guidance Windmill ships: what an agent needs to know about Windmill itself to write flows, scripts, apps, pipelines, resources, triggers and schedules. Two consumers are built from it:
- the chat (the frontend AI chat: flow, script, app and global modes), through the
$system_promptsalias toauto-generated/; - the CLI (
wmill init/wmill refresh prompts), as the skills embedded incli/src/guidance/skills.gen.ts, next to theAGENTS.wmill.mdtemplate incli/src/guidance/core.ts.
The workspace MCP server (backend/windmill-api/src/mcp/session/) is a third reader, one step
removed: it serves the global chat's own get_instructions texts and system prompt, rendered for
an external client by npm run gen:mcp-session in frontend/. Rerun that after generate.py.
Guidance written in one consumer only is guidance the other never gets. Write it here.
Structure
system_prompts/
├── base/ # Hand-written guidance, one file per topic (flow-base.md, raw-app.md, …)
│ # plus CLI-only intros (flow-cli.md, script-cli.md, raw-app-cli.md, …)
├── languages/ # Hand-written per-language guidance (bun.md, python3.md, …)
├── generate.py # Builds everything under auto-generated/ and cli/src/guidance/skills.gen.ts
├── utils.py # Shared helpers, including the fence renderer
└── auto-generated/ # Generated — never edit
├── prompts.ts # One export per base/ and languages/ file (chat render), SDK docs, schema
├── index.ts # Chat helpers (getFlowPrompt, getScriptPrompt, …)
├── skills/ # One SKILL.md per CLI skill (cli render)
├── sdks/ # SDK reference extracted from the TypeScript and Python clients
└── cli/ # CLI command reference extracted from cli/src/commands/
How a topic is assembled
TOPICS in generate.py lists, for each topic, the parts both consumers concatenate in order:
a file under base/ or languages/, or a token filled per consumer ({lang}, {sdk},
{wac_sdk}, {openflow_schema}, {cli_commands}). The same entry produces the topic's chat
helper in index.ts and its CLI skill, so a part added there reaches both. A part tagged
('chat', …) or ('cli', …) goes to that consumer only, and cli_intro heads the CLI skill with
a CLI-only workflow file (flow-cli.md, script-cli.md, raw-app-cli.md).
| Topic | Chat helper | CLI skill | Shared parts |
|---|---|---|---|
| script | getScriptPrompt(lang) |
write-script-<lang> |
script-base.md, the language file, its SDK |
| flow | getFlowPrompt() |
write-flow |
flow-base.md, the OpenFlow schema |
| raw app | getRawAppPrompt(lang) |
raw-app |
raw-app.md (chat adds the SDK) |
| resources | getResourcePrompt() |
resources |
resources.md |
| workflow-as-code | getWorkflowAsCodePrompt(lang) |
write-workflow-as-code |
workflow-as-code.md, the WAC SDK |
| pipeline | getPipelinePrompt() |
write-pipeline |
pipeline-base.md |
| data tables | getDatatablePrompt() |
datatables |
datatable.md |
| workspace settings | getWorkspaceSettingsPrompt() |
workspace-settings |
workspace-settings.md |
| triggers, schedules, preview, CLI commands | — | same names | CLI only |
The app chat embeds RAW_APP_BASE directly (chat/app/core.ts).
Consumer fences
A sentence that only one consumer should see stays in the shared file, fenced:
A tool name outside that character set fails every run of the flow.
<!-- chat-only -->
The flow write tools refuse such a name.
<!-- /chat-only -->
<!-- cli-only -->
`wmill lint <flow folder>` reports it before anything runs.
<!-- /cli-only -->
render_for (utils.py) renders every file once per consumer: prompts.ts gets the chat render,
the skills get the cli render, and the fence lines reach neither. Fences sit on their own lines and
cannot nest; an unbalanced or misspelled fence fails generation (an HTML comment opening with cli
or chat, or ending in only, is read as a fence attempt).
Use a fence for a sentence or a section. When most of a topic differs per consumer, a CLI-only
file used as cli_intro reads better than a file that is mostly fences.
What goes where
- Here: anything true of Windmill regardless of who is asking — module shapes, data flow rules, what the editor accepts, how data tables, secrets or app access work.
- In the chat's TypeScript prompt builders (
frontend/src/lib/components/copilot/chat/*/core.ts): only tool plumbing (which tool to call, its arguments) and values known at run time (the user's name, this app's data-table policy, session capabilities). - In
cli/src/guidance/core.ts(AGENTS.wmill.md): project-level CLI workflow — which skill to use, deploying, debugging jobs.
A file in TOPICS names no chat tool, not even inside a chat-only fence: it reaches every chat
mode (flow mode's system prompt, global mode's get_instructions), each with its own tool names,
and global/sessionToolset.test.ts fails a prompt that names a tool its session lacks. Describe the
action instead ("the flow write tools refuse such a name") and name the tool in TypeScript. The one
exception is flow-chat-special-modules.md, which only flow mode reads.
Regenerating
After editing anything here, or when SDK methods, the OpenFlow schema or CLI commands change:
python system_prompts/generate.py
CI (.github/workflows/check-system-prompts.yml) runs system_prompts/check-freshness.sh, which
fails when the committed output is stale.
To also refresh the standalone skills in a Claude plugin checkout:
python system_prompts/generate.py --plugin-dir ~/windmill-claude-plugin
--plugin-dir accepts:
- the
windmill-claude-pluginrepo root - a plugin root such as
plugins/windmill - a direct
skills/directory
To regenerate the public docs repo (consumed by context7):
python system_prompts/generate.py --context7-dir ~/windmill-cli-docs
--context7-dir writes a fully-rendered snapshot (AGENTS.md,
cli-commands.md, skills/<name>/SKILL.md, README.md, manifest.json
with the Windmill version) with all template placeholders resolved —
suitable for ingestion by docs aggregators. In CI this runs from
.github/workflows/publish-cli-docs.yml on every release tag. The
generator refuses to wipe the target directory unless it's empty or has
a context7 marker (context7.json, manifest.json, or a
windmill-cli-docs git remote), so a typo can't delete unrelated files.