@hasna/contracts
Shared schemas, TypeScript types, validators, fixtures, and CLI checks for Hasna open-source agent infrastructure.
@hasna/contracts is not a database, daemon, scheduler, or orchestrator. It is
the shared language used by the open-* packages when they exchange refs, runs,
decisions, costs, context packs, validation plans, trajectories, and proof
bundles.
Purpose
Agents lose time and tokens when every package invents its own shape for evidence, actors, runs, costs, decisions, and status. This package defines the boring contracts those packages can validate at CLI, MCP, SDK, API, event, and file boundaries.
The goal is to make integration work deterministic:
- Producers emit a small object with a known
schemaid. - Consumers validate the object before spending model context on it.
- Failed validation returns concrete field errors instead of vague agent prose.
- Work products can link task ids, run ids, evidence ids, and proof ids without custom glue scripts.
Install
bun add @hasna/contractsCLI
The CLI is Bun-based. Use the package bin entries (contracts or
contracts-cli) from Bun/npm scripts; do not import @hasna/contracts/cli as a
Node library.
List known schema ids:
contracts schemas
contracts schemas --jsonValidate a file using its embedded schema field:
contracts validate examples/evidence-ref.valid.jsonValidate against an explicit schema id:
contracts validate --schema hasna.evidence_ref.v1 examples/evidence-ref.valid.jsonCheck package fixtures. Files ending in .valid.json must pass, and files
ending in .invalid.json must fail for schema reasons. Empty fixture sets,
unknown schemas, and malformed JSON are harness failures.
contracts conformance examples
contracts conformance --json examplesScan a package source tree or packed .tgz before publishing. The scan focuses
on package manifests, lockfiles, source/runtime surfaces, config files, and
packed artifacts. It intentionally ignores docs/examples so packages can
document forbidden legacy edges without failing their own checks.
contracts no-cloud-scan .
contracts no-cloud-scan --json .
contracts no-cloud-scan --manifest app-cloud.manifest.json .
contracts no-cloud-scan --json hasna-todos-0.11.62.tgzTypeScript
import {
SCHEMA_IDS,
parseEmbeddedContract,
parseContract,
validateEmbeddedContract,
validateContract,
type EvidenceRef,
type EvidenceRefInput
} from "@hasna/contracts";
const draft: EvidenceRefInput = {
schema: SCHEMA_IDS.evidenceRef,
id: "ev_tests",
createdAt: "2026-06-27T10:00:00.000Z",
kind: "command_output",
uri: "artifact://runs/run_123/tests.txt"
};
const result = validateContract(SCHEMA_IDS.evidenceRef, draft);
if (!result.success) {
return { ok: false, issues: result.error.issues };
}
const evidence: EvidenceRef = parseContract(SCHEMA_IDS.evidenceRef, draft);
const embedded = validateEmbeddedContract(draft);
const parsedBySchemaField = parseEmbeddedContract(draft);Use validateContract at boundaries when you want to return structured issues.
Use parseContract when invalid input should throw ContractValidationError.
Use validateEmbeddedContract or parseEmbeddedContract when the producer sends
a top-level object and the consumer should dispatch from its schema field.
Input aliases such as EvidenceRefInput describe producer payloads before Zod
defaults are applied; output aliases such as EvidenceRef describe parsed data.
Contract Catalog
hasna.actor_ref.v1: agent, human, service, model, workflow, or system actor.hasna.resource_ref.v1: portable reference to a task, repo, file, run, session, loop, knowledge item, report, proof bundle, or other shared resource.hasna.evidence_ref.v1: pointer to files, command output, screenshots, logs, diffs, reports, artifacts, URLs, videos, HAR captures, test results, metrics, traces, or other evidence.hasna.work_run.v1: normalized run receipt for agent, command, workflow, loop, eval, test, deploy, or review work.hasna.decision_envelope.v1: decision record with selected/skipped resources, rationale, actor, costs, obligations, redactions, and evidence.hasna.cost_estimate.v1: money and token estimates with provider, model, account, basis, and resource references.hasna.capability_card.v1: machine-readable description of a package command, MCP tool, API operation, workflow, or agent skill.hasna.context_pack.v1: bounded context bundle with objective, resources, evidence, constraints, and token budget.hasna.agent_trajectory.v1: compact trace of agent steps, tool calls, decisions, blockers, and final outcome.hasna.validation_plan.v1: deterministic checks a package or agent should run.hasna.proof_bundle.v1: reviewable validation result that ties a subject to checks, evidence, verifier, and verdict.hasna.scaffold_manifest.v1: public, portable description of a scaffold's type, status, capabilities, output shape, env vars, scripts, and validation checks.hasna.scaffold_install_record.v1: portable receipt for a scaffold install against a target repo or project, including installer, status, generated resource refs, evidence, and proof refs.hasna.app_cloud_manifest.v1: app-owned cloud boundary declaration for a package that uses its own cloud resources, local cache, and conflict policy without depending on shared@hasna/cloudoropen-cloudruntimes.hasna.no_cloud_evidence_pack.v1: prepublish/CI evidence pack for package manifest, lockfile, source/runtime config, packed artifact, published metadata, and app-cloud-manifest scans.
Every top-level contract includes a literal schema field. Consumers should
reject objects whose embedded schema does not match the validator being used.
Top-level EvidenceRef documents are dereferenceable and require a URI; nested
evidence pointers may be compact { id } links when the enclosing bundle or
store can resolve them.
Capability cards use compact kinds: package commands, MCP tools, and API
operations map to tool; workflow runners map to service or lane; agent
skills map to agent; model routes map to model; connectors map to
connector.
Common resource-kind mappings:
| Repo domain | Preferred resource kinds |
|---|---|
open-todos |
task, project, verification, proof_bundle |
open-loops |
loop, workflow, run, artifact |
open-actions |
action, tool, event; decisions are emitted as DecisionEnvelope contracts, not resource kinds |
open-automations |
action, tool, event; deterministic recipe decisions are emitted as DecisionEnvelope contracts |
open-sessions |
session, run, machine, artifact |
open-context |
context_pack, file, url, knowledge |
open-knowledge / open-mementos |
knowledge, memento, context_pack |
open-files |
file, artifact, url |
open-evals |
eval, verification, report, proof_bundle |
open-economy |
cost, budget; budget choices are emitted as DecisionEnvelope contracts, not resource kinds |
open-monitor |
alert, incident, machine, report |
Package Boundaries
@hasna/contracts owns schemas, types, validators, examples, and conformance
helpers. Owning packages still own storage and behavior.
open-todosowns tasks, task plans, locks, comments, and task evidence.open-loopsowns loop and workflow execution.open-eventsowns event envelopes, channels, delivery, replay, and notification semantics.open-actionsowns executable action manifests.open-automationsowns deterministic product/app automations and connector/action recipes. It does not own agent workflow invocation, admission queues, task/PR/review worker routing, or canonical workflow run artifacts.open-sessionsowns transcript and trajectory ingestion.open-contextowns context-pack construction and retrieval.open-knowledgeowns durable knowledge records and promotion workflows under.hasna/knowledge.open-filesowns artifact storage, file indexing, and dereference logic.open-mementosowns memory lifecycle and recall.open-reportsowns rendered reports and proof presentation.open-evalsowns evaluation execution and scored validation results.open-economyowns budget, cost, and usage policy decisions.open-monitorowns fleet health classification and alerting.iapp-scaffoldsowns scaffold templates, registry behavior, install/setup behavior, MCP tools, CLI UX, and private/internal scaffold metadata. It should validate public scaffold manifests and install records with@hasna/contracts, but@hasna/contractsmust not import or executeiapp-scaffolds.- Each open-source app that needs cloud support owns that cloud integration in
its own package and can publish an
AppCloudManifest.@hasna/contractsvalidates the boundary, but it must not become a shared cloud runtime. @hasna/cloudandopen-cloudare forbidden shared runtime dependencies for new app-owned cloud support. UseNoCloudEvidencePackandcontracts no-cloud-scanin prepublish/CI checks to prove package manifests, locks, source/runtime config, and packed artifacts do not reintroduce them.
Downstream Integration Recipes
Adopt contracts as optional compact boundary output first. Prefer --contract
CLI flags, MCP response variants, or SDK adapter functions rather than replacing
native domain objects immediately.
open-files: emitResourceRef,EvidenceRef, andContextPackfor file records, versions, signed URLs, source manifests, and evidence assets.open-todos: expose task refs asResourceRef; verification evidence asProofBundle; task execution receipts asWorkRun; review gates asValidationPlan; and workflow/run manifest pointers as compact task fields, not embedded handoff artifacts.open-loops: emit loop/workflow runs asWorkRun, audit traces asAgentTrajectory, logs/artifacts asEvidenceRef, and verifier output asProofBundle. OpenLoops ownsWorkflowInvocation, admission/work-item queues, leases, workflow runs, retries, cancellation, worktrees, and run artifacts.open-events: emit and replay validated event envelopes to channels. OpenEvents delivers notifications only; it does not create workflow invocations, own queue state, or retry agent work.open-sessions: convert messages/tool calls toAgentTrajectory, token usage toCostEstimate, and transcript paths toEvidenceRef.open-context: serialize built context asContextPackwith citations asEvidenceRefand source chunks asResourceRef.open-knowledge: return retrieval results asContextPack; write policy and promotion decisions asDecisionEnvelope.open-mementos: expose memories asResourceRefkindmementoorknowledge; reflection runs asWorkRun.open-evals: map cases/assertions toValidationPlan, runs/results toProofBundle, reports toEvidenceRef, and judge/baseline choices toDecisionEnvelope.open-economy: ownCostEstimateproduction and emit budget decisions asDecisionEnvelope.open-monitor: output doctor checks, fleet triage, and alerts asValidationPlan,ProofBundle,EvidenceRef,alert, andincidentresources.open-actions: keep domain action manifests, but expose sharedActorRef,EvidenceRef,CapabilityCard,DecisionEnvelope, andWorkRunadapter views.iapp-scaffolds: emitScaffoldManifestdocuments for bundled templates, write schema-taggedScaffoldInstallRecordreceipts for installs, and keep template copying, setup wizards, source paths, and private metadata inside the scaffold package.open-automations: keep deterministic app/product automation recipes and connector/action recipes. Any agentic task, PR, review, or evaluation flow must hand off to OpenLoops rather than creating a second workflow queue.open-reports: consumeProofBundle,WorkRun,ContextPack,CostEstimate, andEvidenceRefto render compact Markdown/JSON/HTML proof reports.- Every package that implements app-owned cloud support should emit an
AppCloudManifestand attach it to aNoCloudEvidencePackduring release. The manifest names explicit app-owned resources and the evidence pack proves the package does not depend on@hasna/cloudoropen-cloud.
WorkflowInvocation And App Storage Boundary
The canonical agent-work root is a WorkflowInvocation, not a todo task.
Only actionable unfinished work needs a todo. OpenTodos remains the
human-visible intent ledger; OpenLoops owns the durable workflow root,
admission queue, execution lifecycle, and canonical run artifacts.
A workflow invocation should carry these fields at the boundary:
idtemplateIdorworkflowIdsourceRef: a WorkflowInvocation-local source kind such astask,event,schedule,manual,pull_request,review, orknowledge, plus an id and dedupe keysubjectRef: a WorkflowInvocation-local subject kind such asrepo,pull_request,task,document,run, ormetric, plus a path, URL, or idintent:route,mutate,review,evaluate, orreportscope: project path, worktree policy, permissions, account policy, and concurrency groupoutputPolicy: when to write reports and when to create a follow-up task
OpenLoops admission/work items are first-class records with route key,
idempotency key, source/subject refs, project key/group, priority, status,
attempts, next-attempt time, lease expiry, loop/workflow/run ids, and last
reason. Status values should be explicit: queued, deferred, admitted,
running, succeeded, failed, dead_letter, or cancelled.
Run artifacts live under:
.hasna/loops/runs/<project-slug>/<subject-key>/<run-id>/manifest.json
.hasna/loops/runs/<project-slug>/<subject-key>/<run-id>/triage.md
.hasna/loops/runs/<project-slug>/<subject-key>/<run-id>/plan.md
.hasna/loops/runs/<project-slug>/<subject-key>/<run-id>/worker-report.md
.hasna/loops/runs/<project-slug>/<subject-key>/<run-id>/evaluation.md
.hasna/loops/runs/<project-slug>/<subject-key>/<run-id>/evidence/
The <subject-key> is never the raw subject reference. It must be a safe path
segment derived as kind-safeSlug-shortHash. Recommended normalization:
lowercase ASCII, replace non-alphanumeric runs with -, trim separators, cap
the slug portion at 72 characters, and append at least 12 hex characters from a
SHA-256 hash of the canonical raw subjectRef. Reject ./.., reserved device
names, path separators, empty keys, and path traversal. Store the raw
subjectRef only inside manifest.json.
OpenEvents webhooks/channels are notifications. A task.created notification
can be delivered through OpenEvents, but OpenLoops consumes the envelope and
upserts/admits work items. OpenEvents must not import OpenLoops or own
admission, retries, leases, verifier execution, or workflow run artifacts.
Every Hasna app stores local state under .hasna/<app>/.... The obsolete
.hasna/apps/<app> layout is not an operational read path. OpenKnowledge's
canonical storage is .hasna/knowledge, not .hasna/apps/knowledge.
No-backcompat migrations must preserve data without keeping legacy shims:
- Create a read-only backup or export before moving data.
- Atomically copy or rename into the canonical
.hasna/<app>path. - Verify JSON item counts, SQLite integrity and table counts, artifact counts, hashes, and any storage-object or sync-snapshot evidence.
- Leave only a diagnostic tombstone at the old path.
- Treat mismatched counts, hash failures, or SQLite integrity failures as blockers.
Unattended automatic routes must fail closed when the configured sandbox cannot
be proven. Acceptable sandbox evidence includes a successful preflight receipt
that names the isolation provider, filesystem/network policy, writable roots,
tool allowlist, environment redaction result, and timestamp for the exact route
or run. danger-full-access plus a worktree is a manual break-glass mode, not a
safe auto-route default.
WorkflowInvocation is documented here as the architecture boundary used by
OpenLoops and neighboring packages. It is not yet a wire schema in the current
catalog; add a hasna.workflow_invocation.v1 schema only when at least two
packages need to validate the object directly at a shared boundary.
Enforcement Model
Validate at every boundary where another package, agent, process, or machine can consume the object:
- CLI: fail with non-zero exit and structured JSON when
--jsonis requested. - SDK/API: reject invalid input before writing to storage or launching work.
- MCP/tool responses: validate outbound payloads before returning to the model.
- Events/webhooks: validate before publish and before handler execution.
- Files/artifacts: validate before using persisted JSON as evidence.
- CI/release: run
bun run verify:releasebefore publishing.
Invalid contracts should fail early at the boundary, not after another agent has spent context trying to repair ambiguous data.
Versioning
Schema ids are immutable, and current wire schemas are strict about unknown
fields. Breaking changes create a new id, such as hasna.proof_bundle.v2.
Additive fields may extend the current version only after a coordinated
validator rollout reaches consumers that will receive those fields. If older
validators can still consume the object, keep emitting only the old field set.
If producers need to emit the new field before that rollout is complete, create
a new schema id instead.
Recommended rollout for a new major schema:
- Add the new schema and examples in this package.
- Keep the old schema exported while consumers migrate.
- Teach producers to emit the new schema behind a feature flag or option.
- Teach consumers to accept both versions when practical.
- Remove old-version production only after downstream repos have release notes and migration tasks.
During dual-version windows, consumers should inspect the embedded schema via
validateEmbeddedContract, route accepted ids to version-specific adapters, and
reject unknown schema ids before writing data or launching work.
Examples
The examples/ directory contains one valid fixture for every known schema plus
targeted invalid fixtures for important invariants. Keep fixtures small and
portable. Prefer artifact://, repo://, task://, or package-owned ids over
machine-local paths.
The conformance command intentionally treats unknown schemas, malformed JSON, and empty fixture sets as harness failures. Invalid fixtures must fail because the schema rejected them, not because the fixture cannot be parsed.
Verification
bun run typecheck
bun test
bun run build
bun run smoke:dist
bun run verify:releaseverify:release runs typecheck, tests, example conformance, build, a smoke test
against the packaged CLI entrypoint in dist/cli/index.js, and a pack dry-run.