Skip to main content

Writing the Workflow Manifest

workflow.json is the manifest file for a workflow, defining all its metadata and behavior. The Workflow Manager discovers and loads workflows through this file.

Basic Structure

{
"id": "my-workflow",
"label": "My Workflow",
"version": "1.0.0",
"provider": "pass-through",
"display": {
"core": false,
"emoji": "🔧"
},
"inputs": { "unit": "parent" },
"parameters": {},
"execution": {},
"request": { "kind": "pass-through.run.v1" },
"hooks": {
"preflight": "hooks/preflight.mjs",
"applyResult": "hooks/applyResult.mjs"
}
}

Field Reference

Basic Identification

FieldRequiredTypeDescription
idstringUnique identifier; must not be duplicated. kebab-case recommended
labelstringUser-visible display name
versionstringSemantic version number, e.g., "1.0.0"
providerstringBackend type. See below for available values

Provider Values

ValueDescription
"pass-through"Pure local execution, no backend needed. Suitable for file operations, exports, etc.
"skillrunner"Execute skills via the Skill-Runner backend
"acp"Execute skills via the ACP backend
"generic-http"Call APIs via the Generic HTTP backend

provider determines which backend types the workflow is compatible with, and also determines which backends are shown as executable in the Dashboard.

Display Control

{
"display": {
"core": true,
"emoji": "📊"
},
"taskNameTemplate": "Processing: {query}",
"debug_only": false
}
FieldTypeDescription
display.corebooleanWhether to mark as a core workflow (prioritized display in Dashboard, with a core badge)
display.emojistringDisplay name prefix icon, e.g., "📖"
taskNameTemplatestringTask name template using {parameter name} placeholders, replaced with actual values at execution time
debug_onlybooleanWhen true, only visible in debug mode

Input Definition

{
"inputs": {
"unit": "attachment",
"accepts": {
"mime": ["text/markdown", "text/x-markdown", "application/pdf"]
},
"per_parent": {
"min": 1,
"max": 1
}
}
}
FieldDescription
unitInput unit type. "attachment" (attachment), "parent" (parent item), "note" (note), "workflow" (no item selection needed, triggered directly from Dashboard)
accepts.mimeAccepted MIME types (only applicable when unit: "attachment"). If not specified, all types are accepted
per_parent.minMinimum number of attachments per parent item
per_parent.maxMaximum number of attachments per parent item

When unit: "workflow", no user-selected items are required to trigger (e.g., "Create Topic Synthesis").

validateSelection — Selection Validation

validateSelection is declarative selection validation. It covers common scenarios like "skip items that already have results" or "only accept selections of specific types" — without writing any JavaScript.

{
"validateSelection": {
"select": {
"policy": "literature-source"
},
"require": {
"counts": {
"parents": 1
},
"allowMixed": false
},
"exclude": [
{
"kind": "generated-notes-all",
"noteKinds": ["digest", "references", "citation-analysis"]
}
]
}
}

select — Selection Policy

FieldTypeDescription
select.policystringSelection policy. Supported values below
select.unitstringOverride the input unit for selection validation. "attachment" / "parent" / "note" / "workflow"

Supported select.policy values:

PolicyDescription
input-unitAccept items matching the input unit
literature-sourceAccept literature sources (attachments or parent items with expandable attachments)
pdf-attachmentAccept only PDF attachments
selected-parentAccept parent items from the selection
generated-note-candidatesAccept candidate items for generated notes
digest-representative-imageTarget items for representative image extraction

require — Selection Requirements

FieldTypeDescription
require.counts.parentsnumberMinimum required parent items
require.counts.attachmentsnumberMinimum required attachment items
require.counts.notesnumberMinimum required note items
require.counts.childrennumberMinimum required child items
require.counts.totalnumberMinimum total required items
require.allowMixedbooleanWhether mixing different item types in selection is allowed

exclude — Exclusion Rules

FieldTypeDescription
exclude[]arrayList of exclusion rules. If any rule matches, the current item is skipped

Supported exclude.kind values:

kindDescriptionAdditional Parameters
generated-notes-allThe item already has generated notes of the specified typenoteKinds: list of note types, e.g., ["digest", "references", "citation-analysis"]
artifact-existsThe item already has the specified artifact (to avoid redundant execution)target: "deep-reading-html" / "translator-markdown" / "mineru-markdown"; parameter: optional language parameter for artifact matching

derive — Derived Selections

FieldTypeDescription
derive[]arrayDerived selection operations. "exportCandidates" — derive candidates for note export; "digestRepresentativeImageTarget" — derive representative image targets from digest notes

Example:

{
"validateSelection": {
"select": { "policy": "literature-source" },
"exclude": [
{ "kind": "artifact-exists", "target": "deep-reading-html" }
]
}
}

