00 START SECTIONS10 REQUIRESnode ≥ 20

0x2F is a local task runtime for coding agents. You give it a piece of engineering work; it routes that work to a coding harness on your machine, runs it in the background, and tracks it until you close it.

You work in terms of tasks, not agent sessions. A task is the object that persists: it has an id, a lifecycle, a history of runs, a result, and — when it cannot proceed without you — a request for your input. Which harness executed it is recorded underneath.

0x2F is hex for the ASCII / (47). The CLI is 2f. One dependency (ws), no build step, local-first.

00.2  INSTALL

Two prerequisites: Node.js ≥ 20, and at least one coding harness for the work to run through. claude (Claude Code), codex (Codex CLI), dsh (DeepSeek Harness) and gemini (Gemini CLI) are built in; anything else is added per project by manifest — see 02 EXECUTION.

npm install -g 0x2f        the `2f` command lands on your PATH

npx --yes 0x2f ... works for a one-off, but install globally for regular use — 2f is a local app you invoke repeatedly, not a one-shot script. No accounts, no daemon.

00.3  QUICK START

2f init                          create .work/ in this repository
2f new "Investigate why retries restart the whole run"
2f                               list today's tasks
2f open 1                        run detail, history, result
2f ui                            open the Web UI

2f init writes .work/ with project.md, rules.md, knowledge.md, decisions.md and providers/. Edit project.md and rules.md once — every prompt 0x2F builds is assembled from them, so the harness starts with your project's context instead of none. It also tells you which provider will run, or what to install if none is available.

If 2f new refuses with Execution provider "claude-code" is unavailable, no harness is on PATH — install one, or configure a provider (see 02 EXECUTION).

00.4  2f ui

2f ui behaves like launching a local application. If a 0x2F runtime is already healthy on 127.0.0.1:4242 it reuses that one and opens the browser; otherwise it starts the runtime in the background, waits until it is healthy, then opens the UI. Runtime output goes to .work/ui.log.

The Web UI is a control surface over tasks that are already running. Closing the browser does not stop anything. 2f ui <port> or 2f ui --port <n> serves another port; --no-browser starts it without opening a browser.

00.5  YOUR FIRST TASK

Write the task the way you would write it for a colleague — the outcome you want, not the steps to get there. 2f new returns immediately; the run is detached.

2f new "…"
    ↓
WORKING ──────→ READY              2f open <id> for the result
    ↓
NEEDS YOU ── 2f allow | 2f reject | 2f answer ──→ …
01 TASKS & RUNS STATEstable SOURCEsrc/core/

The task is the product object. Everything else — runs, events, results, provider sessions — hangs off it.

01.1  A TASK IS NOT A SESSION

A task persists. A provider session is metadata under one of its runs. This is the distinction the whole system is built on: you can run the same task again, through a different harness, and compare — because the task outlived the session that executed it.

Task  "Investigate why retries restart the whole run"
  ├── run 01 · claude-code         a fresh session
  ├── run 02 · deepseek-harness    a fresh session
  └── run 03 · claude-code         your notes and prior results
                                   in context

     the task persists · a session is metadata under one run

01.2  LIFECYCLE

Five states, normalized by 0x2F regardless of what the provider underneath reports. A provider that emits nothing but a final result still moves its task through the same lifecycle.

WORKING ───────────────────────────→ READY
   │                                    │
   ├── permission ──→ NEEDS YOU ──→ allow / reject
   │                      │
   ├── decision ─────────→│ ──────→ answer
   │
   └───────────────────────────────→ FAILED

   any state ─────── close ──────────→ DONE
WORKING
A run is executing. Nothing is required of you.
NEEDS YOU
Execution has stopped on a permission or a decision. See 04.
READY
The run finished and produced a result for you to read.
FAILED
The run ended without a result. It is not silently retried elsewhere.
DONE
You closed it. It leaves active attention and stays on disk.

01.3  RUNS

Each execution is recorded as a run under the task, numbered from 01. Re-running is explicit and never automatic, and runs of one task are strictly sequential — a rerun refuses while the task is working.

