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

@volter/supercode-orchestrator

v0.5.7

Published

The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)

Downloads

22,848

Readme

@volter/supercode-orchestrator

This is currently a private workspace package. Examples assume the checkout's workspace dependencies and a built Volter Harness CLI; the package name is not a claim that an installable npm release exists.

The orchestrator's runtime over the ontology: one typed operational model (OrchestratorState = an orchestration value plus a runtime half) whose folder is its serialization. docs/ORCHESTRATOR-IR.md is normative. The orchestration value and every codec are the ontology's, in Rust (crates/interchange/src/orchestration/); this package reads and writes its home through the harness's orchestration doors (harness.v1.orchestration.*), never a codec of its own.

import { load, save, compile, importFrom, exportTo, closeWorld } from '@volter/supercode-orchestrator';
import { homedir } from 'node:os';
import { join } from 'node:path';

const root = process.env.SUPERCODE_ORCHESTRATOR_HOME || join(homedir(), '.supercode/orchestrator');
const { state, vault } = await load(root);                        // strict; secrets in `vault`, never `state`
await save(state, root);                                          // canonical, byte-stable when unchanged

const { state: hermesState } = await compile('hermes', process.env.HERMES_HOME); // a home as a state value
await closeWorld();                                              // close the shared door when done

The doors are orchestration.mjs: load/save/compile/importFrom/exportTo/closeWorld over one SupercodeHarnessClient. A secret VALUE never crosses the wire — load and compile answer with the .env KEY NAMES, and load reads the values out of the home's own .env. The door is held open while a loop runs (retainWorld/releaseWorld, the loop's start/stop) and closes itself when idle, so a one-shot verb (a test, a migration) exits on its own.

Rules the codec keeps (enforced in Rust, observed here):

  • Unknown keys in an orchestrator-owned block (worker) fail the load, naming the file and key. Hermes-shaped records keep unmodeled fields in residue, verbatim.
  • A file whose decoded record is unchanged is written back byte for byte; a changed record is emitted canonically in Hermes's own field order.
  • Secret values live in the vault (.env in our folder; inline in a Hermes export because that is where Hermes reads them). A value pasted inline into our folder is moved into .env on first load and the file rewritten with the ref. JSON.stringify(state) never contains one.
  • Exporting to Hermes refuses to write Hermes's session store when bindings or obligations changed since import (UNI-22); it copies the store byte for byte when they did not.
  • Files the IR does not model are never touched by save and are carried by path on a migration.

OpenClaw, and the two migration verbs (ORC-12)

import { compile, importFrom, exportTo } from '@volter/supercode-orchestrator';

const { state } = await compile('openclaw', process.env.OPENCLAW_STATE_DIR); // a home as a state value
node bin/orchestrator.mjs import --from hermes|openclaw [--home <path>] [--into <root>]
node bin/orchestrator.mjs export --to   hermes|openclaw [--from <root>] [--into <dest>]

import (orchestration.import) compiles that harness's home and saves ours, credentials along; export (orchestration.export) loads our folder and decompiles it back, printing the tier of every file it wrote. Because a migration moves credentials, both are composed inside the harness process, not from the value-level doors — a client composing them would have to send the secret over the wire. A refused write exits 2 with its reason — a partial migration is never reported as a whole one.

