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:

FieldPurpose
schema_versionBundle schema version. Must be 2.
entrypointRelative path to the entry Harn module inside the package.
transitive_modulesSorted module manifest entries with source and bytecode BLAKE3 hashes.
stdlib_version / harn_versionRuntime and standard library versions used to build the package.
provider_catalog_hashBLAKE3 hash of the provider catalog used at build time.
tool_manifestTool names, providers, optional annotations, and schema hashes captured for review.
sbomSBOM document for package dependencies.
signatureOptional Ed25519 signature over the canonical bundle hash.
parent_trust_record_idOptional link into a parent OpenTrustGraph chain.
id / versionStable bundle identity for hosts and importers.
triggersDeclarations for GitHub, cron, delay, manual, webhook, or MCP wakeups.
workflowA normalized Harn WorkflowGraph with stable node ids.
prompt_capsulesSelf-contained continuation prompts keyed by capsule id.
policyAutonomy tier, tool policy, approval, retry, and catchup behavior.
connectorsProvider ids, scopes, and setup/status requirements.
environmentRepo setup profile, worktree policy, and command gates.
receiptsReplay metadata such as run id, event ids, workflow version, and graph digest.

Trigger kinds:

KindRequired fields
githubprovider: "github" and one or more events.
cronschedule.
delaydelay.
manualNo additional fields.
webhookwebhook_path.
mcpmcp_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.nodes for triggers, actions, agents/subagents, waits, approvals, connector calls, notifications, catchup/DLQ branches, and terminal states
  • normalized graph.edges connecting connector bindings, trigger dispatch, workflow control flow, catchup, DLQ, and terminal outcomes
  • node-scoped graph.diagnostics so hosts can annotate the exact workflow node instead of showing opaque bundle errors
  • graph.editable_fields JSON pointers for trigger config, prompt capsules, model/tool/approval/retry policy, catchup policy, and connector binding surfaces
  • graph.mermaid plus the top-level mermaid string 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.

opWhat it does
insert_nodeInserts a workflow node (agent, action, approval, notification, …).
add_edgeAdds an edge between two existing nodes.
upsert_prompt_capsuleInserts or replaces a prompt capsule for a node.
update_node_policyPatches task_label / prompt / system / tools / model_policy / capability_policy / approval_policy on an existing node.
update_bundle_policyPatches 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 operations list (patches must do something — silent no-ops are rejected).
  • Duplicate insert_node ids; unknown endpoints in add_edge; duplicate edges; collisions on upsert_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.widening array with a stable kind discriminator.

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).