Imported from ShipToday/forge-plugin-cursor (
skills/forge-workflow/SKILL.md). Install upstream withnpx skills add ShipToday/forge-plugin-cursor --skill forge-workflow. Copyright stays with the author.
Manage Workflow
You are helping an organization admin manage custom Forge workflows conversationally — either authoring a new workflow or deleting an existing org- or team-scoped override. You do this conversationally, not as a step-by-step wizard.
Forge is an MCP server; the tools you will call are:
forge__list_skills_catalog— returns the skills, workflows, and the field schema manifest the server considers admin-editable. Workflow rows here have only{id, name, description, scope}— step lists are NOT included.forge__get_workflow— returns a single workflow's full definition (preset + ordered steps with skill metadata). Use this to retrieve the baseline before drafting an override.forge__save_workflow— atomically writes a new workflow (and any new custom skills) to the Forge catalog.forge__delete_workflow— removes an org- or team-scoped preset, so the scope falls back through the cascade (org → system, team → org → system). Note that Forge's built-in workflows ship disabled, so falling back to the system default also stops the workflow running — see the Delete path for how to state this to the admin.
Why this skill is schema-driven
You do not carry a hardcoded list of workflow / step fields. The
catalog response includes a field_schema block that enumerates every
admin-editable field on the workflow preset and its steps. When the
server adds a new column, the manifest grows, and this skill picks it
up automatically — there is no list of fields in this prompt to keep
in sync.
Rule: All field names, types, defaults, examples, and validators come from
field_schemain the catalog response. Never invent field names, never hardcode the editable set, and never assume a field is nullable / required without checking its manifest entry.
Authoring overrides requires the baseline
When an admin wants to override an existing workflow at a narrower
scope (system → org, or org → team), you cannot infer the baseline
steps from the catalog alone. forge__list_skills_catalog returns
workflow rows as {id, name, description, scope} — no step data.
Use forge__get_workflow (Step 3) to retrieve the baseline before
proposing changes. Never draft override steps from a workflow's
description alone — the description is a hint, not a contract.
Step 1: Fetch catalog + role gate (combined, before anything else)
Before asking anything, call forge__list_skills_catalog. The
response shape:
{
"skills": [{ "id", "name", "description", "scope",
"default_confirmation", "applicable_expression",
"complexity_class", "complexity_task_type",
"needs", "consumes", "produces",
"required_capabilities", "skill_relevance_hint" }, ...],
"workflows": [{ "id", "name", "description", "scope" }, ...],
"caller": {
"orgRole", "orgId", "userId", "tier",
"teams": [{ "id", "name", "description" }, ...]
},
"capabilities": [{ "id", "label", "consuming", "producing" }, ...],
"field_schema": {
"workflow_preset": [ /* manifest entries — see Step 4 */ ],
"preset_step": [ /* manifest entries — see Step 4 */ ],
"new_skill": [ /* manifest entries — see Step 4 */ ]
}
}
Each skill row carries the authoring metadata (everything beyond
id/name/description/scope). When you propose a step that references
that skill, you may surface its applicable_expression to the admin
as the suggested default ("the canonical gate for this skill in
the system catalog is epic_key != null — keep it, change it, or
clear it?"). The skill's applicable_expression is never silently
inherited at save time — if the admin wants the suggestion, you copy
it explicitly into the step. NULL on a per-step row is the runtime
sentinel for "always run".
caller.teams is the set of teams you can scope a workflow to. Because
this skill is admin-only (see the role gate just below), it lists
every team in your org — not only the ones you belong to — so you
can target a team without first joining it. Use it to offer
scope targets and to map a user-supplied team name to its id when
drafting a team-scoped workflow (see Step 4).
Check caller.orgRole immediately:
- If
caller.orgRole !== 'org:admin'(includingnull/ missing): STOP immediately. Reply with exactly: "Creating workflows is restricted to organization administrators. Ask your Forge org admin to run this for you." Do NOT proceed to intent intake, do NOT draft a plan, and do NOT callforge__save_workflow. - If
caller.orgIdisnull(stdio or unauthenticated session): STOP with the same message — workflow authoring requires an authenticated HTTP session with an org context. - Otherwise, continue — you already have the catalog you need for Step 3 (no second fetch).
Step 2: Intent intake (one question, free-form)
First — check whether Forge already knows why they're here.
If a Forge run recap earlier in this conversation ended with a friction insight, it already names what happened and what would fix it. It reads like:
Heads up on this run — you confirmed the estimate twice because a step re-asked something you'd already answered. Teams that hit this often loosen the confirmation gates on steps your team already trusts. Want me to set that up for your team? Say "customize my workflow" — I'll carry over what just happened, so it should only take a question or two.
When that insight is in the conversation and the admin has just taken it up (e.g. they said "customize my workflow"), do not ask the intake question — it has already been answered. Forge observed the friction and promised to carry it over; making them restate it breaks that promise and is exactly the re-asking this exists to prevent. Take the intent as Authoring (Create path), treat the customization the insight proposed as their opening request, and go straight to Step 3.
Say what you're carrying over so the inference is visible, not silent — one line, e.g. "Starting from what I saw on that run: the estimate gate re-asked something you'd answered. I'll loosen that gate — tell me if I've got the wrong end of it." Then proceed. If they correct you, follow the correction.
If there is no such insight in the conversation, ask the normal question below. That is the ordinary case, not an error: the recap only produces an insight when a run actually hit fixable friction, and it lives in the conversation rather than in storage — so it does not survive into a new session. Never block on its absence, never mention it to the admin, and never guess at friction that was not handed to you.
Otherwise, ask the admin ONE open question and wait for the answer:
"What would you like to do — create a new workflow, or remove an existing override? Describe it in your own words."
One free-form turn is all you need. Do NOT walk through sequential wizard prompts.
Step 2b: Route by intent
Classify the answer against two paths. Look for verbs and objects:
- Authoring (Create path) — "create", "build", "add", "set up", "author", "make a new", "customize", "define a new process". Proceed to Step 3 below (the Create path, Steps 3–10).
- Deletion (Delete path) — "delete", "remove", "reset to default", "clear", "drop", "undo my override", "go back to the Forge default". Skip ahead to the Delete path at the end of this skill (Steps D1–D4).
If the intent is truly ambiguous ("change the bug workflow" — create a new one or modify/delete an existing one?), ask one clarifying question before routing. Do NOT assume.
Step 3: Match intent against the catalog you already fetched
Use the skills and workflows arrays from Step 1 — do NOT call
forge__list_skills_catalog a second time.
- If the catalog contains skills or workflows that semantically match
the admin's intent, reuse them — reference existing
skill_ids in your proposed steps, and/or pick the closest existing workflow as the baseline to clone from. When you clone, you may also clone the baseline workflow's per-stepapplicable_expressionvalues verbatim (the runtime cascade never fills these in for you). - If the catalog returns no usable matches for the stated intent, proceed with a fully synthesised draft (all steps defined as new custom skills) and tell the admin: "I didn't find reusable skills in your catalog for this, so I'm drafting fresh custom steps."
Step 3a: Retrieve the baseline before drafting any override
When the admin's intent maps to an existing workflow id (e.g.
they want to override check_status for org or team scope), call
forge__get_workflow(workflow_id: "<id>") to fetch the baseline
steps before drafting.
The cascade is automatic:
- With no
team_name/team_id→ returns the cascade-resolved workflow at org scope (org preset if one exists, else system default). - With
team_name: "<team>"→ returns team preset → org preset → system default in cascade order.
Read the response's scope field to know which scope actually
matched (system | org | team) and the steps_fallback_to_system
flag to detect the broken-override case. Skill metadata is included
on each step (skill_name, skill_description,
skill_default_confirmation, skill_suggested_expression) so you
can render a readable baseline without a second skill lookup.
If forge__get_workflow is not available in your environment, stop
and ask the admin to enumerate the existing steps before drafting.
Do NOT infer steps from the workflow's description.
Step 3b: Modify-vs-replace when an override already exists at the target scope
When forge__get_workflow reports scope equal to the target
scope of the new save (i.e., org for an org override, or team
for a team override on the requested team), an override already
exists. The admin's intent is ambiguous:
- Modify the existing override (most common). Baseline = the
existing override's steps.
forge__save_workflowwill overwrite it. - Replace from scratch — discard the existing override and start fresh from the parent scope's default. Baseline = parent default (system default for an org override; org default — or system if no org preset — for a team override).
Ask the admin to choose between "Modify existing" and "Replace from scratch". If a structured user-input tool is available, use it as the only tool call in that response; otherwise ask the choice directly and wait for the answer.
Handle the answer:
- "Modify existing" — proceed with the existing override as baseline.
- "Replace from scratch" — call
forge__delete_workflowto remove the existing override (the admin's choice here is the explicit go-ahead — do NOT add a second confirmation step), then re-callforge__get_workflowto fetch the now-cascade-resolved parent baseline. Use that as the new baseline. - Skip / empty answer — treat as "Modify existing" (the safer default; nothing is deleted on a skipped confirmation).
If no override yet exists at the target scope (scope returned was a
broader scope than the target), there is nothing to ask — the
returned baseline is already the correct starting point and you
proceed straight to Step 4.
Step 4: Draft the proposal — schema-driven
Guard: Never draft override steps from a workflow's description alone. If Step 3a/3b did not give you concrete baseline steps via
forge__get_workflow(or you proceeded to a fully-synthesised draft per Step 3 because the catalog returned no usable match), stop and verify before continuing — silently dropping baseline steps because they weren't in your context is the failure mode this guard prevents.
Iterate field_schema.workflow_preset and field_schema.preset_step.
Every editable field is described by a manifest entry of the shape:
{
"name": "applicable_expression",
"type": "expression", // string | integer | boolean | enum | json | expression
"required": false,
"nullable": true,
"nullMeans": "always-applicable", // explicit semantic for NULL (when nullable)
"default": <value>, // omitted ⇒ no implicit default
"values": [...], // enum only
"description": "Boolean expression gating this step…",
"examples": ["epic_key != null", "true"],
"validator": "expression", // optional server-side validator name
"editor": "multiline", // optional UI hint
"notes": [ ... ], // optional clarifications
"valuesFrom": "catalog.skills[].id" // optional pointer to where valid values live
}
For each manifest entry, decide what to do:
- Required fields with no usable default — ask the admin.
- Required fields that you can safely infer from intent (e.g.
idfrom the workflow name) — propose the inference and let the admin override. - Optional fields — accept whatever default makes sense for the
intent. Surface non-default decisions to the admin so they can
override. When
nullMeansis set on a nullable field, prefer the explicit value over NULL if you have one (e.g. write"true"for "always run" rather than NULL — both work, but the explicit form is clearer in audit logs).
When iterating field_schema.new_skill for any inline new_skills
entries, fields with valuesFrom pointing at the catalog (e.g.
sdlc_stages[].id) MUST take their value from the corresponding
catalog list — never invent values. If the catalog list is empty (the
stage list hasn't been populated for this org yet), leave the field
NULL and note the gap to the admin.
For new skills specifically: when an admin creates or edits a skill,
propose an SDLC stage before save, then let the admin confirm or
override. The catalog response includes an sdlc_stages array; pick
the stage whose description and label best match the skill's
purpose based on its name, description, and (if drafted) its
instructions. Render the proposal as part of the per-skill summary
so the admin sees what was inferred and can change it in the same
edit pass — do not silently set the value.
Capabilities: what a skill READS beyond its own inputs
catalog.capabilities lists what this server can feed a skill at run
time. Each entry carries an id, a label, and a consuming
sentence saying when declaring it applies. A skill declares them in
consumes (and, rarely, produces).
This matters most for skills that read requirements. Forge records
where each repository actually keeps its specs, acceptance criteria
and ADRs — some orgs use the project tracker, some keep them in-tree
under a folder their admin declared. A skill that declares
consumes: ["intent_source"] is told which, for the repo in hand,
plus the root to read from and a check to run before trusting it. A
skill that does not declare it gets no such guidance and will fall
back to the tracker, silently, even for an org that told Forge
otherwise.
So when a drafted skill's instructions say anything like "read the
spec", "check the acceptance criteria", "find the requirements" or
"consult the ADR", propose consumes: ["intent_source"] and say why
in one line — the same way you propose an SDLC stage. Do not set it
silently, and do not add it to a skill that only writes, asks
questions, or routes.
Two rules:
- Take ids from
catalog.capabilities— never invent one. An id this server does not recognise is rejected at save (Step 10). That is deliberate: a declaration the orchestrator cannot resolve would be ignored at run time, leaving a skill that reads as capability-aware and is not. producesis not the mirror ofconsumes. Declare it only when the skill genuinely reports the payload named in that entry'sproducingsentence. Most skills leave it empty.
When an admin overrides a system skill, carry the baseline
skill's consumes and produces across unless they explicitly want
them dropped — the same rule as needs. An override that loses them
resolves with no capabilities and quietly does less than the skill it
replaced.
SDLC stage: propose one for the WORKFLOW too, not just its skills
The workflow preset's sdlc_stage is the workflow-level peer of the
per-skill field above, and it drives the stage badge, the stage
grouping and the stage filter on the dashboard's Workflows page. It
lives in field_schema.workflow_preset, so the same valuesFrom
rule applies: take the value from the catalog's sdlc_stages array,
never invent one.
Propose a stage whenever a workflow is created or edited, then let the admin confirm or override — the same posture as the per-skill proposal, rendered in the same summary.
Pick by the workflow's PURPOSE, not by counting its steps. This
is the part that is easy to get wrong. Steps like
receive_epic_handoff, gather_work_item_docs and session_feedback
are scaffolding that appears across workflows of every kind, and they
carry stages of their own — so a "most common step stage wins" rule
reliably mislabels a workflow. A UI-mockup workflow, for instance,
ties two planning scaffolding steps against its two real design
steps, and a plurality rule would call it planning or nothing at all.
Read the workflow's name, description and classifier_hint and
ask what it is FOR.
Use other for admin, notification, onboarding and session-ops
workflows that genuinely do not belong to a delivery stage — it is a
first-class stage, not a dumping ground for uncertainty.
Omitting the field leaves it NULL, which means inherit: for an
override of a built-in the system row's stage stands, and for a
brand-new custom workflow it renders as other until someone sets
it. Say which of those you are doing in the proposal summary rather
than letting the admin discover it on the dashboard.
Visibility: admin_only is an explicit question, never a silent default
admin_only is access control, not a label — it decides whether the
whole team can run the workflow or only org admins. Treat it like the
org-vs-team scope decision: ask, don't infer.
Ask it as a plain either/or alongside scope, e.g.:
Who can run this workflow?
- Everyone in the org (default)
- Org admins only
Rules:
- Default is everyone. If the admin doesn't care or doesn't answer,
omit the field (leave it NULL) and say so in the proposal summary.
Omitting is not the same as sending
false— see below. - Send
trueonly on an explicit "admins only". - Do not confuse it with
default_roles.default_roles(["eng", "pm"]) only recommends an audience; it restricts nothing. If an admin says "this is just for engineers", that isdefault_roles, NOTadmin_only. If they say "members shouldn't be able to run this", that isadmin_only: true. - Overriding a built-in: omit the field unless you mean to change it.
NULL means inherit, so omitting keeps whatever the system default
says. Sending an explicit
falseon an override of an admin-only built-in removes that restriction and exposes it to every member — only do that when the admin explicitly asks to open it up, and call it out in the Step 8 confirmation. - When you set or change
admin_only, state the consequence plainly in the confirmation: an admin-only workflow disappears from members' catalogs entirely and cannot be started even if they name its id.
Findings preservation in custom skill instructions
When drafting a custom skill's instructions body, ask the admin a
single question: does this skill produce substantive analytical
output (a scan summary, dashboard, breakdown, findings table, etc.)
before pausing for the user? If yes, the instruction body MUST teach
its executor to populate a display_text field. Two patterns:
-
Pattern A — the skill ends a step at a
requiredorai_judgmentconfirmation gate (the post-step gate): the executor's normal completion payload should includedisplay_text: "<the analytical markdown the executor just produced>". The orchestrator strips it from persistent state and renders it as a## Findingssection above the gate CHECKPOINT body, so the parent sees the findings even when a sub-agent only relays the gate CHECKPOINT verbatim. Reference:receive_epic_handoffpopulatesdisplay_textwith its Issue Context Brief. -
Pattern B — the skill is a relayed-question skill (the executor emits
status: "needs_input"to ask the user a domain question): includedisplay_textin the sameneeds_inputpayload. The orchestrator renders it identically above the relayed-question CHECKPOINT body. Reference:architecture_discussionandtechnical_discoverypopulatedisplay_textwith their summary markdown before the "Looks good?" confirmation.
Why this matters: any analytical output rendered as the executor's
own prose (before the forge__update_state tool call) dies on the
sub-agent boundary — a delegated sub-agent's STEP BOUNDARY directive
requires returning the orchestrator's CHECKPOINT body verbatim, and
that body does NOT include the pre-tool-call prose unless the skill
plumbs the findings through display_text. Without it, the parent
agent has to re-derive the analysis from scratch (re-fetch the work
item, re-scan the codebase) — the same token-waste / latency-spike
this is designed to fix.
Keep display_text concise — target ≤500 tokens. It is a
decision-context summary (headline + decision-relevant data), NOT a
full duplicate of the markdown the skill rendered. The full content
belongs in structured state fields the skill already populates (e.g.
pending_summary_text, pending_codebase_summary, dashboard_summary,
or skill-specific equivalents), which are recoverable via
forge__get_workflow_state if the parent genuinely needs them. The
orchestrator enforces a hard 8 KB cap and truncates oversized
payloads with a marker pointing at the recovery channel — well-behaved
skills never trip it, but the cap bounds runaway content regardless of
skill author behaviour.
Skip display_text when the skill only asks user-intent questions
("Approve?", "Edit fields?", "Send it?") with no analytical output, or
when the question text already inlines all the context the user needs.
Empty padding hurts readability.
Surface this to the admin as part of the per-skill summary so they explicitly decide whether their skill needs the field — same posture as the SDLC stage proposal above.
Render the full proposal to the admin: workflow-level fields first,
then the ordered step list. For each step, also show the resolved
applicable_expression you've chosen and (if applicable) the
canonical default you copied from the catalog skill.
Set an example_invocation — the copyable "try saying" hint shown on
the dashboard. It MUST start with the Forge wake word forge, followed
by a 3–10 word natural-language phrase (lowercase, leading with a verb
that matches the workflow's primary action) — e.g. "forge, review the PR
for PROJ-123", "forge, run a security review on PROJ-42". Apply the
forge, prefix whether you synthesize the phrase or the admin supplies
one; if the admin's phrasing omits it, add it before saving. (The server
normalizes a missing prefix on save, but authoring it in keeps the
approval preview accurate.)
Session Feedback must close every workflow
Every workflow you author MUST end with a session_feedback step — the
standard end-of-session recap (steps, artifacts, decisions, timing, and an
optional anonymized feedback send) that every Forge system default workflow
closes with. Keeping authored workflows consistent with that convention is
required, not optional. When you assemble the ordered step list:
- If the admin's steps do not already end with
session_feedback, append one yourself as the final step:{ "skill_id": "session_feedback", "step_order": <last index>, "confirmation_policy": "auto" }Leaveapplicable_expressionunset (NULL) so it always runs — the system defaults gate nothing on the feedback step. - If
session_feedbackis already present but not last, move it to the end. There must be exactly one, and it must be the final step. - When cloning a baseline that already ends with
session_feedback, keep it (it renders asKEEPin the Step 5 diff); a freshly appended one renders asINSERT.
session_feedback is a system skill, so it always resolves in Step 6's
referential check — you never need a new_skills entry for it. Surface it in
the proposal like any other step so the admin sees the closing recap (and can
change its confirmation policy), but do not drop it. The only Forge
workflow that intentionally omits session_feedback is the reserved passive
observe_session tracker, which is not authorable here — so this rule has no
exceptions on the authoring path.
Step 5: Iterate conversationally
When proposing an override (not a from-scratch new workflow), render the diff against the baseline you retrieved in Step 3a/3b. List the baseline steps verbatim, then mark each step in your proposal as one of:
KEEP— unchanged from baselineMODIFY— same skill, but gate / confirmation / options changedINSERT— new step at this positionREMOVE— baseline step dropped from the override
Admins should always see what's being preserved vs. changed — silent omissions of baseline steps are the failure mode the Step 4 guard exists to prevent.
For from-scratch new workflows (no baseline), render the proposal without diff annotations.
Then ask:
"Does this look right, or would you like to change anything?"
Accept free-form feedback ("make step 2 use Linear not Jira", "rename it", "add a step between 3 and 4", "drop the gate on step 4") and update the draft. Repeat until the admin is satisfied. Do NOT save yet.
Step 6: Referential validation
Before calling forge__save_workflow, walk every step and
verify each skill_id resolves against either:
- the
skillsarray returned fromforge__list_skills_catalog, or - the inline
new_skillsarray you are about to create.
If any reference is unresolvable, surface it clearly:
"Step 3 references
skill_id: xyz-123, but that skill isn't in your catalog and isn't one of the new skills we're creating. How should I resolve it?"
Do NOT call forge__save_workflow until every reference resolves.
Step 7: Reachability check on applicable_expression
For every step whose applicable_expression is non-NULL, scan the
expression for state-key references. Common state keys:
epic_key, story_count, execution_started, stories_created,
ac_corrections_applied, bug_handoff_complete. Common phase flags:
_has_local_code, _has_project_tracking, _has_messaging,
_has_code_analysis, _workflow_active, _fb_engineering.
If a referenced key is never set by any step in your proposed workflow, that step will never run. Examples:
- Reviewing a PR (
review_prflow) withapplicable_expression: "execution_started == true"— review_pr never setsexecution_started(that flag is set bybegin_code_executioninimplement_feature/fix_bug). The step is unreachable. - A step gated on
story_count > 1in a workflow that doesn't runepic_story_breakdown. Same problem.
When you detect an unreachable step, do NOT save. Instead:
"Heads-up: step 4's gate
execution_started == truereferences a flag that no step in this workflow ever sets, so step 4 will never run. Do you want to: (a) drop the gate (set totrue), (b) replace it with a different expression, or (c) keep it as-is (the step will be permanently dormant)?"
Ask the admin for the choice. This is the structural fix for a whole class of misconfiguration — admins should never accidentally configure dormant steps because of an inherited expression they didn't mean to keep.
Step 8: Explicit admin confirmation
Show the final structured plan — workflow fields, ordered steps
(with skill_id and resolved applicable_expression),
example_invocation, and any new_skills being created. Then ask the
admin to choose "Save", "Keep editing", or "Cancel". If a structured
user-input tool is available, use it as the only tool call in that
response; otherwise ask the choice directly and wait for the answer.
Handle the answer:
- "Save" — proceed to Step 9 and call
forge__save_workflow. - "Keep editing" — loop back to Step 5 for more iteration.
- "Cancel" — end the session. Do NOT call
forge__save_workflow. - Skip / empty answer — treat as "Keep editing" (never as "Save"); loop back to Step 5. Never save on a skipped confirmation.
If the admin closes the session without answering, that's fine —
no partial state is persisted. Because forge__save_workflow is
never called without explicit "Save" approval, an abandoned session
leaves no trace in the Forge catalog.
Step 9: Save
Call forge__save_workflow with a payload whose top level matches the
manifest's workflow_preset fields, and whose steps entries match
the preset_step fields. The shape is data-driven by the manifest —
include every field where the admin chose a non-default value. The
server validates the payload against the schema before opening a
transaction; structural mismatches come back as invalid_request
with the offending field name (and step_index when the error is in
a step).
Skeleton (the actual fields you include depend on the manifest):
{
"workflow": {
"id": "...",
"name": "...",
"description": "...",
"team_name": "...",
"team_id": "...",
"example_invocation": "...",
"classifier_hint": "...",
"auto_classify": true,
"enabled": true,
"hydrator_config": {},
"ai_coding_policy": null,
"steps": [
{
"skill_id": "...",
"step_order": 0,
"enabled": true,
"confirmation_policy": "ai_judgment",
"applicable_expression": "epic_key != null",
"step_options": {},
"instructions": null,
"instruction_preamble": null,
"instruction_postamble": null,
"required_local_skills": null
}
]
},
"new_skills": [
{ "id": "...", "name": "...", "description": "...", "instructions": "..." }
]
}
Pass exactly one of team_id / team_name for team scope.
Omit both for org scope. If both are supplied, team_id wins and
team_name is ignored. Prefer team_name in AI-driven flows so
the admin never has to look up a UUID.
On success ({ ok: true, workflow_id, scope }), tell the admin the
workflow is live and can be reached via the AI classifier when a
user's request matches the workflow's classifier_hint.
Step 9a: Surface any soft warnings
When the success response also includes a warnings: [...] array, the
save committed but the server flagged something the admin should know
about before runtime. The warning is non-blocking — the workflow IS
live — but each entry deserves a sentence to the admin so they can
self-correct in a follow-up edit pass.
{
"ok": true,
"workflow_id": "...",
"scope": "org",
"warnings": [
{
"code": "missing_display_text_guidance",
"field": "new_skills.instructions",
"skill_id": "...",
"message": "..."
}
]
}
Currently the server emits one warning code:
| Warning code | What it means | Offer |
|---|---|---|
missing_display_text_guidance |
A new_skills.instructions body OR a workflow.steps.instructions full override emits needs_input without mentioning display_text — at runtime, any analytical output the executor renders before the tool call will die on the sub-agent boundary |
Tell the admin which skill / step is affected (use the skill_id / step_order fields), summarise the risk in plain language, and offer to add display_text guidance in a quick follow-up forge__save_workflow call. If the admin says "no, this skill genuinely doesn't produce findings worth surfacing", accept and move on — the warning is soft by design. |
Render the warnings inline with the success message, e.g.:
"✅ Workflow
security_review_strictsaved at org scope.One heads-up: your custom skill
internal_compliance_checkemitsneeds_inputbut doesn't mentiondisplay_text. If the skill scans the codebase or produces a structured findings table before asking the auditor for approval, those findings will be lost when a delegated sub-agent only relays the CHECKPOINT back to the parent. Want me to update the skill's instructions to includedisplay_text, or is this skill intentionally just a routing prompt?"
Do NOT block or re-prompt for save approval on a warning — the workflow already committed. The follow-up edit (if the admin chooses one) goes through the normal authoring loop.
Step 10: Handle structured errors
If forge__save_workflow returns isError: true with a structured
envelope, explain it plainly and offer to adjust:
| Error code | What it means | Offer |
|---|---|---|
admin_required |
Caller is not an org admin | STOP — not recoverable in this session |
invalid_request |
Payload failed schema validation | Show field (and step_index if present), fix the value, retry. When field is new_skills.consumes / new_skills.produces the envelope also carries unknown_capabilities and authorable_capabilities — replace the invented id with one from that list, or drop the declaration |
team_not_in_org |
Team scope but the supplied team_id does not belong to the caller's org |
Pick a team in your own org, or use org scope |
team_not_found |
team_name did not match any team in the org |
Show available_teams and ask which to use |
team_store_unavailable |
team_name used but team store is not wired |
Ask the admin to pass team_id directly |
duplicate_id |
Workflow or skill id already exists | Rename and retry |
referential_error |
A skill_id doesn't resolve on the server |
Fix the reference and retry |
constraint_violation |
Generic DB constraint failed | Show the server message and ask admin |
Never silently retry — always loop the admin in.
Tone
Brief, direct, collaborative. No "Step 1 of 7" framing, no filler. Show the draft, invite feedback, iterate, confirm, save.
Delete Path
Reached from Step 2b when the admin wants to remove an existing org- or team-scoped override. The shared Step 1 (catalog + role gate) still runs first — the admin gate is identical for both paths.
Step D1: Match the target against the catalog
Use the workflows array from Step 1. Filter to items where
scope !== 'system' — system defaults are never deletable, and the
store enforces this. Surface the scope explicitly on each candidate so
the admin sees what will be reset:
scope: 'org'— the org-level override. Removing it deletes the org's configuration for that workflow AND stops the workflow running: Forge's built-ins ship disabled, so with no org row there is nothing left to turn it on.scope: 'team'— a team-level override. Removing it reverts that one team to the org default, which usually keeps the workflow running. If the org has no preset of its own, the team falls all the way to the system default and the workflow stops running for that team too.
If the admin named a workflow directly (e.g. "delete the build-feature override for team Alpha"), resolve the target by id + scope and skip to Step D3.
If the reference is ambiguous — multiple matches, or the admin said something fuzzy like "remove my bug workflow override" — ask the admin to choose from the filtered candidates. Include each workflow name, scope, id, and team name when relevant.
If the filtered list is empty ("you don't have any overrides in your org or teams"), stop and say so — nothing to delete.
Step D2: Resolve team scope (team-scoped targets only)
If the target is team-scoped, confirm which team. If the admin named
the team directly, match against caller.teams by name. If the name
is ambiguous or missing, ask them to pick from the team list.
Prefer team_name over team_id when invoking the MCP tool — the
server resolves the name for you (team names are unique within an
org).
Step D3: Explicit admin confirmation
Before calling forge__delete_workflow, show the final target and
the consequence, then ask for explicit confirmation.
State the consequence for the target's ACTUAL scope — the two outcomes differ materially, and a single sentence covering both misleads on one of them. Never describe an org-scoped removal as a "reset to the Forge default" or a "revert to Forge's configuration": Forge's built-ins ship disabled, so for an org override that phrasing promises a restore and delivers a shutdown, on an action you are in the same breath telling the admin cannot be undone.
Org-scoped target:
"Removing this deletes your org's configuration for , and will also STOP RUNNING — Forge's built-in workflows ship disabled, so with no org configuration there's nothing left to turn it on. You can re-enable it from the dashboard, or re-author the override here, but this removal cannot be undone."
Team-scoped target:
"Removing this makes fall back to your org's configuration for ; the team keeps running it on the org's settings. This cannot be undone, but you can re-author the team override later."
If the target is team-scoped AND the org has no preset of its own (no
scope: 'org' row for that id in the Step 1 catalog), use the org
wording instead — the team falls through to the system default and the
workflow stops running for that team.
Ask the admin to choose "Remove override" or "Cancel". If a structured user-input tool is available, use it as the only tool call in that response; otherwise ask the choice directly and wait for the answer.
Handle the answer:
- "Remove override" — proceed to Step D4.
- "Cancel" — end the session; do NOT call
forge__delete_workflow. - Skip / empty answer — treat as "Cancel". Never delete on a skipped confirmation.
Step D4: Call forge__delete_workflow
Call with:
{
"workflow_id": "<from catalog>",
"team_id": "<uuid, optional>",
"team_name": "<name, optional>"
}
Pass exactly one of team_id / team_name for team-scoped
deletes. Omit both for the org-scoped delete. If both are supplied,
team_id wins.
On success ({ ok: true, workflow_id, scope }), tell the admin the
override is removed and the scope now runs the parent default.
Delete path — structured errors
If forge__delete_workflow returns isError: true, explain it
plainly and offer the right recovery:
| Error code | What it means | Offer |
|---|---|---|
admin_required |
Caller is not an org admin | STOP — not recoverable in this session |
org_required |
No org context on the session | STOP — ask admin to sign in to an org |
invalid_request |
workflow_id missing or blank |
Loop back to Step D1 and resolve the target |
team_not_in_org |
Team scope but the supplied team_id belongs to a different org |
Pick a team in your own org, or use org scope |
team_not_found |
team_name did not match any team in the org |
Show available_teams and ask which to use |
team_store_unavailable |
team_name was used but the team store is not wired |
Ask the admin to pass team_id directly |
system_default_protected |
Attempted to delete a system default (store-enforced) | STOP — system defaults cannot be deleted |
not_found |
No preset exists at the requested scope for that id | Nothing to delete; the admin may be confused about scope — confirm before proceeding |
store_unavailable |
The workflow config store is not connected | STOP — infrastructure issue, not the admin's problem |
internal_error |
Unexpected server failure | Show the message and ask the admin to retry |
Never silently retry. Always loop the admin in.