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

owenwork

v0.1.0

Published

Execution-side CLI companion to owenloop: parks, holds, and runs delivery orders against the hub.

Readme

owenwork

CI License: Apache 2.0

owenwork is the execution-side runner for owenloop.

owenloop is the workflow engine that decides what should happen next. owenwork is the small local process that actually shows up to do it: it parks at a hub, picks up an order, holds it with a heartbeating lease, and runs it to completion — handing the work back cleanly if the process dies so nothing strands.

One binary, a handful of roles. A standing proxy conductor watches a hub and dispatches orders; a hold keeps an interactive agent session's order lease warm; a detached exec child owns a command order end to end; and a few supporting roles (prepare, lint, release, settings, join) handle setup and draining. All of them read their hub credentials through owenloop's own credential store — owenwork never invents its own secret storage.

Quickstart

owenwork is a single TypeScript package (Node >= 22.13.0, plain npm).

npm install -g owenwork      # or run ad hoc with: npx owenwork <role>
owenwork --help

Connect an agent account to a hub (this writes to owenloop's credential store — see Credentials):

owenwork join <join-code> --hub https://your-hub.example

Then park a conductor at the hub and let it dispatch orders:

owenwork proxy --workflow <workflow-id>

Build from source

npm ci          # or: npm install
npm run build   # compile src/ -> dist/
node bin/owenwork.mjs --help

The full gate (what CI runs) is:

bash .dev/checks.sh   # npm ci + typecheck + lint + build + tests

Roles

owenwork <role> [args].

| Role | What it does | | ----------------------------------------------- | ----------------------------------------------------------------------- | | prepare <workflow> [--origin <url>] | fetch, cache & compile per-step agent templates | | lint <workflow-name \| path> | validate x.claude-code bags in a workflow def | | proxy [options] | park at the hub and dispatch orders | | hold --order <id> | hold an order with a heartbeating lease | | exec <order-id> | run a command order in a self-leasing loop | | release --session <id> | drain a session's held claims (exec-held exempt) | | settings | print the resolved settings file | | join <code> [--hub <origin>] [--as <account>] | redeem a join code, store the agent credential (one-time provisioning) |

--help / -h / help print usage; --version prints the version.

Exit-code contract

Stable across roles — scripts may depend on it:

| Code | Meaning | | ---- | -------------------------------------------------------------------------- | | 0 | success, or --help / --version, or lint clean / warnings-only | | 1 | runtime failure (fetch / hub error, cache write), or lint found an error | | 2 | usage error — unknown/missing role, or a missing required arg | | 3 | role not implemented |

1 separates a real failure (a hub 404, a bag that fails lint) from a usage mistake (2). proxy returns 0 on a clean shutdown, 1 on a runtime failure, 2 on a usage error.

proxy

owenwork proxy [--origin <url>] [--as <account>] [--name <n>] [--serve-pools a,b] [--cap <n>] \
               [--workflow <id>] [--poll-interval <ms>] [--settle-margin <ms>] \
               [--once] [--mcp] [--no-stamp] [--cache-dir <p>] [--agents-dir <p>] [--state-dir <p>]

proxy is the standing conductor loop. It parks at the hub and, each tick:

  1. Presence — refreshes conductor presence via presence_ping every ~60 s (the hub marks a conductor offline after ~3 min), carrying --name and the serve pools. --serve-pools a,b narrows this conductor to a subset of the key's pools; with no flag it serves every pool on the key.

  2. Wake — a cheap wake(cursor) pre-check ("anything servable changed since cursor X?"). It only pays for a full whats_next sweep when wake says changed, and it always adopts the returned cursor (monotonic). Bootstrap (no stored cursor) does one full sweep, then polls with the cursor.

  3. Sweep + meter — free capacity is cap − in-flight (default cap 3). At zero free capacity the loop skips whats_next entirely (keeps wake + presence). Otherwise it sweeps (inbox → per-instance whats_next, or just --workflow <id> when pinned) and dispatches at most the free capacity.

  4. Dispatch — by kind. The proxy makes no get_order first contact for any order; it holds no leases. The bound worker makes first contact and closes the hub's ~2-min pickup window, so a failed hand-off lapses back no-fault and re-offers. Each order it will dispatch is stamped into a per-order agent file from the compiled template, then, after a one-time settle-margin wait (default 5 s), dispatched by kind:

    • command orders spawn a detached owenwork exec <workflow>/<run> --origin <url> child that self-leases;
    • agent orders are not spawned — they are returned to the conductor as lean orders { order:"<workflow>/<run>", agent:"<stamped stem>" } (each stamped agent carries its own born-bound hold). In the standing CLI loop (no conductor to return to) they are logged and left for the pickup window; the --mcp whats_next tool is what actually returns them.

    Orders it will not dispatch (over capacity, or a command routed to a conductor) are left for the pickup window. Metering counts both live exec children and outstanding agent records (see below).

Defaults: --cap 3, --poll-interval 5000, --settle-margin 5000, presence every 60 s. --once runs a bootstrap wake + one sweep and exits (the demo/e2e hook). --no-stamp dispatches without writing agent files. --as <account> (default default) picks the agent:<account> credential slot and is resolved once here, then propagated into each stamped hold's argv (--as) and each exec child's spawn env (OWENWORK_ACCOUNT) — see Credentials.

The cap and the agents/state dirs each take a settings-file fallback (see Settings): cap resolves --cap → settings.dispatchCap → 3; agentsDir resolves --agents-dir → $OWENWORK_AGENTS_DIR → settings.agentsDir → default; stateDir resolves --state-dir → $OWENWORK_STATE_DIR → settings.stateDir → XDG default. A CLI flag or env var always wins over the settings file.

Command routing (commandRouting + x.owenwork.routing)

worker: command steps can run here or be left for a human/session. Two inputs decide, and most restrictive wins:

  • the machine-level commandRouting setting ('proxy' default, or 'conductor');
  • a per-step x.owenwork.routing override ('proxy' | 'conductor') — the x.owenwork bag is owenwork's own namespace.

If either says 'conductor', the proxy does not auto-run that command order (one log line, left for the pickup window). An invalid/unknown value anywhere fails closed to 'conductor' plus a warning. Agent orders are not routed through this knob.

In-flight state + restart recovery

Detached children survive a proxy restart, so in-flight count is reconstructed from a state dir, not from memory. One JSON record per in-flight order lives under --state-dir → $OWENWORK_STATE_DIR → settings.stateDir → $XDG_STATE_HOME/owenwork/exec → $HOME/.local/state/owenwork/exec. There are two record kinds:

  • kind:'exec' ({ workflow, run, pid, spawnedAt }) — a detached command child. Liveness is probed with kill(pid, 0); dead ones are reaped.
  • kind:'agent' ({ workflow, run, pid:0, spawnedAt, def, hash, step }) — a dispatched agent order. There is no local process, so liveness is a TTL (~60 min): an agent record older than the TTL is reaped. The stored def/hash/step provenance lets the GC re-vouch (re-stamp) a live agent's file after a restart, even with an empty agents dir.

On startup and each sweep the loop scans the records, counts the live ones (across both kinds) as in-flight, and reaps the rest. A re-offered agent run (its handout lapsed past the pickup window) replaces its stale record and is re-emitted without consuming a fresh slot — the record is already counted. Metering off this count is an efficiency mechanism — the engine's race-safety and the pickup/lease TTLs are the correctness backstop, so a rare miscount is harmless.

Shutdown

SIGINT/SIGTERM drains: the in-flight sweep finishes, run() returns 0, and no hub call is made afterward. Detached children are not killed — that is the drain semantic (a session going away hands its agent-held orders back via owenwork release --session, but exec-held command orders keep running). A second signal hard-exits.

--mcp mount (stdio-MCP)

--mcp mounts the same loop core behind a stdio-MCP server instead of the self-driven park, so a conductor drives dispatch on demand over JSON-RPC. stdout is the protocol channel (every loop/diagnostic line is redirected to stderr); the process stays alive on stdin and exits on its EOF. Dormant until the first tool call — no hub traffic until the conductor asks. Mutually exclusive with --once. Three tools:

| tool | args | returns | | --- | --- | --- | | whats_next | { wait_ms? } | parks up to wait_ms (default 25 s) running iterate() until it yields agent lean orders, then { orders:[{order, agent}], cap, free }. Command orders are exec-spawned as a side effect and never returned. Cancellable; emits a keepalive progress frame ~every 25 s while parked. One park at a time — an overlapping call fast-fails. | | set_dispatch_cap | { cap } | sets the live cap (non-negative integer); returns { cap, free }. | | submit | { workflow, run, path, value, done? } | posts an artifact receipt through the proxy's hub connection; returns { outcome, closed, text }. closed:true frees the run's dispatch slot immediately (its stamped file is reaped on the next iterate). |

hold

owenwork hold --order <workflow>/<run> [--origin <url>] [--as <account>] [--session <id>]
              [--heartbeat-interval <ms>] [--ignore-stdin] [--mcp]
   or: owenwork hold --order <run> --workflow <wf> [...]

hold keeps one order's lease alive on behalf of an interactive session and performs a final-breath handoff when that session goes away, so work re-offers instead of stranding. It first calls get_order (which beats the lease, closes the pickup window, and validates the workflow/run pairing), then heartbeats on a safe cadence — default 60s, well inside the hub's reap TTL — with jitter/backoff on transient failures. A 403 is a loud ownership error; a clock jump (laptop sleep) triggers a lease re-check before the next beat.

On SIGINT, SIGTERM, or stdin EOF it takes a targeted release (the "final breath", bounded at 5s) so the order re-offers on the next tick. Release is idempotent hub-side, so a normal completion and a reaper race both land cleanly. A second signal hard-exits.

  • --order <workflow>/<run> — the order to hold; composite splits on the first / only. Bare --order <run> requires --workflow <wf>.
  • --session <id> (env OWENWORK_SESSION) — rides every call as the holder tag {kind:'session', id}. Optional hub-side; when absent no holder is tagged.
  • --heartbeat-interval <ms> — renew cadence (default 60000).
  • --ignore-stdin — don't final-breath on stdin EOF, for backgrounded/detached use where stdin is /dev/null or already closed at spawn.
  • --mcp — run as a stdio-MCP work-holder (see below). Mutually exclusive with --ignore-stdin (stdin is the transport, and its EOF is the final-breath signal).

Exit codes: 0 on a clean stop — order completed elsewhere, final breath released, or stopped before the hold was ever established; 1 on a runtime failure — ownership-error (403), lease-lost, hub-unreachable (transient failures spanned the window), or release-failed; 2 on a usage error (missing --order, malformed target, no origin/token).

--mcp mount (born-bound work-holder)

A stamped agent's frontmatter declares mcpServers.owenwork = owenwork hold --order <wf>/<run> --origin <url> --as <account> --mcp, so when the agent session boots it launches hold --mcp as its stdio-MCP server (the --as <account> is the proxy-resolved account, stamped in at dispatch). The ids arrive on argv — they never pass through the model. Underneath the tools the same lease loop keeps the order's lease warm (first contact captured via onOrder so get_order needn't re-fetch); stdout is the JSON-RPC channel, every diagnostic goes to stderr, and stdin EOF (the session died) fires the final breath. Two tools:

| tool | args | returns | | --- | --- | --- | | get_order | {} (none) | the bound order's { workflow, run, order, text }. The run is fixed at launch. | | submit | { path, value, done? } | posts a receipt for an owed output path; returns { outcome, closed, text }. When the hub reports the run closed, the lease loop stops without releasing (the claim is already gone). |

The server keeps answering until the transport ends, not until the lease does: after a closing submit (or a lost lease) both tools fast-fail with isError and make no further hub calls, but the process serves until stdin EOF and then exits with the lease loop's outcome as its exit code — so a closing submit's reply never races process exit.

The bearer comes from owenloop's agent:<account> slot (see Credentials); hold takes --as <account> (the proxy stamps it in), while exec/prepare/release read OWENWORK_ACCOUNT.

exec

owenwork exec <workflow>/<run> [--origin <url>] [--heartbeat-interval <ms>]
   or: owenwork exec <run> --workflow <wf> [...]

exec is the detached, self-leasing runner the proxy spawns per command order — it is the one process that actually shells an order's command out. It shares hold's lease loop (src/lease/loop.ts), so cadence, backoff, clock-jump handling, and the final breath are identical; on top of it, exec reads the first-contact order packet and runs the command while the lease stays warm underneath.

  • Holder tag — every get_order/heartbeat rides {kind:'exec', id} where id is <hostname>:<pid>. kind:'exec' is the drain exemption: an exec-held claim survives a session drain — only a signal aimed at this process hands the order back.
  • Receipt — when the command settles (any exit code, or a machinery failure), exec submits a command-receipt to every owed output path: { exitCode, signal?, error?, outputHash, stdoutBytes, stderrBytes, outputTail (last 4 KiB), timings, orchestrator }. outputHash is sha256:<hex> over full stdout then full stderr; if out-of-order stderr overflows the 1 MiB buffer cap, capture degrades to the bounded two-part form sha256:<sha256(stdout)>+<sha256(stderr)> (both streams still fully hashed). The receipt IS the result contract — a failing command still exits 0 because delivering the receipt is the job; the exit code inside carries the truth.
  • Misroute — a null / non-command / nothing-owed packet is not exec's to fail (an agent could legitimately run it), so it takes a targeted release and exits 1.
  • Lease lost mid-run — if the lease goes terminal while the command runs, exec kills the command's process group (SIGTERM → 5s grace → SIGKILL) and does not submit (a submit would race the re-offer). A signal aimed at exec does the same, then releases — killed work never gets a receipt, even when the TERM'd command settles before the release round-trip finishes.

Exit codes: 0 on submitted (receipt delivered) or a first-contact completed; 1 on misroute, killed, lease-lost, ownership-error, hub-unreachable, submit-rejected, or submit-failed; 2 on a usage error (missing/malformed order-id, no origin/token). The command runner is a seam, so unit tests drive a fake runner and never spawn a real child; only the runner's own tests exercise the real spawn, against harmless fixtures.

release

owenwork release --session <id> [--origin <url>]

release drains a session: it releases every claim the given session id holds so those orders re-offer immediately instead of stranding until their leases expire. It calls the hub's by-session release verb and prints the hub's summary line, one line per released <workflow>/<run>, and a fixed note that exec-held claims are drain-exempt.

Drain semantics. The hub releases the session's agent-held claims and leaves exec-held claims running — a detached command doing real work must survive its launching session going away. The hub enforces the exemption server-side and does not enumerate the exempt claims, so release can only note the exemption; it never lists which claims were exempt. Draining a session with zero held claims is a success (exit 0), not an error.

  • --session <id> (env OWENWORK_SESSION) — the session to drain. An empty string counts as missing. Missing both is a usage error (exit 2).
  • --origin <url> (else settings.hubOrigin) and the bearer (agent slot for OWENWORK_ACCOUNT, with OWENWORK_TOKEN as a dev-only override) resolve exactly as for the other roles. release has no --as flag.

Exit codes: 0 on a successful drain (including an empty release list); 1 on a hub/network error (the hub message is surfaced); 2 on a usage error (missing session or origin, no stored agent key, or an unknown flag).

Settings

owenwork's machine-level config is a single JSON file at $XDG_CONFIG_HOME/owenwork/settings.json (else $HOME/.config/owenwork/settings.json). Every knob is optional; a missing file loads as {}. Each knob is the lowest-precedence fallback for a role option — the resolution order is always CLI flag > env var > settings file > built-in default.

| Knob | Type | Fallback for | | ---------------- | -------------------------- | --------------------------------------------- | | hubOrigin | string | --origin (prepare, proxy, hold, exec, release); also seeded by owenwork join, first-write-wins | | cacheDir | string | --cache-dir / OWENWORK_CACHE_DIR | | agentsDir | string | --agents-dir / OWENWORK_AGENTS_DIR | | stateDir | string | --state-dir / OWENWORK_STATE_DIR | | dispatchCap | positive integer | --cap (proxy), default 3 | | commandRouting | 'proxy' | 'conductor' | proxy command routing (default 'proxy') |

Loading validates the type of every known key (a wrong type is a hard error naming the key, the expected type, the received value, and the file path). Unknown keys are not fatal — they pass through untouched (forward compatible) but are reported as unrecognized so typos are visible.

owenwork settings

prints the resolved file path, whether it exists, each known knob with its value and provenance (settings when the file supplies it, default / unset otherwise), and any unrecognized keys. It exits 0 when the settings are valid (including no file at all), 1 when the file is malformed JSON or a known key has the wrong type, and 2 on stray arguments (there are no options).

NO secrets, ever. There is deliberately no token/credential knob. Hub credentials stay in owenloop's own store, read through the CredentialReader seam (agent:<account> slot; OWENWORK_TOKEN is a dev-only override) — never in settings.json. See Credentials.

prepare

owenwork prepare <workflow> [--origin <url>]

prepare fetches a published workflow def from the hub, caches it keyed by the def's content hash, and compiles each agent step into a Claude Code agent .md template. A step's template is a complete agent file with exactly one substitution token left in it — the literal __OWENWORK_ORDER__ — which the proxy replaces with the order id when it stamps a per-order agent file.

Not every step compiles to a template, and that is not an error:

  • a step with no x.claude-code bag is a plain-dispatch step (the conductor runs a bare subagent) — listed in the summary, no file written;
  • a worker: command step or a calls: step is an exec/engine concern — listed as skipped;
  • a bag whose key no adapter claims (e.g. a future x.codex) is left alone and noted, so it neither breaks nor silently vanishes.

Origin, token, and cache resolution

  • Origin — --origin <url> → settings.hubOrigin → usage error (exit 2).
  • Bearer — the agent:<account> slot from owenloop's store (account from OWENWORK_ACCOUNT, default default); OWENWORK_TOKEN is a dev-only override. A missing agent key is a refuse (exit 2) naming a runnable owenloop login command. prepare has no --as flag — a conductor sets OWENWORK_ACCOUNT. See Credentials.
  • Cache dir — OWENWORK_CACHE_DIR → settings.cacheDir → $XDG_CACHE_HOME/owenwork → $HOME/.cache/owenwork (throws if none resolve).

Cache layout

<cacheDir>/bundles/<workflow-name>/<hash>/
  bundle.json            # the validated fetch response + fetchedAt + version
  templates/<step>.md    # one compiled template per agent step

Keying by content hash means invalidation is automatic: a republished def gets a new hash and a new dir, and the superseded hash dir for that workflow is pruned. A re-run against an unchanged hash is idempotent — nothing is rewritten. Writes are atomic (temp file + rename) and byte-identical for a given hash, so a concurrent prepare for the same def is safe: last rename wins with no torn files.

After compiling, prepare runs a four-layer garbage-collect over the stamped per-order agent files (in OWENWORK_AGENTS_DIR, default ~/.claude/agents/owenloop): vouch open orders → delete ended orders → evict by TTL (48 h) → hard-cap oldest-first (500). Only files matching the stamped-name pattern are ever touched, and every filesystem op fails open — GC never kills a run. prepare calls it with empty open/ended sets (TTL and cap still apply); the proxy is the live caller with real run info.

Environment variables

| Var | Purpose | | -------------------- | ----------------------------------------------------------- | | OWENWORK_ACCOUNT | agent-slot account to read (agent:<account>, default default) | | OWENWORK_TOKEN | dev-only bearer override; when set, skips the store | | OWENWORK_CACHE_DIR | cache root (overrides settings / XDG / HOME) | | OWENWORK_AGENTS_DIR| stamped-file GC directory |

Hub bundle-serving note

The hub does not expose a bundle verb yet. Today GET /api/workflows/:name returns each step's name/consumes/produces/terminal but not its body, model, worker, or x — the four fields prepare needs to compile a template. Against a hub that does not yet serve those fields, prepare detects an agent step missing its body and fails with an actionable error naming the gap rather than caching a bundle it cannot compile. Tests run against a mocked hub serving the enriched shape.

calls: limitation

prepare fetches exactly one def per run. Frozen child defs for calls: steps (multi-def bundles) have no hub publish surface yet, so when a def contains a calls: step, prepare notes it in the summary (child workflow '<x>' not fetched — bundle serving lands with the hub's publish surface) and compiles only the parent. The cache layout deliberately leaves room in the hash dir for those child defs to land later.

lint

owenwork lint <workflow-name | path>

lint validates each step's x.claude-code bag against the Claude Code agent field surface. The target is resolved by shape:

  • a path ending .yaml / .yml is parsed as a raw owenloop def;
  • a path ending .json is a cached / exported bundle;
  • a bare name loads the latest cached bundle for that workflow (errors with run \owenwork prepare ` first` if none is cached).

It does not reimplement owenloop's full def validator — only what it needs to reach the bags. Per step it checks: the bag is a map; reserved keys name / description are absent (they are generated); known keys have the right types (tools/disallowedTools, model, permissionMode, maxTurns, effort, skills, background, memory, mcpServers, hooks, isolation, color, initialPrompt); unknown keys warn; a bag model alongside a first-class step model warns (the step field wins); an author mcpServers.owenwork entry warns (it is overwritten at compile time).

Output is one line per finding — error|warning step <step>: x.claude-code.<field>: <message> — then a summary. Exit 0 when clean or warnings-only, 1 when any error is found.

join

owenwork join <code> [--hub <origin>] [--as <account>]

One-time provisioning: redeem a hub-issued join code and store the resulting agent credential. This is the only place owenwork writes to owenloop's credential store — every other role only reads it (see Credentials).

Origin rule. The hub origin comes from --hub, or else from an existing settings.hubOrigin — one of the two is required. The code itself never selects or influences the origin, and the hub's redeem response is not trusted to redirect it either: a pasted code can only redeem against the hub the human named, never one it points to itself.

join posts the code (unauthenticated — there is no credential yet) to the hub's /enroll/redeem route, then:

  • writes the returned agent token to owenloop's store at slot agent:<account> (--as <account>, default: the agent name the hub returns) via owenloop's storeCredential;
  • records settings.hubOrigin, first-write-wins — an existing, differing hubOrigin is left untouched with a warning, never overwritten.

The confirmation output never prints the token or the code. Exit 0 on success; 1 on a redeem/store failure; 2 on a usage error or an unresolved/invalid origin. A burned or already-used code, an invalid code, and a hub rate-limit each surface as a distinct, actionable message.

Hub client

src/hub/ is a typed client for the hub verb surface the roles call (whats_next, get_order, heartbeat, release, submit, whoami), matching the hub's REST shapes. Transport and auth wiring are real; tests exercise it only against a fake fetch or a throwaway local server. The presence verb is intentionally not included yet — its hub surface has not landed, and inventing its shape is off-limits.

Credentials

owenwork reads hub credentials through owenloop's store only, and never reads the macOS keychain or credentials.json formats directly. At runtime (proxy/hold/exec/prepare/release) it is strictly read-only over the store — join is the sole exception, a one-time provisioning write via owenloop's own storeCredential. src/credentials/reader.ts ships the CredentialReader seam plus a FakeCredentialReader for tests; the live OwenloopCredentialReader (src/credentials/owenloop.ts) wires it to owenloop 0.4's readStoredCredential.

Agent slot only, never human. owenwork always addresses the agent:<account> slot (account from OWENWORK_ACCOUNT, default default) — the OwenloopCredentialReader is the one enforcement point, so a human-only origin reads as absent. When no agent key is stored, every role refuses to start (exit 2) with a message naming the origin, the account, and a runnable owenloop login --hub <origin> --as <agent[:account]> command.

Account handoff. The proxy resolves the account once (from --as <account>, default default) and propagates it two ways: into each stamped hold agent file's argv (hold … --as <account>) and into the exec child's spawn env (OWENWORK_ACCOUNT). hold accepts --as; exec, prepare, and release read OWENWORK_ACCOUNT (a conductor sets it for a non-default account).

OWENWORK_TOKEN is a documented dev-only override. When set (trimmed, non-empty) it is used verbatim and the store + account are skipped — handy for local runs against a scratch token. It is not the normal path and is never required; the agent slot is. Precedence is OWENWORK_TOKEN → agent slot → refuse, shared by all five roles via resolveBearer.

Connecting / storing an account. owenwork's runtime roles are read-only over the store, so you store an agent account for an origin by running owenloop, not owenwork:

owenloop login --hub <origin> --as agent:<account>

Use --as agent for the default account. Provisioning a fresh box from a hub-issued join code is the one owenwork-side alternative — see join:

owenwork join <code> --hub <origin>

owenwork then reads the stored slot at run time via --as <account> (proxy/hold) or OWENWORK_ACCOUNT (exec/prepare/release).

Listing accounts. owenwork intentionally cannot enumerate stored accounts — it never reads the keychain or credentials.json directly, and owenloop 0.4 exposes no enumeration API for it to call. Account listing is an owenloop-side capability; to see which slots are stored for an origin, use owenloop.

License

Apache-2.0 © Typical Day LLC. Contributions require a signed CLA — see CONTRIBUTING.md.