The task file
Every HarmFlow task is one YAML file. This page describes its keys, for whoever writes a task by hand or
generates one. The authority is the task schema in harmflow-app,
contracts/schemas/task.schema.json:
where this page and the schema differ, the schema wins.
Where a task file lives
- One file per task. A task is
tasks/<id>.yamlin HarmFlow's data folder,~/Library/Application Support/HarmFlow, or the folder theHARMFLOW_HOMEvariable names. Itsmetadata.idmust match the file's name. - The daemon reads it. The daemon validates every task file against the schema when it starts. A file that fails validation is listed as invalid and never fires.
- Edits made outside HarmFlow count. The daemon notices a file that was changed, added, or removed outside HarmFlow within seconds. A changed file that fails validation keeps the last valid definition running, with a warning that names the error. An execution already running keeps the definition it started with.
- Naming. Keys are camelCase, closed-set values are snake_case, and the identifiers of named things — tasks, agents, skills — are kebab-case: lowercase letters, digits, and hyphens, up to 64 characters.
An example
A task that asks Claude Code every Monday at 9:00 to summarize a folder, and turns to Codex when Claude Code cannot run it:
apiVersion: harmflow/v1alpha1
kind: Task
metadata:
id: weekly-notes
name: Weekly notes
spec:
type: general
prompt: Summarize this week's changes in notes/.
workspace:
path: ~/Projects/example-notes
schedule:
cron: "0 9 * * 1"
timezone: America/New_York
harness:
preferred:
provider: claude-code
fallback:
candidates:
- provider: codex
permissions:
profile: workspace_write
execution:
timeoutMinutes: 30
The top-level keys
All four are required.
apiVersion— alwaysharmflow/v1alpha1.kind— alwaysTask.metadata—id, the task's identifier (required);name, 1 to 120 characters (required); anddescription, up to 2,000 characters.spec— what the task does, below. Onlyspec.typeis required in every task.
The task types
spec.type is one of three values, and decides which other keys the task takes.
general— a harness session whose behavior lives in its prompt, agents, and skills. It needspromptandharness, and takes nogithubsection.github— a harness session that works GitHub repositories. It needsprompt,harness, and thegithubsection. What it does in those repositories is written in its prompt, agents, and skills: there is no filter syntax.script— a program of yours, run instead of a harness session. It needs thescriptsection, and takes noprompt,agents,skills,workspace,harness, orexecution.allowExtraUsage.
The fields every task shares
enabled
spec.enabled— boolean, defaulttrue. A disabled task never fires, and a firing still waiting is dropped.
schedule
A task with no schedule runs only when started by hand. It takes cron or
interval, never both.
cron— a 5-field cron expression, with no seconds field, or one of the macros@yearly,@annually,@monthly,@weekly,@daily,@midnight, and@hourly, in lowercase.interval— a whole number of minutes, hours, or days:30m,2h,1d.timezone— an IANA time zone, such asEurope/Lisbon; the system's when absent.missedRun.policy—run_on_resume(default) orskip: what becomes of the firings missed while the Mac or the daemon was unavailable.run_on_resumeruns one catch-up execution for all of them;skipdrops them.
concurrency
overlappingRuns—queue(default) orskip. A task runs one execution at a time; a firing that arrives while it runs waits for it, or is skipped.
permissions
Translated to each harness's own controls. A restriction a harness cannot enforce runs with a
policy.unenforced warning.
profile—read_only,workspace_write(default), orcustom.restrictToWorkspace— boolean, defaulttrue.network—allow(default) ordeny.custom— for thecustomprofile:allowedToolsanddeniedTools, lists of tool names, andwritablePaths, a list of paths. Each path takes~and${VARIABLE}, and must lead inside the home folder, a mounted volume, or a folder Settings allows.
execution
timeoutMinutes— 1 to 1,440, default 60. It covers the whole execution, shared by all its attempts.cancelGraceSeconds— 1 to 600, default 30: how long the execution's processes have between the signal that asks them to stop and the one that kills them.allowExtraUsage— boolean, defaultfalse. Whether the execution may spend usage billed beyond what its harness's plan includes. When false, an attempt does not start on a harness whose quota is exhausted, and a Claude Code attempt that turns to extra usage is ended.rateLimit— how an attempt retries on the same harness after a rate limit:maxRetries, 0 to 20, default 5;initialWaitSeconds, 1 to 3,600, default 60; andmaxWaitSeconds, 1 to 86,400, default 900.
notifications
native.enabled— boolean, defaulttrue: macOS notifications for the task.native.events— which events notify, fromexecution.started,execution.completed,execution.failed,execution.cancelled,execution.needs_attention,pull_request.created,pull_request.updated,test.failed,harness.unavailable, andharness.no_alternative. By default:execution.completed,execution.failed,execution.needs_attention,pull_request.created, andharness.no_alternative.
The fields of a task that runs a harness
A general or github task.
prompt— the instructions given to the harness at the start of each execution (required).agents— agents cited by name, each available to the session as a subagent; none becomes its main agent. Built-in agents carry thehf-prefix; your own live in the harness's configuration.skills— skills cited by name. Built-in skills carry thehf-prefix; your own are the ones the task's harness finds for its workspace; a skill a plugin installed is cited as<plugin>:<skill>, exactly as its harness reports it.workspace.path— where the session works. It takes~and${VARIABLE}.origin— written by HarmFlow when a prompt is copied from an agent: that agent'sagentname and ahashof what was copied. A record only: the task never runs that agent.
harness
preferred(required) — the harness the task runs on, a profile of four fields:provider—claude-codeorcodex(required).model— a model the installed harness accepts;default(the default) leaves the harness's own.effort— a reasoning effort the installed harness accepts;defaultleaves the harness's own.ephemeral— boolean, defaulttrue: the session is not kept. Only a harness that can keep its session acceptsfalse.
fallback.candidates— the profiles to turn to, in order, when the preferred harness cannot run the task.fallback.on— what triggers that switch:quota_exhausted,harness_unavailable, or both, the default. A rate limit retries on the same harness instead (execution.rateLimit).fallback.maxSwitches— 0 to 5, default 1: how many times one execution may switch.
The harnesses says what each provider is given and how a failover happens.
The github section
repositories(required, at least one) — each withname, theowner/repoon GitHub (required);localPath, its clone on the Mac (required); andbaseBranch, defaultmain.queue— the limits of each cycle:ordering,oldest_first(default) ornewest_first;maxDiscoveredPerCycle, 1 to 100, default 10;maxItemsPerCycle, 1 to 20, default 1;resumeStrategy,resume_first(default),new_first, oralternate;maxResumeAttemptsandmaxFailedRetries, 0 to 20, default 3 each.delivery.testRepairAttempts— 0 to 10, default 2.
The script section
It takes command or inline, never both.
command— a program and its arguments, as a list, started with no shell, so no argument is expanded or split. The program comes first: a name looked up on thePATH, or an absolute path. Only the program takes~and${VARIABLE}.inline—interpreter, a program and its arguments as above, andsource, the script. The daemon writes the source to a file in the execution's folder and adds that file's path to the interpreter's arguments.workingDirectory— where the program starts; your home folder when absent.environment— variables added to the program's environment, each replacing one of the same name. Names that start withHARMFLOW_are HarmFlow's own and are refused.systems— the systems the task runs on, frommacos,linux, andwindows; every system when absent.
apiVersion: harmflow/v1alpha1
kind: Task
metadata:
id: nightly-fetch
name: Nightly fetch
spec:
type: script
schedule:
interval: 1d
script:
command: ["git", "-C", "/Users/Shared/example-repo",
"fetch", "--all"]
An imported script task is saved disabled, whatever its file says: it runs a program with your rights, so it runs only once you have read it and enabled it.