npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

owenloop

v0.5.30

Published

Deterministic rails for agentic workflows. Declare steps and dependencies; the engine guarantees order, redoes what changes invalidate, and stops what keeps failing — instead of hoping the agent follows through.

Readme

owenloop

CI License: Apache 2.0

owenloop is deterministic rails for agentic workflows.

Most agent workflows today run on hope. You write careful instructions in a prompt or a skill — update the state file, run the verifier, stop when it's done — hand them to an agent, and trust it to follow through. Sometimes it does.

owenloop replaces the hope with guarantees. You declare the steps and what each depends on; the engine enforces the rest. A step runs only when everything it needs is actually done. When a result changes, everything built on it is invalidated and redone. When a step keeps failing, it's stopped and flagged for a human instead of retried forever. The agents stay probabilistic — that's what makes them useful. The workflow around them doesn't.

See it work

A research pipeline: a researcher gathers findings, a writer turns them into a report, and an independent reviewer must sign off before the report counts. Every clause of that sentence is enforced below, not requested:

# workflows/report.yaml
name: report
inputs:
  - name: question
    seedOwed: true
steps:
  - name: researcher
    consumes: [question]
    produces:
      - name: findings
        schema: { type: object, required: [claims, sources] }
    body: Research the question. Every claim needs a source.
  - name: writer
    consumes: [findings]
    produces:
      - name: report
        judges:
          - name: reviewer
            body: Reject the report if any claim lacks a citation
                  or drifts from the findings. Otherwise approve.
    maxAttempts: 3
    body: Write the report from the findings, citations inline.

You don't drive this by hand. An orchestrator — an agent skill, a plain while loop, your own code — ticks the engine, hands each job to a Step Agent, and reports results back. Here's what the engine enforces as that loop runs:

  1. The writer cannot start early. The first tick emits exactly one order — the researcher's. There is no writer job to hand out, so no eager Step Agent can write a report from findings that don't exist. The hope version is a prompt: "wait until research is complete before writing."

  2. Malformed output never enters the pipeline. The researcher reports findings missing sources — the engine refuses the commit at the schema, and the retry order carries the validation errors as feedback. The hope version: the bad output flows downstream and fails somewhere confusing.

  3. The report can't approve itself. When the writer commits report, it lands in submitted, not done. The reviewer is a separate order to a separate Step Agent — one that never saw the writer's reasoning. A rejection re-arms the writer with the reviewer's reasons attached to its next job. The hope version: "review your work before finishing" — the fox auditing the henhouse.

  4. Failure has a floor. Third rejected attempt and report stalls: the engine stops issuing jobs for it and flags it for a human, instead of letting an agent grind the same mistake all night on your API bill.

  5. "Done" stays honest. Sharpen the question after the run finishes and findings and report fall back to owed — the finished workflow un-does itself, automatically, rather than standing on inputs that no longer exist.

None of this depends on an agent reading carefully, remembering instructions, or being honest about its own work. And notice that you never defined a state or a transition — only what each step consumes and produces. This isn't a state machine an agent is asked to role-play from a prompt; the states live in the engine, and the engine doesn't negotiate. The agents stay probabilistic; the bookkeeping around them never is. The Quick start gets you running in two commands, and Driving it with a loop covers orchestrators — including the shipped skills that do it for you.


Why it exists

Agents are good at doing one task. They're bad at the bookkeeping around a task: remembering what's already done, noticing when an earlier step's output changed, retrying the right number of times, and knowing when to stop. Wire a few agents together by hand and you end up writing a pile of glue — who runs next, what to re-run when something upstream moves, when to give up and ask a human.

owenloop is that glue, written once and tested hard. You declare the steps; it handles the three things that are tedious to get right:

  • What runs next. A step is ready the moment everything it depends on is accepted and it still owes an output. That's the whole scheduler — there's no status field to flip, nothing to sequence by hand.
  • What to re-run. Change an early step's output and everything built on it automatically falls back to "not done." No manual invalidation, no stale results slipping through.
  • When to stop. If a step keeps getting rejected past its limit, owenloop stops re-running it and flags it for a human — instead of looping forever burning tokens.

Hope is not a control flow.

The mental model: owed, not done

The checklist your agents can't cheat.

owenloop doesn't track whether a step is "running" or "done." It tracks what each step owes. Every output is in one of six states:

