Appearance
Workflow packages
Each discoverable workflow is an immediate child directory of WORKSPACE_ROOT/workflows. It contains a mandatory manifest.yaml, a referenced flow file, and optional scripts, prompts, or fixtures.
Server startup uses strict discovery by default: invalid workflow or driver packages fail startup. Set WORKSPACE_DISCOVERY_MODE=degraded to retain healthy packages; skipped items are logged and make /ready report a degraded state.
Manifest
yaml
schemaVersion: 1
id: summarize-document
name: Summarize document
description: Produce a concise summary.
flow: flow.yaml
permissions:
tools: []
webhook:
endpoints:
base: async
request:
type: json
inputSchema:
fields:
- name: document
type: textarea
required: true
schedule:
inputSchema:
fields:
- name: document
type: textarea
required: trueid is lower-kebab-case and must equal the directory name. flow is relative and cannot escape the workflow directory. Scheduled workflows cannot use multipart-file or require any multipart file field (minItems must be omitted or 0). Add tool metadata to expose the workflow as a composable tool; tool names are unique and cannot contain /.
permissions is optional. If omitted, it is treated as tools: [].
tool.authorization.grants and schedule.authorization.grants may reference capability IDs declared in permissions.tools. Grant IDs must be unique per trigger and every referenced ID must exist in the manifest capability list.
yaml
schemaVersion: 1
id: post-document
name: Post document
description: Post one document and archive it.
flow: flow.yaml
permissions:
tools:
- id: post
tool: accounting/post
- id: archive
tool: storage/archive
tool:
name: documents/post-and-archive
description: Post and archive a document.
inputSchema:
type: object
authorization:
grants: [post, archive]
schedule:
authorization:
grants: [archive]Use multipart-file for the backward-compatible single-file input. Use multipart when a webhook needs text fields and repeated files:
yaml
request:
type: multipart
inputSchema:
fields:
- name: prompt
type: textarea
required: false
fileFields:
- name: attachments
minItems: 0
maxItems: 10
maxBytes: 26214400
allowedMimeTypes: ["*/*"]
maxTotalBytes: 104857600Each file is exposed through flow.input as { filename, mimeType, size, data }, where data is base64. Repeating a text field, exceeding count or size limits, sending an empty file, or using a MIME type outside its allowlist rejects the request before execution. A multipart webhook also accepts JSON when no files are supplied, allowing text-only callers to avoid multipart encoding.
Flow document
yaml
summary: Summarize a document
version: 1
start: summarize
nodes:
- id: summarize
type: llm-prompt
model:
role: summarization
input:
document:
expr: context.input.document
outputSchema:
type: string
connections: []Node IDs are unique. Connections reference existing nodes and may include a route for branching nodes. The graph must be reachable from start, obey each node type's structural rules, and avoid uncontrolled cycles.
version in flow.yaml is optional metadata. Current runtime validation accepts numeric or string values and does not change execution behavior by version value.
External files
Script and prompt nodes accept bounded workflow-relative file references:
yaml
- id: prepare
type: script
script:
source:
inline: prepare.ts
- id: summarize
type: llm-prompt
prompt:
inline: summarize.mdResolution is relative to flow.yaml; absolute paths and traversal outside the configured root fail loading. The inline property is supported only at script.source.inline and prompt.inline.
Discovery lifecycle
Packages are scanned on server startup. Additions and edits take effect after restart.
In lenient discovery mode, startup continues when a package is invalid (for example missing manifest.yaml, invalid manifest schema, unresolved workflow dependency, or dependency cycle). The server logs one warning per skipped package with directory and error context.
When embedding @faimulus/core, discoverWorkflows is strict by default and throws on invalid packages unless called with { strict: false }.