What OpenClaw's state directory compiles to, at the pinned v2026.7.1-2:

  • agents.* → profiles. The DEFAULT agent (the entry flagged default, else the first declared, else main) is the IR's default profile; every other agent keeps its id. Each is rooted at its own agents/<id>/.
  • channels.<name>[.accounts.<id>] → one ChannelConfig per row channels list shows, keyed by that row's name (slack/T0FIXTURE); the transport is extra.kind and the source blocks ride verbatim in extra.channel_block / extra.account_block.
  • bindings[] → Route. The IR's match carries the three fields it has a noun for (channel → platform, guildId → guild_id, peer.id → chat_id — the projection routes list makes); accountId, teamId, roles, peer.kind and the binding's type ride in residue.
  • hooks.mappings[] → WebhookSubscriptions (the inbound-trigger noun triggers list reports), with the block's shared token as each subscription's secret.
  • cron_jobs → Job. sessionTarget has no IR field: it stays in residue verbatim AND surfaces as attach_to_session when it names an existing conversation (main → agent:<id>:main, current → the row's own session key, session:<id> → <id>; isolated opens a fresh one).
  • cron_run_logs → Fire. ok | error | skipped map to succeeded | failed | unknown and the native word stays in residue. OpenClaw's run log is written once, at finish, and records no claim, so the fire's start stands in for claimed_at and residue.__no_claim says so. delivery_status / delivery_error / delivered ride in residue, and the queue entry on the same conversation key becomes the fire's obligation_id.
  • delivery_queue_entries → Obligation, partitioned by the agent segment of its session key.
  • each agent's sessions/*.jsonl header key → a Binding. agent:<id>:main is OpenClaw's DM COLLAPSE: it becomes {platform: 'main', kind: 'dm', chat_id: 'main'} and the collapse is recorded in residue, because the IR cannot recover which transport a collapsed DM arrived on.

Tiers on the way out:

  • byte — state/openclaw.sqlite when every record decoded from it is unchanged (the file is copied); any row whose own record is unchanged when the store must be rewritten (its columns go back verbatim, and report.rows counts them); every unmodeled file under the state directory; and openclaw.json when its decoded record is unchanged.
  • semantic — a CHANGED openclaw.json. It is re-emitted as JSON, which openclaw's JSON5 parser reads, but the source's comments, trailing commas and key order are lost; report.notes says so every time that path is taken.
  • refused — bindings. An OpenClaw conversation's surface lives in its session KEY inside the transcript header, so writing them back is behind the UNI-22 stability gate, exactly as the Hermes export refuses Hermes's session store. A credential ref the vault cannot resolve is refused too, rather than written as its own reference.

Secrets never enter the model: any string under a credential-shaped key — openclaw's own rule (channels.rs::is_credential_key: the lower-cased key ENDS WITH token|key|secret|password| credential), minus the names it spells that way for non-secrets (sessionKey, store_key, …) — moves to the vault as a {dotenv} ref and is re-inlined on export where openclaw reads it.

Two limits worth naming. save writes our folder in the IR's own serialization, so a home imported from another harness loses that harness's file COMMENTS and, for fires, any run-log column our Hermes-shaped cron/executions.db has no room for (OpenClaw's delivery_status, session_key, summary, model, token counts): a direct compile-then-decompile of an OpenClaw home is byte tier, the same trip through our folder is not. And import carries the source home's unmodeled files by path, because save only ever writes the files the IR owns.

The loop (ORC-4)

import { Orchestrator, LoopbackAdapter, events, load } from '@volter/supercode-orchestrator';
import { SupercodeHarnessClient } from '@volter/supercode-harness-sdk';

const { state, vault } = await load(root);
const orch = new Orchestrator({ state, vault, client: new SupercodeHarnessClient(), adapters: { loopback: new LoopbackAdapter() } });
await orch.start();                      // adapters connect; inbound messages become events
const now = new Date().toISOString();
await orch.dispatch(events.tick(now));   // the clock is an event too

step(state, event) is the pure reducer (§4): it mutates the one state value and returns effects (§5); the Orchestrator performs them over the SDK (runtimes.start|send_input|respond) and the adapters (deliver). A worker runs with the profile folder as cwd and the profile's harness home inside it (activationEnv), so its AGENTS.md is in context by the ordinary instruction-file walk.

Cron and webhooks (ORC-5)

await orch.dispatch(events.tick(now));       // §4.3: expiry, due jobs, obligation retry, breakers
await orch.tick();                           // the same, on the orchestrator's own clock
// POST /webhooks/<name> on orch.webhookPort (config `gateway.webhook_port`, else any free port)
  • A due job is claimed before it runs: the fire row (cron_<job_id>_<YYYYMMDD_HHMMSS>, which is both the fire's id and its session name) is saved before the runtime_start effect, and a second tick that sees a claimed/running fire for that job does nothing.
  • The fire runs on a synthetic key {platform: 'cron', kind: 'dm', chat_id: job_id} with a binding carrying recurrence. Its final delivers to the job's resolved target — origin → job.origin, home → profile.home, explicit → as given, local → cron/output/<fire_id>.md under the profile — never to the synthetic key. An error or timeout goes to failure_deliver, else to deliver with a Cron job <id> failed: prefix.
  • next_run_at is recomputed after every fire: once is spent (unless a numeric repeat remains), interval counts from the run, cron evaluates five fields in schedule.tz. context_from prepends a note naming the session; the transcript is never loaded.
  • The daemon does not die on a failed event: a timer tick, an adapter's inbound, the webhook listener or a queued follow-up whose effect fails (a save on a full disk) is logged, where before its unhandled rejection ended the process. History is bounded as Hermes bounds it: run files per job (cron.output_retention, 50), completed one-shots (cron.completed_retention_days, 7), 1000 finished fires, and obligations as its delivery ledger (7 days, 500 rows).
  • Slack, Telegram and Discord blocks from a Hermes home connect through the Chat SDK sidecar with Hermes's own .env variables and ingress (Socket Mode, polling, the gateway); install the platform package (@chat-adapter/slack, …) beside the orchestrator.
  • The agent-setup applier (orchestrator apply plan|apply|adopt|provision --home-id <id> --hermes <home> | --orchestrator <root> --spec <setup.json>): a package's declared jobs and Inference reach one home through its own functions (Hermes's cron and config functions under its own interpreter; the orchestrator's operator verbs), owned by harness id against a base in the applier's own state. A home with no state is plan-only until provision or adopt; delete runs only with --reviewed.
  • Limits as Hermes sets them: cron.max_parallel_jobs / HERMES_CRON_MAX_PARALLEL caps the fires in flight across the daemon; an agent.max_turns a worker cannot take at launch is reported as not enforced.
  • A webhook subscription with deliver_only: true (Hermes's direct-delivery mode) runs no agent: the rendered prompt is delivered as the message.
  • A job's gate runs before any model, as in Hermes: script (its stdout leads the prompt, or skips the model when empty or {"wakeAgent": false}), no_agent (the script IS the job), and monitor_script/monitor_url (unchanged output suppresses the run; a change puts a diff in the prompt). Scripts live under the profile's scripts/.
  • A fire whose explicit delivery target is on a platform no adapter serves is blocked before its worker runs (Hermes's delivery preflight), alerting once until a healthy run. Each run resets the job's last_delivery_error; a delivery that finally fails writes it.
  • A fire whose worker has been silent for HERMES_CRON_TIMEOUT seconds (600 by default, 0 for no limit, as in Hermes) is closed as a timeout, its failure delivered, and its runtime closed; before, a hung fire kept its job from ever running again.
  • A job's model (and a profile's, and /model's) reaches the worker's launch (claude --model, codex app-server -c model=…, gemini -m). For a harness with no such flag it is refused where it is set (/model, jobs.create|update, cronjob_create) and again at the start, rather than run on the harness's default. A job's skills are invoked as Hermes invokes them: each is found through the worker harness's own skill loader, from the fire's own working directory, and its SKILL.md (with ${HERMES_SKILL_DIR} resolved) goes ahead of the prompt, which follows Hermes's instruction marker; a skill that is not there is named in a notice.
  • A schedule nothing can evaluate — a cron expression that does not parse, a tz that is not an IANA zone (America/New_Yrok) — is refused by name at every door a job enters by (jobs.create|update, jobs.resume, cronjob_create). One that reached the folder by hand is caught fail-closed: the tick that finds it due, or the fire that cannot re-arm it, disables the job (enabled: false, next_run_at: null, the reason in Hermes's last_error) and sends one notice to failure_deliver, else deliver, else home. It is never left due.
  • A pending obligation is retried on the tick with a 1 / 2 / 4-minute backoff up to three attempts. Three consecutive send failures on one adapter open its breaker for five minutes (state.runtime.adapters[platform].breaker); while it is open, obligations stay pending and their attempts are untouched. An adapter an adapter event has already declared down (state.runtime.adapters[platform].state) is treated the same way before the breaker has counted to three, so a single delivery cannot spend its three attempts inside a known outage and die failed while the transport is still gone.
  • POST /webhooks/<name> verifies an HMAC-SHA256 X-Signature-256 header against the subscription's secret from the vault and is fail-closed: unverified is dropped and logged. A verified call renders prompt_template with {field} substitution from the JSON payload, opens a turn on {platform: 'webhook', kind: 'dm', chat_id: name} in the subscription's profile, and delivers the answer per subscription.deliver.
  • A subscription's events filter on the event the SOURCE names: X-GitHub-Event, X-GitLab-Event, X-Event-Key (Bitbucket), X-Event-Type, else the body's own event/type/action. An entry with a dot names the action too (pull_request.opened matches <event>.<payload.action>); a call naming no event is not a subscribed one. A delivery id (X-GitHub-Delivery, X-GitLab-Event-UUID, Webhook-Id, X-Request-Id) already handled for that subscription — the last 200 are remembered — is dropped with an info log, so a redelivery runs once. The memory is runtime state, as in Hermes's own adapter: a daemon restart forgets it.
  • A permission prompt raised inside a fire or a webhook turn has nobody to ask, so it is answered at once — never left to worker.permission.timeout_seconds — with worker.permission.unattended (deny, the default, or approve; Hermes's approvals.cron_mode), and one line saying what was decided goes to the job's failure_deliver, else deliver, else home (a webhook's: its deliver). A chat's prompt is still relayed to the chat.
  • A Codex worker asks only when started with an approval policy: where the profile keeps Hermes's approvals (approvals.mode unset or not off) it starts with approval_policy: untrusted, so it asks before every command and Hermes's rule answers; with approvals off it asks nothing.
worker:
  harness: codex
  permission:
    unattended: approve   # deny | approve; what a cron fire or webhook turn's prompts get

Known gap: Fire.session_id and Fire.obligation_id are live-record fields with no column in Hermes's executions table, whose schema is frozen. They round-trip by construction instead — the fire's id is its session name (fireSessionId), its obligation is obl-<fire_id>, and the binding carries residue.fire_id — so load() still joins a fire to its session and its delivery.

The model-facing door (ORC-6)

One stdio MCP server per worker session, spawned by the worker itself and bound to the conversation that spawned it (§7). Orchestrator.start() opens a Unix socket at <root>/orchestrator.sock (mcp/bridge.mjs); startRuntime mounts mcp/server.mjs through whatever door the worker's harness publishes (mcp/mount.mjs), with the binding in the server's environment — the three variables activationEnv already sets, plus SUPERCODE_ORCHESTRATOR_SOCK.

| Harness | Door | What is written | |---|---|---| | claude-code | --mcp-config <file> in the launch arguments | <profile>/claude-code/mcp-orchestrator-<surface>.json, .mcp.json-shaped | | codex | [mcp_servers.orchestrator] | appended (idempotently) to <profile>/codex/config.toml | | opencode | mcp.orchestrator | <profile>/opencode/opencode.json | | pi | none | pi ships no MCP client by design; the mount refuses and says so | | hermes, openclaw, any other ACP agent | session/new's mcpServers | harness.v1.runtimes.start's mcp_servers |

Only the flag and ACP doors are named per launch, so only they carry the per-session SUPERCODE_ORCHESTRATOR_SURFACE; under codex and opencode the server inherits it from the worker process the loop already activated for this session.

Tools, one per verb, names and argument shapes transcribed from Hermes's tools/cronjob_tools.py (cronjob_manage) and tools/send_message_tool.py (send_message): cronjob_create, cronjob_list, cronjob_delete, send_message, session_search, session_handoff, memory_show, skills_list. Every result names what it did in ran.

  • cronjob_create captures origin from the binding, never from an argument, so a job created in a conversation delivers back to it. Hermes's name has no IR field and rides the residue channel.
  • schedule takes the IR's typed Schedule or the four Hermes string forms that map onto it exactly (30m / every 2h, in 30m, five-field cron, an ISO timestamp); natural day/time phrasing is refused by name rather than re-implemented.
  • send_message defaults to this session's own surface; a target that routes to another profile is refused, naming that profile.
  • memory_show and skills_list proxy harness.v1.memory.show / skills.list for the worker harness against this profile's own config home.

supercode mcp serve (the coding tools) is a different server for a different purpose and is untouched.

Acceptance (ORC-8)

The bar of docs/ORCHESTRATOR-IR.md §8 — one scripted week of realistic use replayed against Hermes and against this package, compile-diffed, only zero defects done — closed on 2026-09-04 at defect 0 (61 scenes, 21/21 expectations, 28 seconds, both subjects on the scripted clock). §8 carries the record, the defects fixed on the way and the Hermes evidence boundary; the instrument was deleted after its run (owner rule: the roadmap's one test run, then gone).

The two borrowed adapters (ORC-9)

Every adapter is the same three methods — connect(emit) / send(target, content) / close() — and every one of them reaches the reducer through inbound and nothing else (§3: "An adapter that cannot express something as an inbound event does not get a private path into the loop"). A platforms: block picks its adapter by extra.adapter when it names one and by the block's own name otherwise:

platforms:
  slack:                      # one block = one (platform, account)
    enabled: true
    extra:
      adapter: chat-sdk       # -> adapters/chat-sdk.mjs
      botToken: ${SLACK_BOT_TOKEN}              # a reference, as Hermes writes one; the value lives in .env
  api_server:                 # -> adapters/api-server.mjs, by the block's name
    enabled: true
    extra:
      host: 127.0.0.1
      port: 8090
      key: ${API_SERVER_KEY}

RH2 Room chat is an installed app (ADR 0017). Register the Room-chat app in the RH2 organization (organization.read, room.read; a Room's manager seats its principal where it should answer), have an admin install it, and exchange the code the install returns:

supercode orchestrator apps install room-chat --origin https://pilot.runhuman.com \
  --client-id app_… --client-secret-file ./room-chat.secret --code … --profile default
supercode orchestrator apps list
supercode orchestrator apps remove room-chat

The record (with the installation's token) is kept owner-only under <home>/apps/; the next daemon start attaches the rh2 channel for that profile. Uninstalling in RH2 revokes the token and the channel stops. A platforms: rh2 block grants no integration authority and the runtime reports it as unsupported. Native Hermes channel configuration remains the worker's configuration.

The Chat SDK sidecar mounts [email protected] (npm package chat, repo vercel/chat; a devDependency of this package, installed alongside @chat-adapter/[email protected]). The platform packages (@chat-adapter/slack, …) are not dependencies: the one the block names is imported lazily at connect, and the credential key names in the block are that factory's own option names — Volter Harness renames nothing and resolves the refs out of the vault at connect, never earlier. Surface identity is read off the handler's Thread (adapter.name, channelId, id, isDM, channelVisibility) and content off the Message (author.userId/userName/isMe/isBot, isMention, id, metadata.dateSent, attachments with fetchMetadata as the IR's Attachment.ref) — ORC-1 F4's table, transcribed. Two decisions the IR left open are made in adapters/chat-sdk.mjs and nowhere else:

  • kind (the surface key's chat type) (F4 finding 4, which named this as a rule the IR "would have to invent"): isDM → dm; else channelVisibility: private → group; else (workspace / external / unknown) → channel. thread is never produced — a thread is the key's own thread_id.
  • Queueing (F4 finding 11): the SDK's per-thread concurrency is set to concurrent, so §4.1 step 5's queue is the only queue on a surface. extra.concurrency overrides it.

is_me is now on the event and the reducer drops such an inbound at step 0 (§4.1): a platform that replays what we posted is not a turn. send returns the platform's handle and deliver records it as Obligation.posted.message_id (§2.6) — the thing §4.2's edit-in-place would edit.

The api_server surface serves Hermes's own client API on its own node:http listener bound to platforms.api_server.extra.host|port (exactly Hermes's keys, api_server.py:1515-1519). It is deliberately not mounted on the webhook listener: that one binds gateway.webhook_port, a different key with a different meaning. Auth is Authorization: Bearer <extra.key> — Hermes's _check_auth (:1916-1968) — and every route quotes the handler it was matched against. Routes: GET /v1/health (the one unauthenticated one), GET /v1/capabilities, POST /api/sessions, POST /api/sessions/{id}/chat, GET /api/sessions/{id}/messages, POST /v1/runs, GET /v1/runs/{id}, GET /v1/runs/{id}/events, and POST /v1/runs/{id}/approval|steer|stop.

Everything it makes the loop do is an inbound: a chat is one; a run is one on a fresh binding {platform: 'api_server', kind: 'dm', chat_id: <run id>}; a steer is one queued behind the running turn (§4.1 step 5); a stop is /reset (§2.7); and an approval is resolved to the worker's own option and answered as the number §4.2's permission prompt asked for. The run event stream is derived from §2.8 runtime state — the turn's text buffer, the pending permission requests, and the obligation the turn's final produces — because the loop publishes no event bus and ORC-9 does not add one.

Where Hermes's shape has a field the IR has no source for, it is returned empty, never guessed:

| route | empty field(s) | why | |---|---|---| | POST /api/sessions/{id}/chat | usage, runtime | the IR records no token accounting and no provider/model routing | | GET /api/sessions/{id}/messages | token_count, tool_calls, tool_call_id, tool_name, finish_reason, reasoning | §2.5 keeps no per-message tool or token record; the transcript stays native and is read through sessions.load | | POST /api/sessions (session object) | user_id, model, tool_call_count, input_tokens, output_tokens, parent_session_id | same | | GET /v1/runs/{id} | model | the run does not choose a provider model; profile.worker does |

Four honest divergences, stated rather than hidden:

  • platform reads supercode-orchestrator, not hermes-agent, on /v1/health and /v1/capabilities: it names the server that answered.
  • X-Hermes-Session-Key is not an auth credential in Hermes either — _parse_session_key_header (:2403-2440) reads it as the caller-declared conversation scope, and it is read with that meaning here. Bearer is the trust boundary.
  • /v1/runs/{id}/approval accepts Hermes's once|session|always|deny (with approve|approved|allow aliasing to once), but the IR has no standing-grant store: session and always resolve the pending request exactly as once does and no grant outlives it.
  • /v1/chat/completions, /v1/responses, /api/sessions/{id}/chat/stream, /api/jobs/*, idempotency (Idempotency-Key) and the /p/<profile>/ route mirrors are not served; /v1/capabilities reports each as false rather than copying Hermes's true.
  • A run is running from the moment it is accepted (Hermes passes through queued first), and /stop leaves it cancelled once the /reset lands rather than in stopping.

Known gaps, named:

  • Obligation.posted lives on the in-memory record only. Hermes's delivery_obligations table has no column for it and the store is byte-compatible with that schema, so a save/reload loses the handle. Adding it is a schema change to §2.6's codec, not an adapter change.
  • deliver projects the obligation to { text, at } before calling send, so OutboundContent.reply_to and attachments never reach an adapter today even though §2.1 declares both and the Chat SDK sidecar honours both (thread.reply) when a caller supplies them.
  • The production path that builds a real platform adapter from a block (@chat-adapter/* → createSlackAdapter(...)) is not exercised by the suite: the tests drive a hand-written Adapter (the SDK's packages/chat/src/mock-adapter.ts shape, transcribed — that file is not re-exported from the package entry, is outside its published files list, and imports vitest). Evidence for the sidecar stops at the SDK boundary; the platform packages are extrapolation.

Access, the daemon's lease, and coming back (ORC-10)

node bin/orchestrator.mjs access pair-list                    --root <home> [--profile <name>]
node bin/orchestrator.mjs access pair-approve <code>          --root <home>
node bin/orchestrator.mjs access allow|revoke <platform> <user_id>          --root <home>
node bin/orchestrator.mjs access admin-add|admin-remove <platform> <user_id> --root <home>

supercode orchestrator pair approve|list and access allow|revoke|admin add|remove spawn exactly these: the folder has one encoder (save()), and it is not in the Rust binary. Each verb loads the home, applies the reducer's own operator event and saves; a verb the reducer refuses exits non-zero with the reducer's own words and writes nothing it did not ask for. They are the COLD path — for a running daemon the same verbs go through its socket (ORC-13).

  • Access lives in Hermes's own stores, and only there. A platform is open when the profile's own .env sets <PLATFORM>_ALLOW_ALL_USERS=true (or GATEWAY_ALLOW_ALL_USERS=true for every platform); its allowlist is <PLATFORM>_ALLOWED_USERS plus the pairings Hermes approved (platforms/pairing/<platform>-approved.json). A platform with neither is allowlist-only. An unknown sender is answered with a pairing code from Hermes's own PairingStore (its expiry, rate limit and lockout) and nothing else: no worker starts.
  • Admins are each platform's allow_admin_from / group_allow_admin_from in config.yaml: the tier whose numeric reply resolves a relayed permission prompt (§4.2). An allowlisted non-admin's number is an ordinary message.
  • No other file grants access. approve, allow, revoke and admin go through Hermes's store and writers, live or cold.

The lease <home>/orchestrator.lock is written by this daemon, not by supercode orchestrator start: one writer means a launchd/systemd-managed daemon reports the same lease a foreground one does. The lock names the process and its machine (hostname, and Linux's boot id). A home whose lock names a live process on this machine, since this boot, is refused; a lock naming a process that is gone, or one another machine or an earlier boot wrote (a home on a volume the sandbox before this one mounted), is the mark of a daemon that died, and the daemon dispatches restart (§4.7) before it accepts anything. Nothing is signalled on the word of a lock from elsewhere. leaseIsLive and readLease are the package's reading of it. A clean stop removes its own lock, so an ordinary start replays nothing.

On restart: every pending obligation goes out again, and every binding that held a lease is re-opened. The lease table is never persisted (§2.8), so a lease start records runtime_id on the binding's residue; the loop tries runtimes.attach_existing where the harness's registry row says attach_existing_process, and otherwise marks the binding interrupted and tells its surface once (Session interrupted by a restart; send your message again.). Cron and webhook surfaces get the mark without the notice — nobody is reading them.

The repo's Hermes fixture home (crates/harness/fixtures/hermes_home) is a ready home to drive it against by hand; that needs target/debug/supercode built.

The operator door (ORC-13)

The orchestrator's WRITE door — what supercode jobs|profiles|sessions <verb> --harness orchestrator drives, the way Hermes's write door is hermes cron … (§4.6). Volter Harness writes no file of this folder: save() here stays the only writer, so the IR's byte-stability and residue rules hold by construction.

Two doors, one implementation (applyOperator in operator.mjs); only how the event is dispatched differs.

Live — the same socket the model-facing door uses, a second line family beside it, told apart by the op key:

in   {"op":"jobs.create","args":{"prompt":"…","schedule":"every 30m"},"profile":"coder"}
out  {"ok":true,"result":{"ran":"created cron job job_… …","job_id":"…","job":{…}}}
     {"ok":false,"error":"jobs_delete: no job job_x"}

op is one of jobs.create|update|pause|resume|run|delete, profiles.create|delete, sessions.new|reset, access.pair_approve. The verb dispatches its events.operator(...) on the loop's own queue, so an operator write is ordered against inbound messages and ticks like anything else and the running loop's state and the folder can never disagree. result re-reads the affected record out of state after the reducer ran.

Cold — the same verbs when no daemon is serving the home:

node bin/orchestrator.mjs <op> --root <home> [--profile <p>] [--at <rfc3339>] --json '<args>'

It loads, applies through applyOperator, performs the effects the loop would (save, remove_profile_dir), prints the same {ok, result} line and exits 0, or 1 on a refusal. crates/harness/src/orchestrator_door.rs picks between the two: the socket when <root>/orchestrator.lock names a live pid and the socket answers, the CLI otherwise. A refusal on the live door is final — only a broken socket falls through, so a verb the daemon already answered is never run twice.

  • A refusal names its reason. The reducer refuses by emitting a warn log effect and no save; that sentence becomes {ok:false, error} verbatim, and nothing is written.
  • sessions.new|reset name a BINDING by its SURFACE (platform|kind|chat_id|thread_id|participant_id), not by a session id — a binding has none of its own. A surface with no live conversation refuses.
  • profiles.delete never unlinks. It removes the record and emits remove_profile_dir, which renames the home to profiles/.trash/<name>-<stamp>/. A dot-prefixed folder is not a profile to either loader, so a trashed home neither loads nor lists.

Hosted scheduled tasks

A host can retain its existing conversation and deterministic action runtime while this package owns schedules, durable fire claims, run output and the operator socket:

supercode-orchestrator --root /instance/var/orchestrator \
  --executor-url http://127.0.0.1:5200/os/scheduled-task \
  --executor-token-file /instance/var/executor.token

The executor URL must be loopback HTTP. Its private token file contains at least 32 characters. Each authenticated POST carries {root, profile, job, fire}; the host returns {accepted: true, profile, fire_id, output, delivery?} only after accepting that exact fire. A delivery receipt is {platform, chat_id, id} and confirms the submitted task, not a model's eventual answer. The output must say which milestone it confirms. jobs.create accepts opaque JSON metadata, stored in job.residue.host, and enabled: false for initially paused tasks.

Hosted execution starts no worker and uses no channel adapter. The host must persist an idempotency record before its side effect, return the saved receipt on duplicates, and refuse to repeat an uncertain dispatch. The daemon records failed/timeout fires without replaying them; a restart advances a stale claim once. callOperator addresses the live socket, with no cold-write fallback. Orchestrator({executeJob}) is the corresponding in-process API for embedders.

The daemon acquires its exclusive lease before loading state. A crash during the short acquisition critical section leaves orchestrator.acquire; subsequent starts fail closed until an operator inspects and removes that guard. Normal stops remove only their own lease. Native jobs.resume computes the first fire for a job that was created paused with no next-run timestamp.

Hosted daemon lifecycle

stopHostedDaemon({ root, executorUrl?, timeoutMs? }) validates the hosted lease and signals this package's daemon through its native shutdown handler. A foreign root/executor is refused; the lease is retained until shutdown completes. The helper does not clear a live lease or escalate to SIGKILL. Embedders can stop their owned clock before disabling the integration and start it again on the next boot. Hosted HTTP refusal messages preserve the executor's bounded JSON error reason.