| state | still owed? | meaning | |-------------|:-----------:|------------------------------------------------------------------| | owed | yes | declared but not produced yet, or re-armed — the step owes it | | green | no | accepted; satisfies everything downstream that depends on it | | rejected | yes | produced, then judged unfit (or knocked back by a change) — a debt | | retracted | no | a member dropped from a collection; gone for good | | skipped | no | a step declined its own output on a dead branch | | submitted | no* | produced, awaiting sign-off from one or more declared judges |

* submitted isn't a producer debt — the producer already did its job — but the workflow isn't done while it sits there either. See judges.

A step is eligible to run when it owes a debt (an owed or rejected output) and every input it consumes is green. Status is never stored — it's computed from these states on every read, so it can't drift out of sync.

Two things make this more than running steps in dependency order:

  • Outputs stay honest as inputs move. A green output counts as done only while the inputs it was built from are still green and unchanged. Re-run an early step and everything built on it quietly falls back to a debt — no code required to invalidate it.
  • Rejections carry reasons. When a reviewer rejects an output, the text rides along. The next job for the producer shows why it's being asked again, so the Step Agent has the feedback in hand. (Three flavors: a reviewer's judgment, the engine's own schema refusal of a malformed value, and structural knock-backs from a change cascading downstream.)

What owenloop is not

  • Not a scheduler with its own clock. cadence: and maxRunsPerDay: cap how often an eligible step can fire, but nothing in owenloop wakes up on a timer — the outer loop (see below) is what initiates every tick.
  • Not shared state across instances. Every workflow instance is its own island — artifacts, tasks, and runs are all scoped to one instance. The one deliberate exception is the calls:/producedBy link between a parent instance and the child it explicitly spawned.
  • Not a dynamic graph at runtime. Collections give a workflow dynamic width — a producer can emit any number of elements — but the wiring graph itself (which steps exist, what each consumes and produces) is fixed when the definition loads, not mutable while an instance runs.
  • Not a command runner. executor:/command: declare which kind of worker an order is for (Order.worker) and what command text a worker resolves for it — the engine never shells out, executes, or interprets it. Actually running anything is always the dispatcher's job, on the other side of tick.

It scales up when you do

Each of these is a small addition to the base model above — most workflows use only a few. Skim the ones that sound relevant; every entry links to the full reference.

Judges — enforced independent verification

A produces: entry can declare one or more judges: deterministic quality bars an artifact must clear before it counts as green. This is the independent-verifier pattern, enforced structurally instead of remembered as a convention: the artifact lands in submitted, not green, and cannot move further until a separate order — with no view of the producer's reasoning — signs off. A rejection carries its reason back to the producer's next job, the same as any other knock-back. See docs/authoring.md and judged-research.yaml.

Durable by default

State lives in a single SQLite file, not in a session or a context window. Kill the process, come back next week, run owenloop tick — the engine knows exactly what's owed and picks up where it left off. The workflow outlives the process, the session, and the model that's driving it. See Storage.

Stall detection — the token-burn stopper

If an output is rejected more times than its maxAttempts, the engine stops re-arming it. It stays a debt, but produces no more jobs — the step has demonstrably failed, and it's flagged for a human instead of looping forever. owenloop retry clears the stall and resets the counter, optionally with new guidance. maxAttempts (and maxSchemaFailures below) is set on the step as a default for all its outputs, but any single produces: entry can override either cap for itself when one output needs a different bound than its siblings. See docs/cli.md and docs/design.md §6.

Schema refusal

A produces: entry can carry a JSON Schema; a green/emit/seal whose value fails it is refused at the engine, not silently accepted and discovered downstream. Repeated schema failures trip the same stall mechanism as judgment rejections. See docs/authoring.md.

Cascade invalidation

Think of it like a build system: change a header file and make knows every object file that includes it needs recompiling, without you tracking that by hand. owenloop does the same thing for agent outputs — change an early step's result and everything built on it automatically falls back to "not done," and gets redone the next time its inputs are green. No manual invalidation code, no stale results slipping downstream. See docs/design.md §7.

Collections — fan-out/fan-in

A step can emit any number of elements at runtime; a map step runs once per element, and a reduce step runs once after they're all in (or, with a suffixed reduce, once every element's own per-element output is in). See docs/authoring.md and research.yaml. A rejected collection seal can be re-emitted and resealed; rejected members are recovered by an authorized retract, as described in the authoring guide.