In this example, items that already have the deep reading HTML artifact are automatically skipped, without requiring manual filtering by the user.

Trigger Control

{
"trigger": {
"requiresSelection": false
}
}
FieldDescription
requiresSelectionWhether user-selected items are required to trigger. Defaults to true. When set to false, the workflow can be run from the Dashboard without selecting any items. Usually set to false when inputs.unit: "workflow"

Execution Control

{
"execution": {
"timeout_ms": 600000,
"poll_interval_ms": 2000,
"mcp": {
"requiredTools": ["search_items", "get_item_detail"]
},
"zoteroHostAccess": {
"required": false,
"allowWriteApprovalBypass": false
},
"feedback": {
"showNotifications": true
}
}
}
FieldDescription
timeout_msTimeout in milliseconds (only effective for Generic HTTP backends)
poll_interval_msPolling interval in milliseconds, controls progress check frequency
mcp.requiredToolsMCP tools required by this workflow (array of tool name strings)
zoteroHostAccess.requiredWhether Zotero host access is required (to read/write library data)
zoteroHostAccess.allowWriteApprovalBypassWhether write operation approval bypass is allowed
feedback.showNotificationsWhether to show execution notifications. Defaults to true; set to false to run silently

Execution mode (auto / interactive) has been moved to request.create.mode — see Request Kinds.

Result Retrieval

{
"result": {
"fetch": { "type": "bundle" },
"final_step_id": "finalize",
"expects": {
"result_json": "result/result.json",
"artifacts": [
"result/artifact1",
"result/artifact2"
]
}
}
}
FieldDescription
fetch.typeRetrieval method. "bundle" (download zip bundle), "result" (only retrieve result JSON)
final_step_idFor sequence workflows, specifies the id of the final step, used to determine the final result
expects.result_jsonExpected result JSON file path (relative to the runtime workspace)
expects.artifactsList of expected artifact file paths

Request Definition

Declarative request definition, mutually exclusive with hooks.buildRequest (if both exist, hooks.buildRequest takes priority).

{
"request": {
"kind": "skillrunner.job.v1",
"create": {
"skill_id": "my-skill",
"skill_source": "local-package"
},
"input": {
"upload": {
"files": [
{ "key": "source", "from": "selected.markdown" }
]
}
},
"poll": {
"interval_ms": 2000,
"timeout_ms": 600000
}
}
}

For detailed information on each kind, see Request Kinds.

Hook Declaration

{
"hooks": {
"preflight": "hooks/preflight.mjs",
"buildRequest": "hooks/buildRequest.mjs",
"normalizeSettings": "hooks/normalizeSettings.mjs",
"applyResult": "hooks/applyResult.mjs"
}
}
FieldRequiredDescription
applyResultRequired. Script path for post-execution result handling
preflightOptional. Runs after selection resolution and before request construction. It can continue, skip, short-circuit to applyResult, or replace one input unit with virtual request units
buildRequestOptional. Build the request to be sent to the backend. Mutually exclusive with the request field
normalizeSettingsOptional. Normalize user-set parameters

Input filtering has been replaced by the declarative validateSelection mechanism — see Selection Validation below.

preflight does not participate in menu enablement, debug-probe selection classification, or Host Bridge readiness checks. Keep selection constraints in validateSelection, keep provider request construction in buildRequest or request, and keep Zotero writes in applyResult.

Paths are relative to the directory containing workflow.json.

Localization

{
"i18n": {
"defaultLocale": "en-US",
"messages": {
"zh-CN": {
"label": "My Workflow",
"parameters.language.title": "Language"
}
}
}
}

See the Localization page for detailed information.

Complete Example: A Literature Analysis Workflow with Parameters

{
"id": "my-literature-analysis",
"label": "My Literature Analysis",
"version": "1.0.0",
"provider": "skillrunner",
"display": { "emoji": "📄" },
"inputs": {
"unit": "attachment",
"accepts": { "mime": ["application/pdf"] },
"per_parent": { "min": 1, "max": 1 }
},
"parameters": {
"language": {
"type": "string",
"title": "Output Language",
"default": "en-US",
"enum": ["en-US", "zh-CN", "ja-JP"],
"allowCustom": true
}
},
"execution": {
"mode": "auto",
"skillrunner_mode": "auto",
"timeout_ms": 600000
},
"request": {
"kind": "skillrunner.job.v1",
"create": { "skill_id": "literature-analysis" }
},
"result": {
"fetch": { "type": "bundle" },
"expects": {
"result_json": "result/result.json"
}
},
"hooks": {
"applyResult": "hooks/applyResult.mjs"
}
}

Next Steps