2f rerun 1 --provider deepseek-harness   run 02 under task #1
2f open 1 --run 2                        one run's factual detail

Run history is for inspection. There are no scores, no winners and no recommendation — comparison is your judgment, not a ranking.

01.4  CONTINUING THROUGH ANOTHER AGENT

A new run is a continuation of the task, not a blank attempt. Its input (runs/<n>/prompt.md) is rebuilt from current task state — the original request plus your constraints and answers, and prior runs' results, verification and changed files — and handed to a fresh provider session. The task is persistent; provider sessions are disposable.

Add a constraint with 2f note <id> "<constraint>" (or 2f answer on a decision block); it becomes part of the next run's context with no manual copying. The original prompt.md is never overwritten.

01.5  EVENTS

Runs append normalized events to .work/tasks/<slug>/events.jsonl. The CLI and the Web UI subscribe to the same stream; neither reads provider processes directly. Providers that emit structured events produce a fine-grained stream; providers that do not produce a sparse one. 0x2F does not invent the difference away.

01.6  RESULTS & CLOSING

A finished run writes runs/<n>/result.md — prose written by the harness, not a diff or a score. 2f open <id> shows it.

2f close <id> removes a task from active attention — a finished run you have read, a failure you have understood, a stop you do not want to answer. It never resumes a provider and never starts an execution.

02 EXECUTION STATEstable SOURCEsrc/providers/

0x2F has three provider integration paths behind one contract. To 0x2F they are all just providers — the harness underneath differs, and the difference is declared, not hidden.

02.1  THE THREE PATHS

NATIVE     a deep adapter for one harness's capabilities
           claude-code · codex · deepseek-harness · gemini   built in
           claude-code: permissions → needs_you → same-session resume

ACP        one generic provider, Agent Client Protocol v1 over stdio
           any ACP-compatible agent — configured by manifest

COMMAND    one generic provider for headless executables
           any CLI that takes a prompt and prints a result — by manifest

2f providers lists every provider with its integration type and availability.

02.2  MANIFESTS

Everything that is not built in is added declaratively: drop one JSON manifest into .work/providers/ and it becomes a provider — no source changes.

{ "id": "cursor",
  "displayName": "Cursor",
  "transport": "acp",
  "command": ["cursor", "--acp"] }

ACP manifests may set "permissions": interactive (default — a permission request pauses the run and asks you), deny, or approve (headless auto-resolution). Command manifests must pass the task through the {prompt} placeholder. Commands are spawned as argv arrays, never through a shell.

02.3  AVAILABILITY

Availability is a deterministic, cheap fact: whether the provider's executable can be resolved — a PATH lookup, never a spawn. 2f providers shows it; AUTO routing (see 03) uses it; nothing ever runs against a provider that is not available.

02.4  WHEN NONE IS AVAILABLE

2f init reports what is ready, or what to install. 2f new refuses before persisting anything — a run against an unavailable provider would be doomed, so no task is created:

Execution provider "claude-code" is unavailable on this machine.
Expected executable: claude
Install or configure Claude Code, then retry.

Install a harness, add a manifest (02.2), or point routing at what you have (03).

03 ROUTING STATEstable SOURCEsrc/core/router.mjs

By default 2f new uses the configured routing default. When that default is auto, 0x2F picks a harness deterministically — it does not read the task text and never claims a provider is "best".

03.1  CONFIG

// .work/routing.json
{ "default": "auto",                    // "auto" or a provider id
  "prefer": ["claude-code", "deepseek-harness"] }

03.2  THE DECISION

auto selects available providers first, then the prefer list in order, then registry order. The decision — provider, node, reason, considered — is persisted with the run and shown by 2f open and the Web UI.

Same state + same policy → same routing decision. No randomness, and no automatic failover: a routed run that fails is failed, not secretly retried elsewhere.

03.3  OVERRIDE