Composition — include: and calls:

Build a workflow out of other workflows two ways: include: splices another def's steps directly into the parent at load time (one flat graph); calls: delegates to a separate child instance at runtime, keeping its internals hidden as a black box. See docs/authoring.md.

Side-effect policies — effect:

Most steps are safe to re-derive when their inputs move — that's what the cascade assumes by default. A step with an irreversible side effect (a deploy, a publish, an external write) can declare effect: { idempotent: false, onInvalidate: … } to tell the engine to pin the old result, escalate to a human, or run a compensating step instead of silently re-firing. See docs/authoring.md.

Model tiers

model: fast | standard | strong | strongest declares intent, not a vendor id — the engine passes it through untouched to whatever dispatches your Step Agents. A portable workflow says "this step needs strong judgment"; the host binds that to whatever model it runs on. strong is a high-capability workhorse tier, not the host's single most capable model — that's what the opt-in strongest tier is for, reserved for the rare step where nothing less will do. See docs/authoring.md.

Worker dispatch

executor: agent | command | … declares which kind of worker a step's order is for — the authored value lands on the order as Order.worker, and the default (agent, silent when omitted) is unaffected; every def written before this feature stays byte for byte the same. Opt a step into executor: command and give it a command: string to switch it to a deterministic worker instead of an LLM — the engine never runs it; your dispatcher branches on order.worker and resolves the authored command text from the local workflow store through the order's defDigest at the (defDigest, step, key) boundary. The packet itself carries no command text: a command worker must refuse when the digest or command cannot be resolved and must never spawn from transport-supplied text. An optional spec: map carries further opaque config (a timeout, a working directory), and a judge entry accepts the same fields, so a quality gate can be a script's exit code instead of a verdict. See docs/authoring.md and command-executor.yaml.

Event subscription — for embedding

Driving the engine in-process doesn't require polling: engine.subscribe(...) pushes a typed event the instant a mutation commits, so a host can react instead of ticking on a timer. See Embedding it and docs/embedding.md.

Shared workflows — the content-addressed store

