Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

Model-job reference

Import std/model_job for the public model-job, media-asset, ComfyUI, and test APIs.

import {
  ModelBackend,
  ModelJobRequest,
  ModelJobRunOptions,
  model_job_run_result,
} from "std/model_job"

The model-job explanation describes why this boundary exists. This page lists its contracts.

Request

ModelJobRequest has these fields:

FieldTypeMeaning
idstringCaller-generated request ID.
taskModelTaskimage.generate, image.edit, audio.generate, video.generate, embedding, or custom.
promptstringRequired except for embedding.
outputModelOutputSpecRequired MIME type and optional width, height, duration, or count.
modelstring?Model selected by the caller.
inputslist<MediaAsset>?Input assets for editing or other conditional work.
seedint?Reproducible random seed when the backend supports one.
paramsdict?Backend-specific settings.
metadatadict?Application data carried with the request.

model_job_request_result validates the common fields before the backend is called. model_job_request_digest hashes the fields that affect model output. Replay requires the same digest.

Backend

A backend has one ID and three functions:

type ModelBackend = {
  id: string,
  submit: fn(Harness, ModelJobRequest) -> Result<ModelJobObservation, ModelJobError>,
  inspect: fn(Harness, ModelJob) -> Result<ModelJobObservation, ModelJobError>,
  cancel: fn(Harness, ModelJob) -> Result<ModelJobObservation, ModelJobError>,
}

submit returns the first observation. inspect returns the latest provider state. cancel requests cancellation and returns the resulting observation. The backend may use harness.net, a connector, or a local process.

Map every provider status through model_job_state_result. An unknown status is an invalid_state error, not a running job.

State transitions

The closed state set is queued, running, succeeded, failed, and canceled.

Current stateAllowed next states
queuedrunning, succeeded, failed, canceled
runningsucceeded, failed, canceled
terminal statethe same state only

succeeded, failed, and canceled are terminal. A canceled job cannot later succeed. model_job_transition_result enforces this rule for all backends.

Run options and events

model_job_run_result(harness, backend, request, options) submits and polls one job. ModelJobRunOptions accepts:

FieldDefaultMeaning
timeout_ms300000Total polling deadline.
interval_ms500Delay between inspections.
max_attempts0Inspection limit; 0 has no attempt limit.
asset_rootruntime asset rootContent-addressed output directory.
session_idnoneAgent session that receives model_job transcript events.
on_eventnoneCallback for UI or CLI progress.

Events are ordered. Their kinds are submitted, state_changed, progress, output, and failed. Every event includes the request ID, job ID, backend, state, and monotonic timestamp.

After submission, polling, timeout, transition, and asset-storage errors emit a terminal failed event before model_job_run_result returns the typed error. This gives transcript and UI consumers a terminal state even when no receipt is created.

Run a job from an interactive app

model_job_run_result is convenient for a CLI or batch step that can wait. An interactive app should retain a ModelJobRun and advance the job one step at a time:

FunctionWork performed
model_job_submit_resultValidate and submit once; return the first job and event.
model_job_step_resultInspect once and append the checked state or progress event.
model_job_finish_resultStore a finished run's outputs as verified assets.
model_job_cancel_run_resultCancel a non-terminal run and append its event.

ModelJobRun can be encoded as JSON. It contains the current job, ordered events, start time, and inspection count. Calling model_job_step_result on a finished run returns it unchanged. That makes a scheduled check safe when it crosses with a cancel or final response. Each step also enforces timeout_ms and max_attempts, so remembered jobs settle instead of polling indefinitely after a provider loses their remote state.

The synchronous model_job_run_result uses these same functions, so Harn has one implementation for both waiting and interactive callers.

Receipt and media asset

A successful ModelJobReceipt contains the final job, ordered events, request digest, backend ID, and verified assets.

Each MediaAsset includes:

  • an asset://sha256/<digest> URI and SHA-256 digest;
  • MIME type, byte size, kind, and current path;
  • optional dimensions, duration, producing job, and metadata;
  • optional parents — parent asset URIs for edit/candidate lineage.

Image-edit jobs copy verified request input URIs into each output asset's parents list so sketch → candidate lineage stays structural.

media_asset_store_result rejects bytes whose signature does not match the declared MIME type. media_asset_verify_result re-reads the file and rejects a changed digest, size, MIME type, or identity.

Exact-text composition

Import std/media/composition when an app must preserve user-authored lettering instead of hoping an image model renders it. Build a harn.design_document.v1 with at least one image layer and one text layer, then call design_document_export_result to write:

  • a reopenable .design.json document;
  • an editable .svg whose text nodes carry the exact strings;
  • a .png of the verified image layer for hosts that need a bitmap.

design_document_with_exact_text is the common logo helper: one background asset plus one lettering layer. Exported SVG and PNG assets record parent URIs so design lineage stays structural alongside model-job parents.

Test and replay backends

model_job_fake_backend(id, observations) consumes a fixed observation list. It has no network effects. Use it for job-state and UI tests.

model_job_replay_backend(receipt) replays recorded states and asset paths. It rejects a different request digest and never falls back to the live provider.

When a test-bench run emits a tape, model-job transcript events are recorded as model_job tape kinds with job identifiers, state, redacted event payload, and output-asset digests. Fake and replay backends therefore exercise the same cassette shape as a live adapter without calling a model.

comfyui_backend(endpoint, build_workflow, options) implements the same interface over ComfyUI. Its graph builder is separate, so any API-format ComfyUI workflow can use the backend. comfyui_flux2_klein_workflow is the included text-to-image graph. When a request has input assets, the backend verifies and uploads them before building the graph; their ComfyUI names are available in request.params.comfy_input_names. Use comfyui_flux2_klein_edit_workflow for a one-image FLUX.2 Klein edit. Its graph follows ComfyUI's official Klein image-edit template.

openai_responses_image_backend(options) completes generation or editing in its submit call. It uses the OpenAI Responses API image-generation tool, accepts verified media assets as edit inputs, and returns base64 output as normal model-job assets. The API key is a required option and is never added to events or receipts.

Run the local backend with the ComfyUI how-to guide, or run the hosted backend with the OpenAI image how-to guide.