2f new "Audit the auth flow" --provider auto         deterministic routing
2f new "Audit the auth flow" --provider claude-code  explicit
04 NEEDS YOU KINDS2 SOURCEsrc/core/lifecycle.mjs

0x2F interrupts you only when your input is genuinely required. Everything else it finishes on its own and tells you afterwards.

There are two reasons a task stops, and they are not interchangeable.

04.2  PERMISSION

A concrete operation needs authorization — an edit, a command. The request names the operation.

2f allow <id>
2f reject <id>

Where the provider supports it, answering continues the same run — nothing restarts. claude-code works this way natively.

04.3  DECISION

The harness cannot proceed without your judgment. It asks one concrete question.

2f answer <id> "<your answer>"

A decision is never allowed or rejected. Your answer is recorded with the task; whether execution can continue from it depends on the provider's resume capability — see 04.5.

04.3.1  THE DECISION PROTOCOL

A decision request is machine-read, not prose. A harness signals one by ending its run with exactly this block:

## Needs human decision
REQUIRED: yes
QUESTION: <the concrete question a human must answer>

Anything else — a bare heading, "None", "No decision required", or any amount of prose — is read as no decision. A finished run completes READY instead of interrupting you for work that did not need you. This is deliberate: arbitrary provider chatter must not be able to summon a human.

04.4  CLOSE

2f close <id> — or CLOSE in the Web UI — belongs to the 0x2F lifecycle, not to the provider, so it is available whatever the harness underneath can do: a stop you do not want to answer, a wrong request, a failure, a result you have read. It never resumes and never starts a run.

04.5  RESUME CAPABILITY

Providers differ, and 0x2F does not pretend otherwise. Answering always records your input against the task; continuing the same run is a provider capability.

                 PROVIDER CONTRACT
                        │
      ┌─────────────────┼─────────────────┐
   NATIVE              ACP             COMMAND
   adapter           manifest         manifest
   rich               broad            minimal
   events · resume    structured       start → result
   permissions        permissions*     no interruption

* an ACP manifest sets "permissions": interactive (default) · deny · approve

05 SURFACES STATEstable SOURCEsrc/server.mjs

The CLI and the Web are two surfaces over the same core. Both call the same shared actions and read the same normalized events — the browser never implements lifecycle or provider logic of its own.

05.1  THE LOCAL RUNTIME

2f ui behaves like opening a local application. If a 0x2F runtime is already healthy on 127.0.0.1:4242 (localhost only) it reuses it; otherwise it starts the runtime in the background, waits until it is healthy, and opens the UI. Runtime output lands in .work/ui.log. A second 2f ui probes the port and reuses the running runtime — another tab is fine.

Port: 2f ui <port> or 2f ui --port <n>. --no-browser starts it without opening a browser.

05.2  WHAT THE UI SHOWS

The browser is a control surface over tasks that are already running: see NEEDS YOU / WORKING / READY / FAILED, open a task and its run history, and ANSWER / ALLOW / REJECT / NOTE / SEND BACK / ACCEPT. The ledger renders the same normalized events the CLI reads. Closing the browser does not stop anything.

05.3  LOCAL ONLY

The API binds to 127.0.0.1, is token-authenticated (the shell sets an HttpOnly SameSite=Strict cookie), and refuses cross-site browser requests — 0x2F serves only its own loopback origin. Remote control is an outbound control layer, not remote execution (see 07 REMOTE).

06 LOCAL STATE STATEstable SOURCEsrc/core/store.mjs

Everything 0x2F knows about a project lives in .work/ inside that repository. Nothing is stored globally, and nothing leaves your machine unless you opt into remote pairing (see 07).

06.1  WHAT .WORK/ CONTAINS