Workflow definitions are shared, not hand-copied. Two install routes land them in the tree, both all-or-nothing with crash recovery:

  • GitHub reposowenloop add <owner>/<repo>[@ref] fetches a public repo's workflows/**, validates every def with the engine's strict pass, and installs it under <defsDir>/<owner>-<repo>-<hash>/, recording provenance (resolved commit sha) in .owenloop/installed.json.
  • Workflow bundlesowenloop add widget.wnlp [--global] (a local .wnlp file or an https:// URL) installs into a content-addressed store: the project store lives under the defs dir, --global installs into <home>/.owenloop/workflows. Each store holds an index.json mapping namespace/name@version coordinates to content digests, plus immutable objects at objects/sha256/<digest>/ — identical content deduplicates to one object, and an object's identity is its digest alone (never its name or source). Objects are hardened read-only in place, and every resolution re-verifies the bytes before returning a path. Execution workers resolve static prompt and command text from those verified local objects by the order's defDigest; a remote packet cannot substitute instruction text.

The bundle route derives <home> from the first non-blank caller-injected HOME or USERPROFILE value (HOME wins), and uses that home for the default recovery-marker directory. A bundle install refuses when neither variable is supplied rather than falling back to the process user's ambient home.

Normal definition discovery no longer floods stderr for expected bundle history. It emits at most one note: N superseded bundle versions hidden; --verbose to list them; the global --verbose flag restores every detailed superseded-version notice. The actionable has no selectable version warning for competing non-SemVer versions is never hidden.

Store history can be bounded safely with a dry run first:

owenloop bundle gc --keep 2             # project store, report only
owenloop bundle gc --keep 2 --yes       # recompute under lock and apply
owenloop bundle gc --global             # global store, report only

The default keep count is 2 (current plus one rollback version). GC preserves selected versions, explicit pins, retained local instance snapshots, transitive bundle locks from every retained coordinate in either root, exact versioned calls from retained legacy snapshots and current project/add definitions, and project-index fallback into the global store. Reachability is coordinate- and root-aware, so an intact non-target copy does not keep a redundant target history beyond --keep. It never contacts the hub; --yes is the only destructive switch and only the target bundle index/object tree is changed. Applied GC takes both roots' writer locks (creating only coordination state at a known missing counterpart root when necessary). Project GC also takes the legacy GitHub-add lock and recovers its journal before clearing their shared staging tree; global GC uses that lock as a read barrier without recovering or otherwise mutating the project tree. CAS-backed snapshot writers share the store locks, and bundle installs revalidate exact manifest locks before commit, so stale definitions or callers cannot be pinned during or immediately after collection.

Bundle installs fail closed without their two required adapters (bundle ingestion and pre-commit verification). The default CLI binds the real bundle ingestor, so a .wnlp produced by packBundle reaches the normal entrypoint-aware install validation; the pre-commit verifier remains unbound, so the CLI still fails closed rather than accepting an unverified bundle. There is no default accepting signature verifier; the add route does not consume publication sidecars or verify a bundle's signature. Use owenloop publish to create the .wnlp bundle plus a signed publication sidecar and optional signed origin sidecar, or an explicitly unsigned marker. --global applies only to .wnlp bundle sources; a GitHub source with --global is refused before any network request. Resolution is deliberately split: execution resolves by digest only (project first, fall-through to global only when the project object is absent — a corrupt project object is a hard error, never masked by a global copy), while human-facing lookups resolve by coordinate and surface a structured ambiguity error when the two levels disagree — never a silent project-first pick. Full semantics: docs/cli.md.


Driving it with a loop

owenloop never runs anything itself. It hands out jobs and waits to hear back — something has to tick it, run the work, and report the result. That something can be as simple as a while loop around an agent. The Ralph loop — keep an agent ticking with a fresh context each pass — is exactly this kind of outer loop, and owenloop is the half it's missing: the persistent state and the brakes. The loop keeps going; owenloop remembers what's owed, what failed and why, and when the whole thing is actually done. They work side by side — the loop is the muscle, owenloop is the memory.

The outer loop is deliberately not owenloop's business, which means it can be anything that can run a CLI command or call a function. In practice that looks like:

  • Your own harness — a while loop, a cron job, a CI stage: tick, run each order with whatever executes your work (an agent CLI, an API call, a script), report, repeat. Fully deterministic dispatch if you want it — see Embedding it for the in-process version. The root owenloop shift command is the standing Shift runtime — the engine core still ships no host harness of its own.
  • A Prime Agent as the orchestrator — point any tool-using agent (Claude Code, Codex, Gemini CLI, anything that can run a shell command) at the CLI and tell it to drive the instance to done. A slash command or skill that wraps this turns "run the release workflow" into one line.
  • A Prime Agent structuring its own work, inline — mid-task, the Prime Agent authors a throwaway workflow, drives itself through it, and deletes it: the engine as scratch discipline rather than standing infrastructure.

The engine doesn't know or care which of these is ticking it — an order is an order. The Owenloop Claude Code and Codex plugins provide six hub-native skills:

  • conduct — supervise one existing workflow through a scoped Shift, check its status, and relay human gates.
  • author — interview a human, draft a workflow, validate it through create_workflow, present it for approval, and offer start_run. Use conduct or shift to supervise the resulting run.
  • ephemeral — structure the caller's own one-off work with an ephemeral workflow.
  • graduate — turn evidence from a completed ephemeral composite into a reusable, policy-conformant library candidate.
  • plan — compile novel multi-domain work into a checked, approval-gated ephemeral composite that delegates library playbooks.
  • shift — attend a crew's ongoing workflow workload through the blocking Shift loop and relay human gates.

Put the owenloop CLI on PATH and run owenloop setup; the setup flow converges the bundled Claude Code and Codex plugins on that same CLI and hub connection.


Quick start

Put the owenloop CLI (Node ≥ 22.13) on PATH and run owenloop setup once. Then ask your Prime Agent for what you want:

Use author to build me a workflow that researches a topic, writes a report, and doesn't accept it until an independent reviewer signs off — then run it on "tidepools".

The author skill interviews you for anything missing, writes and validates the YAML definition, and presents the process for approval before offering to start it. Already have a def? "Conduct the report workflow" hands it to conduct. The shipped Claude Code and Codex plugins launch the same owenloop command as owenloop mcp, rather than using a separate npx-pinned server. The plugins check the installed CLI/plugin version pair at session start when their hooks are enabled, and the MCP server reports a readable mismatch error on tool calls; run owenloop setup when the versions differ. For one-off shell use, npx owenloop still works — no clone, no build, no environment variables, no CLI verbs to memorize. The same package also includes the execution-side companion; use owenloop work <subcommand> for lower-level execution tasks. There is no separate owenwork package or binary.

Deterministic workflow bundles

A workflow and its referenced regular files can be packaged as one deterministic .wnlp file. The package format uses a canonical POSIX/PAX tar stream wrapped in gzip; the package digest is SHA-256 over the exact uncompressed canonical tar, not over the gzip bytes. bundle.yaml carries package identity, platform selectors, requested capabilities, generated per-file SHA-256 values, and namespace-qualified, digest-pinned calls: references. workflow.yaml remains the only execution definition.

owenloop bundle pack ./report --output ./report-1.2.0.wnlp
owenloop bundle inspect ./report-1.2.0.wnlp
owenloop bundle digest ./report-1.2.0.wnlp
owenloop bundle unpack ./report-1.2.0.wnlp ./unpacked-report

The bundle format commands (pack, inspect, digest, and unpack) are filesystem-only: they do not open or create the local .owenloop/state.db and do not contact a remote service. bundle gc is intentionally store- and local-snapshot-aware, but likewise never contacts the hub. Packing never edits the source manifest. Inspection and unpacking reject unsafe paths, symlinks, unsupported archive entry types, malformed canonical headers, duplicate files, invalid manifests, workflow/manifest name mismatches, missing lock entries, and integrity mismatches before files are written. See the bundle format reference and the CLI bundle commands.

For a foreground local shift, start the daemon in one terminal. Run the client commands from another terminal:

# Terminal 1: foreground daemon
owenloop shift start alpha

# Terminal 2: client calls
owenloop shift next
owenloop shift status
owenloop shift end

shift start requires at least one named crew unless you pass --all explicitly; --all serves all Crews for the Scoped Identity. shift next waits up to 90 seconds by default. For flags, JSON output, daemon behavior, and exit codes, see the shift reference.

A running shift also writes to disk: its own dispatch record as JSON Lines in shift.log, and each dispatched worker's stdout and stderr in <run>.log. Both outlive the shift, which is what makes a postmortem possible after a shift dies. See docs/shift-logs.md.

Want to see or drive the machinery yourself? Everything above goes through the same small CLI (create, tick, green, reject, …). docs/cli.md has the full command reference and a hand-driven walkthrough of a pipeline — including a rejection knock-back and a stall — and examples/workflows has seventeen runnable defs, from a minimal review loop written to teach the wiring (delivery — a four-step example, not the production line) to a production-shaped pipeline (ship), a collections-heavy research pipeline (research), and a compiler that turns live vendor docs into a new gate-checked def (compile-dev-playbook). Full YAML grammar: docs/authoring.md. Driving it from your own code: Embedding it.

Want someone else's workflow defs instead of writing your own? owenloop add <owner>/<repo> fetches a public GitHub repo's workflows/**, validates every def, and installs them locally with pinned provenance — see the add entry in docs/cli.md. An installed def's steps later run with your Step Agents' full privileges and owenloop never executes them itself, so install only sources you trust, and pin a commit SHA (@<sha>) for anything you re-add — the trust model is spelled out there.

Publishing your defs to a hosted hub instead? owenloop login authenticates the CLI (loopback OAuth, or a pasted token via --with-token; the credential goes into the macOS Keychain or a 0600 file, never the repo, and is verified against the hub before it's ever stored), owenloop connect binds the project to a hub, and owenloop push publishes your defs. Use owenloop push --bundle <bundle.wnlp> after owenloop publish when execution must be pinned to the exact signed bundle digest; plain YAML pushes remain idempotent against the hub's own def hashes. To publish an outside repo's defs, owenloop install <owner>/<repo> scopes every capability they author to <defName>.<capability> unless you deliberately map it onto a name your crews already serve — see install. owenloop agent new <name> mints a Scoped Identity on the hub and stores its token in slot agent:<name> without ever printing it. See the Hub section in docs/cli.md.

Setting up a machine from scratch? owenloop setup may sign you in as a human and, when needed, mint or rekey and store a Scoped Identity. Setup writes only hubOrigin into the execution settings file, preserving the other keys. The file is $HOME/.owenloop/settings.json by default (OWENLOOP_CONFIG_DIR can provide an absolute isolated directory; XDG_CONFIG_HOME is not consulted). For a non-default account, setup only prints the OWENLOOP_ACCOUNT=<name> instruction. Setup probes and, when needed, converges the bundled owenloop plugins for Claude Code and Codex. Plugin convergence is non-fatal; a missing harness or failed plugin command does not fail setup. A second run with the expected plugin version already installed performs no plugin writes when its effective MCP launch is verified safe. owenloop doctor is the read-only counterpart: it checks both harness plugin states, including the effective owenloop mcp command and arguments, Codex's PATH pass-through, and PATH availability. It reports same-version absolute or worktree launch declarations as drift, but does not treat a plugin's absolute marketplace or cache location as a launch verdict. A known unsafe declaration is repaired by the existing non-fatal owenloop setup convergence flow. See setup and doctor in docs/cli.md.

Driving the hub from an MCP host instead? owenloop mcp serves the hub control plane to a local MCP host (Claude Code) over stdio — MCP hosts spawn it, you don't run it yourself — authenticating as your logged-in human credential and never surfacing a token to the model. See the mcp section in docs/cli.md.


Requirements

  • Node ≥ 22.13. Storage is Node's built-in node:sqlite, which is available unflagged from 22.13 onward (it still prints an experimental warning until it stabilises in Node 24.15 / 25.7). owenloop is an ESM-only package. CI runs the full check on Node 22 (active LTS) and 24 (current).
  • No native dependencies. node:sqlite is built in, so there's nothing to compile. The only runtime deps are yaml (parsing defs) and @cfworker/json-schema (optional per-artifact schema validation).
  • OpenSSH ssh-keygen with -Y support (OpenSSH 8.1+) for the signing features. owenloop setup automatically ensures an Ed25519 signing key for each of the three local principals — human, machine, agent — and signs / verifies records with stock SSHSIG (ssh-keygen -Y). Keys land in the macOS Keychain, Linux libsecret (secret-tool), or a 0700/0600 file store under $HOME/.owenloop/keys/; one backend is chosen once and never error-fallback. See Signing and key storage.
npm install owenloop
import { createEngine } from 'owenloop';   // see "Embedding it" below

Upgrading from 0.2.1

Upgrading a project from the 0.2.1 release pulls in everything that has landed since. The changes that need operator or embedder attention:

  • Deep recursive ticking. One tick now advances the whole calls: tree by default, so a single call can drive many workflow instances at once and their execution timing shifts. Embedders that key per-workflow logic — checkpoints, transactions, counters — must key on order.workflow, not the id passed to tick (--shallow / { deep: false } is the single-instance escape hatch). See Migrating from shallow ticking and Deep tick and order.workflow; the full guidance lives there.
  • Node ≥ 22.13 required. The engines field pins Node ≥ 22.13, and CI runs the full check on Node 22 and 24 — see Requirements just above.
  • Database schema v9. The first open by a new binary migrates the database in a single transaction that rolls back on failure; a database whose stored version is newer than the binary is refused rather than opened. Migrated databases do not get backfilled artifact-history payload snapshots — durable payload history begins after migration (legacy lifecycle reasons are carried over exactly once). Copy the SQLite database (.owenloop/state.db plus its -wal/-shm sidecars) before upgrading; downgrading means restoring that copy, because an older binary refuses a v9 database.
  • New retained plaintext data. Issued order packets and artifact history now persist in the database, so rotating and disposing of that data is the operator's job — see Retention and disposal.
  • New network-facing commands and credential storage. owenloop login / connect / push (and add, for installing shared defs) reach the network and store a credential in the macOS Keychain or a 0600 file — see the Hub section and the add trust model. A hub origin now holds one credential per named slot (--as human|agent|agent:<account>), and credentials stored under the earlier keying are not read — there is no migration, so re-run owenloop login. owenloop agent new <name> mints an agent token straight into slot agent:<name> without ever printing the secret. Setting OWENLOOP_CREDENTIAL_COMMAND instead supplies the credential from a command of your own (a secret manager, or any host without a keychain); it takes precedence over both stores and fails loudly rather than falling back. Embedders can now read, write, and refresh a hub credential programmatically through the package's exported credential surface (readStoredCredential, storeCredential, deleteCredential, ensureFreshOAuth, CredentialIO); concurrent OAuth refreshes are serialized by a credentials.lock file so a token rotation can't clobber a fresher one — see What's exported.
  • Max-lease cap is now opt-in. There is no default lease ceiling; anyone who relied on the brief post-0.2.1 default cap must set maxLeaseMs (or a per-step maxLease) explicitly.

Embedding it

The CLI is a thin adapter: it maps argv to engine calls and prints JSON. The engine is an ordinary class, so you can drive it in-process and get typed objects back (Order, CommitResult, WorkflowStatus) — no subprocess, no JSON parsing.

import { createEngine } from 'owenloop';

const { engine, store, resolver } = createEngine({
  db: '.owenloop/state.db',         // or ':memory:' for an ephemeral instance
  defsDir: 'workflows',             // load YAML defs from a dir … or pass `defs: [myDef]`
});

// start an instance (proposal is seeded as owed, so provide it up front)
const wf = engine.createInstance('delivery', {
  provide: { proposal: { text: 'add dark mode' } },
});

// the Step Agent loop: tick → resolve → run → report
const { orders } = engine.tick(wf);
for (const order of orders) {
  // orders are reference packets: routing + dynamic data + a defDigest, never
  // authored prompt/command text. Resolve before dispatch — same boundary the
  // CLI uses; an unknown digest throws UnknownDefDigestError here.
  const instructions = resolver.resolveOrder(order);     // { prompt?, command? }
  const result = await runYourAgent(order, instructions); // ← your domain
  engine.green(order.workflow, order.run, order.outputs[0], result); // typed CommitResult back
  engine.close(order.workflow, order.run);
}

engine.status(wf);   // typed WorkflowStatus: done / debts / eligible / blocked
store.close();        // on shutdown

Prefer to react instead of poll? engine.subscribe(listener) (or createEngine({ onEvent })) pushes a typed event the instant a mutation commits — so you can re-tick only when there's new work, or resolve a promise when the workflow is done. See examples/events.ts.

The engine/store pair is meant to be long-lived (one per database). Concurrency is the store's job: node:sqlite is synchronous and single-writer-per-process, and cross-process safety comes from a commit fingerprint check (described under Storage). See docs/embedding.md for the full surface, lifecycle, and trade-offs.


How it's built

owenloop is small and split along a pure-core / imperative-shell line:

| module | responsibility | |---|---| | src/types.ts | shared types: the six-state lifecycle, reason threads, def shapes | | src/paths.ts | parse/match the src[$i] / src[*] / src[] path grammar | | src/defs.ts | load YAML → validated WorkflowDef (the static wiring checks) | | src/schema.ts | JSON Schema validation of artifact values, via @cfworker/json-schema | | src/model.ts | the pure core: what's eligible, the cascade, status, stall detection | | src/store.ts | the SQLite runtime store — node:sqlite persistence; transactions; the commit check (instance/artifact state) | | src/install.ts | the host-neutral install transaction: safe staging, atomic swap with a retained backup, two-phase journal, lock, recovery | | src/store/ | the content-addressed workflow store — digest-addressed definition objects, the two-level coordinate index, .wnlp bundle install, fail-closed resolution (immutable defs; distinct from the runtime store) | | src/engine.ts | the imperative shell: tick/green/reject/… → mutate → settle() | | src/cli.ts | argv → engine calls, JSON on stdout |

Invariant: every engine mutation ends with settle() — materialize owed outputs and run the cascade to a fixpoint — so status() is a pure read over artifact state and never lies.

Storage

State lives in a single SQLite database via Node's built-in node:sqlite in WAL mode — no native module to compile, no separate graph engine. The flat artifact/task/run tables are the graph; the dependency structure is recomputed from the definition on each tick. Concurrent advancement is made safe by a commit fingerprint check: a run records the version of every input it claimed, and its commit is rejected ("born-rejected") if any of those inputs moved underneath it. Each artifact carries a monotonic version, so the engine can always ask "is this green output still resting on the inputs it was built from?".

Retention and disposal. That file is the system of record and it keeps everything: every artifact version's value and every run's issued order packet — the dynamic input values it consumed and its rejection reasons, including any sensitive values that flowed through an input — persist after a workflow finishes. Authored prompt and command text are not in the packet (orders are reference packets that carry a defDigest instead), so instruction text is retained once, in the definition, not per firing. The default location is .owenloop/state.db (plus its WAL -wal/-shm sidecar files), or wherever --db / OWENLOOP_DB / the db: embed option points. Rotating or scrubbing that data is the operator's job: owenloop delete <wf> removes an instance's rows, but SQLite frees those pages without erasing or shrinking the file on disk, so for reliable disposal delete the database file together with its -wal/-shm sidecars.

Testing

npm test          # node --test, spec reporter
npm run typecheck # tsc --noEmit (type-checks the source)
npm run check     # both
npm run build     # compile src/ → dist/ (also runs automatically on npm pack/publish)

The suite spans unit tests (paths, store, model, defs, schema, util, cli), engine integration tests (the cascade, the stall, schema validation, the concurrency check, judges: sign-off/CAS/throttling in test/judges.test.ts), and end-to-end tests that spawn the real bin/owenloop.mjs binary and drive the example workflows through their full lifecycles.

Two e2e files carry most of the weight, by opposite intent. test/edge.e2e.test.ts is an edge battery aimed at the corners the design is most particular about: cascade invalidation, terminal completion surviving an upstream reject, empty / fully-retracted collections, the commit check, cadence and daily-budget gating, the skip-cascade, and CLI robustness against malformed input. test/scenarios.e2e.test.ts takes the opposite tack — multi-step positive stories that confirm the documented behaviors hold end to end: the map parallel cap, map and reduce firing as concurrent branches, the reason thread riding the next job, stall → retry → re-stall, and the cascade re-firing on a re-provided input while leaving a healthy graph and a terminal output untouched. test/schema.e2e.test.ts drives schema validation end to end: a malformed value is rejected rather than greened, a corrected value greens on the same open job, repeated failures trip the stall and a retry clears it.

owenloop check <def> runs a bounded static reachability search over a workflow definition and reports dead steps (never seen firing) split by severity: steps that can NEVER fire regardless of search bounds are structurally dead — a real wiring defect, reported nonzero exit — while steps that CAN fire but the bounded search just didn't reach are unreached within bounds — informational only, exit 0. It also splits every reachable non-done, no-moves state into exactly one of two buckets: a stall state — the state is blocked only by a frozen/stalled debt (maxAttempts / maxSchemaFailures / held) or an idle trigger waiting for its threshold; recomputing eligibility with the freeze lifted (a human retry = unlimited attempts) and with eventual idle time shows a move would become available. This is an EXPECTED, by-design human-escalation brake or future wait and never fails the check. The classifier does not add future idle transitions to the timeless reachability search, so an idle-only completion path can still report completable: false. A true deadlock is a state where the same recompute STILL yields no moves, a genuine structural dead-end which fails the check nonzero when the search is exhaustive. See docs/design.md §25 for the full breakdown.

By default, seedOwed inputs are assumed provided (modeling the operator's provide already having run at create), so a def whose only initial gate is an unprovided seeded input no longer reports a false True deadlocks ... (initial state) and nonzero exit. --strict-inputs restores the seeded-inputs-start-owed behavior; when that's the sole blocker, it also prints a one-line hint naming the seedOwed input(s) responsible. --assume-provided is still accepted but is now a no-op (redundant with the default).


Design reference

owenloop is a faithful, decoupled implementation of a dataflow-engine spec. docs/design.md is a self-contained walkthrough — the lifecycle, firing rule, forward cascade, the reject kinds, the liveness rules, and the concurrency model — cross-referenced from the source. docs/cli.md has the full command reference, docs/authoring.md has the full YAML grammar, docs/wire-contracts.md defines the versioned trust-boundary records, and docs/shift-logs.md is the on-disk log contract a shift writes and an uploader reads.


License

Apache-2.0 © Typical Day LLC.

owenloop is permissively licensed — use, modify, self-host, and redistribute it, including in proprietary or closed-source products, under the terms of the Apache License 2.0.

Contributing

Contributions are welcome — see CONTRIBUTING.md. Note that owenloop requires every contributor to sign a Contributor License Agreement that assigns copyright in contributions to Typical Day LLC, so the project can be maintained — and relicensed in the future if ever needed — under one clear owner. The process is a one-time comment on your first pull request.