Portable workflow bundles
A workflow bundle is Harn's local-first artifact for durable engineering automations. It is designed to run on a trusted laptop under Burin/Harn and to remain importable into Harn Cloud later without changing the workflow's durable identity, graph, policy, or replay metadata.
The canonical package format is .harnpack: a deterministic tar.zst archive
with harnpack.json and sbom.spdx.json at the archive root. The manifest can
also be read as plain JSON during authoring. The current schema version is 2.
For an end-to-end walkthrough that authors a bundle, validates it, previews the graph, and runs a deterministic local receipt — all without paid credentials — see the workflow authoring quickstart.
harn workflow validate --bundle docs/fixtures/workflow-bundles/github-pr-monitor.bundle.json --json
harn workflow preview --bundle docs/fixtures/workflow-bundles/github-pr-monitor.bundle.json --json
harn workflow preview --bundle docs/fixtures/workflow-bundles/github-pr-monitor.bundle.json --mermaid
harn workflow run --bundle docs/fixtures/workflow-bundles/github-pr-monitor.bundle.json --json
Contract
Top-level bundle fields:
| Field | Purpose |
|---|---|
schema_version | Bundle schema version. Must be 2. |
entrypoint | Relative path to the entry Harn module inside the package. |
transitive_modules | Sorted module manifest entries with source and bytecode BLAKE3 hashes. |
stdlib_version / harn_version | Runtime and standard library versions used to build the package. |
provider_catalog_hash | BLAKE3 hash of the provider catalog used at build time. |
tool_manifest | Tool names, providers, optional annotations, and schema hashes captured for review. |
sbom | SBOM document for package dependencies. |
signature | Optional Ed25519 signature over the canonical bundle hash. |
parent_trust_record_id | Optional link into a parent OpenTrustGraph chain. |
id / version | Stable bundle identity for hosts and importers. |
triggers | Declarations for GitHub, cron, delay, manual, webhook, or MCP wakeups. |
workflow | A normalized Harn WorkflowGraph with stable node ids. |
prompt_capsules | Self-contained continuation prompts keyed by capsule id. |
policy | Autonomy tier, tool policy, approval, retry, and catchup behavior. |
connectors | Provider ids, scopes, and setup/status requirements. |
environment | Repo setup profile, worktree policy, and command gates. |
receipts | Replay metadata such as run id, event ids, workflow version, and graph digest. |
Trigger kinds:
| Kind | Required fields |
|---|---|
github | provider: "github" and one or more events. |
cron | schedule. |
delay | delay. |
manual | No additional fields. |
webhook | webhook_path. |
mcp | mcp_tool. |
harn workflow validate checks schema version, manifest metadata, relative
package paths, BLAKE3 hash syntax, stable workflow/node ids, workflow graph
validity, trigger references, prompt capsule references, policy values,
connector identity, and environment worktree policy. The validation report
includes a stable graph_digest over canonical graph JSON so receipts and
replay runs can pin the exact workflow graph.
Bundle identity for .harnpack archives is BLAKE3 over the canonical manifest
bytes plus the sorted content hashes. Re-packing the same manifest and content
produces the same bundle hash.
harn pack --sign --key <private.pem> signs the canonical bundle hash with an
Ed25519 PKCS#8 PEM key, embeds the signature in harnpack.json, and appends an
OpenTrustGraph release record. harn pack --unsigned skips the manifest
signature but still appends the release record at autonomy tier suggest.
harn pack --json emits a JsonEnvelope summary with the bundle hash, output
path, archive size, signature presence, SBOM counts, bytecode/debug-symbol
metadata, and the full manifest.
harn pack --exclude-secrets refuses to bundle entrypoints and skips imported
non-Harn assets that match a conservative secret-bearing glob: .env,
.env.*, *.pem, *.key, credentials*, and any path under a secrets/
directory. Skipped assets are reported as structured JSON warnings and in
manifest.metadata.skipped_assets. Pass --include-secrets to be explicit
about the default behavior in release pipelines.
Verifying a .harnpack
harn pack verify <bundle.harnpack> reads a bundle back, recomputes the
canonical bundle hash, verifies the embedded Ed25519 signature (if any), and
cross-checks every transitive_modules[*].source_hash_blake3 and
harnbc_hash_blake3 against the in-archive payload. The command exits non-zero
on any mismatch and emits structured error codes (verify.signature_failed,
verify.source_mismatch, verify.bytecode_mismatch,
verify.recorded_hash_mismatch, verify.archive_failed,
verify.unsigned).
Pass --strict to additionally cross-check SBOM package hashes against the
archive payloads they describe when the bundle format carries a corresponding
entry. Today that primarily hardens module:* SBOM rows and surfaces drift as
verify.sbom_mismatch.
By default, unsigned bundles fail verification. Pass --allow-unsigned to
accept a bundle without an Ed25519 signature (useful in local development).
For compliance gates, pass --require-trusted-signer to require that the
bundle signer resolve from the trusted signer registry. Add
--trust-policy policy.json to layer a JSON allowlist on top:
{
"signer_registry_url": "./signers",
"trusted_signers": ["<sha256-fingerprint>"]
}
When that gate fails, the verifier exits with verify.untrusted_signer.
harn pack verify --json emits a JsonEnvelope with the recomputed
bundle_hash, signature presence/verification flags, signing key fingerprint,
and per-bundle counts (module_count, content_entry_count). The schema is
harn --json-schemas --command "pack verify".
harn run <bundle.harnpack> also runs the same verification before extracting
into the content-addressed pack cache and executing the entrypoint — harn pack verify is the standalone equivalent for CI and supply-chain audits.
Preview
harn workflow preview --json emits the contract Burin GUI/TUI surfaces need
before committing autonomous resources:
- bundle and workflow identity
- graph digest
- validation diagnostics
- trigger declarations
- connector requirements
- environment requirements
- normalized
graph.nodesfor triggers, actions, agents/subagents, waits, approvals, connector calls, notifications, catchup/DLQ branches, and terminal states - normalized
graph.edgesconnecting connector bindings, trigger dispatch, workflow control flow, catchup, DLQ, and terminal outcomes - node-scoped
graph.diagnosticsso hosts can annotate the exact workflow node instead of showing opaque bundle errors graph.editable_fieldsJSON pointers for trigger config, prompt capsules, model/tool/approval/retry policy, catchup policy, and connector binding surfacesgraph.mermaidplus the top-levelmermaidstring for a low-cost debug rendering
JSON is the product contract. --mermaid prints only the Mermaid view for
quick debugging and docs snippets; hosts should use --json for editing and
validation.
Local run receipts
harn workflow run --bundle <path> --json materializes a deterministic local
receipt for the current bundle. The MVP runner walks the reachable graph from
the entry node, records completed node receipts, attaches trigger/event ids, and
emits the connector, policy, environment, workflow version, and graph digest
needed for replay.
The command does not call cloud services or run mutating tools by itself. Hosts remain responsible for approval UX, concrete file mutations, and notifications; Harn owns the portable contract, graph digest, deterministic receipt shape, and replay metadata.
Use --event-id and --trigger-id to pin a replayed event:
harn workflow run \
--bundle docs/fixtures/workflow-bundles/github-pr-monitor.bundle.json \
--trigger-id github-pr-updated \
--event-id github:event:42 \
--json
The same bundle and replayed event produce the same receipt bytes, which lets Burin compare local executions and later cloud imports against one stable artifact.
Authoring skill pack
The examples/skill-packs/workflow-authoring/ directory ships a Harn skill
pack that teaches an agent (including 4–8B local models such as qwen, gemma,
or llama.cpp) how to author bundles that pass harn workflow validate. It
contains:
SKILL.md— a Claude Code / Agent Skills compatible card the model loads before responding.prompting.md— explicit XML output envelope (<bundle>,<rationale>,<verify>), a hard-rule checklist that mirrors the validator, and a validation-and-retry loop.recipes/{pr-monitor,pr-repair}/bundle.json— validated golden bundles for the two steel-thread workflows from the parent epic.cases/*.case.json— eval cases pinning each prompt to its golden bundle and a list of structural assertions (entry node id, required trigger kinds, required approval nodes, etc.).eval.harn— a Harn driver that feeds a case to any provider/model, extracts the<bundle>block, runs the validate → preview → run pipeline, and emits a JSON report.
Run the offline eval (no network — replays the golden):
harn run examples/skill-packs/workflow-authoring/eval.harn -- \
--case examples/skill-packs/workflow-authoring/cases/pr-monitor.case.json
Run a live eval against any provider / model (point HARN_BIN at the binary
under test if it is not on PATH):
harn run examples/skill-packs/workflow-authoring/eval.harn -- \
--case examples/skill-packs/workflow-authoring/cases/pr-monitor.case.json \
--provider ollama --model qwen3:4b
crates/harn-cli/tests/workflow_authoring_eval.rs is the CI regression gate.
It validates every recipe golden and every case's structural assertions, so a
new case automatically extends the gate.
Workflow patch proposals
Once a bundle exists, agents can propose bounded, auditable edits with a workflow patch instead of regenerating the whole bundle. A patch is a flat list of operations Harn applies to a copy of the bundle, then re-runs the validator and computes a structural diff plus a capability-ceiling delta. The patch contract is intentionally small — each op maps directly onto an "insert a verifier here" or "narrow this node's tool policy" intent.
op | What it does |
|---|---|
insert_node | Inserts a workflow node (agent, action, approval, notification, …). |
add_edge | Adds an edge between two existing nodes. |
upsert_prompt_capsule | Inserts or replaces a prompt capsule for a node. |
update_node_policy | Patches task_label / prompt / system / tools / model_policy / capability_policy / approval_policy on an existing node. |
update_bundle_policy | Patches autonomy_tier / tool_policy / approval_required / retry / catchup at the bundle level. |
harn workflow patch validate \
--bundle docs/fixtures/workflow-bundles/github-pr-monitor.bundle.json \
--patch docs/fixtures/workflow-bundles/pr-monitor-verifier.patch.json \
--parent-ceiling docs/fixtures/workflow-bundles/parent-act-with-approval.policy.json \
--json
harn workflow patch apply --bundle ... --patch ... --out ...
harn workflow patch preview --bundle ... --patch ... --mermaid
Failure modes the validator enforces:
- Empty
operationslist (patches must do something — silent no-ops are rejected). - Duplicate
insert_nodeids; unknown endpoints inadd_edge; duplicate edges; collisions onupsert_prompt_capsule.node_id. - Any patch that widens the parent ceiling along tools,
capabilities, side-effect level, workspace roots, connector scopes,
command gates, or autonomy tier. Each violation lands in the report's
capability_delta.wideningarray with a stablekinddiscriminator.
Safe Harn function tools
harn workflow function-tools --json enumerates the allowlisted Harn
functions an agent may call from inside the patch-authoring loop. Each
descriptor carries an ACP-aligned ToolAnnotations block (kind +
side-effect level + capability requirements) so a host can wire the tool
straight into a model surface. The current allowlist is read-only or
pure-think only:
workflow_bundle_validate/workflow_bundle_preview/workflow_bundle_capability_ceiling— inspect a bundle on disk.workflow_patch_validate— apply + validate a patch in memory and return the report.
Adding a function to the allowlist is a deliberate, reviewed change in
crates/harn-vm/src/orchestration/safe_function_tools.rs. Anything outside
the list is not exposed to agents.
Nested invocation ceiling
When a script or host launches another Harn invocation (harn run,
harn workflow run, harn supervisor fire/replay, a Burin harness),
Harn projects the target's requested ceiling and rejects launches that
would widen the parent's. harn workflow nested-ceiling --bundle <path> --parent <policy> exposes the same scanner so hosts can sanity-check
before launch:
harn workflow nested-ceiling \
--bundle docs/fixtures/workflow-bundles/github-pr-monitor.bundle.json \
--parent docs/fixtures/workflow-bundles/parent-act-with-approval.policy.json
The scanner also accepts a Harn script source (token-level capability
projection) and a Burin harness manifest (explicit capability_ceiling
block, falling back to "request everything" if the manifest is silent —
silence is treated as the most invasive request, so the parent rejects
rather than rubber-stamps).