tasks/<slug>/
Task state, runs, per-run prompts and results, the normalized event log.
project.md
The project described once — every prompt starts from it.
rules.md · knowledge.md · decisions.md
Working rules and promoted knowledge and decisions, carried into every prompt.
routing.json
The routing default and prefer list (03).
providers/*.json
Extra ACP/command providers (02).
ui.log
The UI runtime's output.
relay.json · relay-acks.json
Pairing credentials and the ack cache — only after 2f pair.

06.2  WHAT STAYS LOCAL

Task prompts and results may quote repository code, so they are written owner-only (mode 0600). Nothing is sent anywhere unless you pair. Execution is local-only: the local API binds 127.0.0.1 and never listens on other interfaces.

06.3  REMOVING STATE

Delete .work/ to remove 0x2F's state from a project — your source files are never touched. Project .work/ also survives uninstalling the CLI (see 08).

07 REMOTE STATEv1 SOURCEsrc/relay/

Remote control lets a phone drive the 0x2F running on your Mac — the Mac keeps running the work; the phone is a compact control surface. v0.5 pairs over the local network: the phone and the Mac must be on the same Wi-Fi, and no relay, account, or configuration is needed.

07.1  WHAT IT DOES

2f pair

Run it in the project you want to control. It detects the Mac's private LAN address, makes the Mac's runtime its own relay for the pairing window, and prints a phone-openable URL plus a one-time pairing code (with a "same Wi-Fi required" note). Open the URL on your phone — same network — and type the code into the trusted page served by the Mac itself. The phone then speaks the same Web UI against the Mac: see NEEDS YOU / WORKING / READY / FAILED, open a task, and ANSWER / ALLOW / REJECT / NOTE / SEND BACK / ACCEPT.

Every command, ack, event and snapshot between the phone and the Mac is end-to-end encrypted (AES-256-GCM keyed by the pairing code): a passive observer on the Wi-Fi sees only ciphertext, and nothing on the network can forge a command.

07.2  OFFLINE

While the Mac is offline the phone shows its own last-known state with a MAC OFFLINE banner and disables actions. Commands are never queued, and a retried command reuses its requestId so it can never execute twice.

07.3  PREREQUISITES

The Mac and the phone on the same Wi-Fi / private LAN (macOS may ask to allow incoming connections for node — allow it). The LAN surface is bounded: it exists only while pairing is active, only on private-LAN addresses, and the normal local API stays loopback-only — 2f ui is never reachable from other devices.

Away from the LAN, the hosted path remains available: 2f pair --relay https://… --client https://… (or the 0X2F_RELAY_URL / 0X2F_CLIENT_ORIGIN env vars) against a relay you deploy. That hosted relay stays private infrastructure — its deployment details live in the repository (relay/README.md and docs/remote-control.md).

07.4  LIFECYCLE & REVOKE

Pairing tokens are one-time and expire in 10 minutes; phone sessions live 30 days. 2f pair --off revokes remote access — a real revocation, not just a local disconnect — and on the LAN also closes the pairing surface within a second. Re-pairing rotates the Mac's credential and the encryption key, so a stale phone session can never silently come back after the Mac reconnects.

08 UPDATE & UNINSTALL STATEstable SOURCEpackage.json

0x2F updates and uninstalls through npm like any global CLI. Project state belongs to the project, not to the install.

08.1  UPDATE

npm update -g 0x2f               or: npm install -g 0x2f@latest

08.2  UNINSTALL

npm uninstall -g 0x2f            removes the CLI; project .work/ stays

Uninstalling never touches task state. To remove state from a project, delete .work/ (06.3).

09 LIMITS STATEcurrent SOURCEREADME.md

The current, honest boundaries of the product.

LOCAL-ONLY EXECUTION
There is no remote/mini-PC node yet. Remote control is an outbound control layer, not remote execution.
REMOTE CONTROL IS v1
No push notifications (the phone works while the app is open), no offline command queue by design, one phone at a time per Mac (re-pairing revokes the previous phone's session).
AUTO IS POLICY ROUTING
Deterministic, not semantic selection — and there is no automatic failover: a routed run that fails is failed, not secretly retried elsewhere.
RUNS ARE SEQUENTIAL
Runs of one task are strictly sequential. No concurrent or multi-agent orchestration of a single task.
NO EVALUATION
Run history is for inspection — no scores, no winners, no recommendations.