Skip to content

Control effects

Source: docs/runtime/control-effects.md
Status: current

Parents: ../canon/grain.md. Compact fold lives here. Authorization is not Control: approval.md. Session/Job ontology: session-job.md. Living ADRs: ../decisions/calls-propose-verdicts-dispose.md, ../decisions/budget-is-data-not-policy.md, ../decisions/scheme-at-structure-cadence.md.

PEW is work; verdict dispose is the pure/impure cut; control effects are named traps with different return laws; policy chooses named restarts; program bodies are always flat — time, join, and re-invocation belong to the interpreter. Compact is a turn-queue fold, not a Control trap.


1. Flat execution — interpreter-owned wait

Section titled “1. Flat execution — interpreter-owned wait”

LAW. Userland and kernel programs never await. A run starts, computes, and returns data. It does not block on humans, I/O, PEW seals, or sibling work.

Waiting, parallelism, and re-invocation are interpreter responsibilities (daemon / claim-owner host / PEW substrate) — not Scheme (or ts-builtin) control-flow syntax.

Who Does Does not
Program body Return Done(series), Control(condition, meta), Abort(reason), parallel work descriptors, optional join/after lambdas (as values) await, park Promise, stack suspend, thread sleep
Interpreter Schedule PEW; fan-out map/foreach; join seals; apply verdicts; hold open control obligations; re-invoke procedures when results land Push async syntax into the program language
PEW / surfaces Durable “not done yet” + progressive face Live inside a program stack frame

Author vocabulary is return values, not pause:

run(env) →
Done(series) — host disposes Expectations, KV, inbox, log-ops
Control(condition, meta) — control obligation; this run is finished
Abort(reason) — fail closed; this run is finished

There is no Pause as a program-facing return. Host-internal “lane stopped” / fuel stop is SCHED_STOP bookkeeping after a control condition or host law — still not an await inside a body.

Re-invocation is a new flat evaluation after the host changed the world or delivered a settlement (restart name + payload). It is not classical resume k v into a frozen stack.

(map (lambda (x) …) items) returns a list of work descriptors. The interpreter mints N obligations and runs them concurrently. map / foreach do not mean sequential await-in-a-loop.

Forbidden Why
await / Promise-style suspend in program bodies Hides wait inside the language
harness/pause as userland API Suggests the body is still running while blocked
Saga yield need-* as OS spine Same class of mistake at the extension layer
One frozen stack continuation across claim death PEW one-execution + re-invoke a new run instead

Claim-owner host modules (TypeScript actors — e.g. OpenAI-compat) may use process-local async to talk to HTTP. That is interpreter/host, not program language. Process-local parks that outlive the pure tick must be dual to durable PEW surfaces.


Layer Grounding Owns
OS process model PEW (durable Expectations + openWork) Work identity, claim, cancel tree, seals, progress
Extension language map → reduce → verdict / Control return → host dispose Programs propose; host applies
Execution law Flat runs; interpreter owns wait/join/re-invoke Time and parallelism
Control ABI Four trap classes How a flat run’s Control return is interpreted
Control situations Conditions + named restarts What is wrong; which recoveries exist

Rejected as primary: whole OS as algebraic effects; one big interaction tree; userland around-advice; unified saga yield need-*; “everything re-enters the same reduction” (only FAULT_RESTART re-invokes the same event); async/await in program bodies.

Permission / UAC is not a Control return and not a PEW/Proposal. Sole home: approval.md.


Every control effect must state which class it is. If it cannot, it is not a control effect yet.

Class OS analogue Interpreter law after Control return Typical
FAULT_RESTART Page fault Fix world → new flat run, same event defined-action compact handlers that still return need-compact
SYSCALL_BLOCK Blocking call Hold obligation → new flat run on a settlement event need-credential, need-attach, need-elicit
SIGNAL_CANCEL Signal to process group Drain/cancel openWork; do not re-run the interrupted step need-interrupt-settle
SCHED_STOP Scheduler stop No auto re-invoke; manual wake only need-fuel-pause

Not control: minting PEW (normal Done series); inbox post/consume; KV setup; PEW progress yields; session compact (the fold below); session authorization.

Session compact does not return Control(need-compact). Session authorization does not return Control(need-approval). Those two seats are host pipelines, not trap returns.


Chrome groupings (Continue / Abort / stop) are not the kernel algebra. Named restarts are. Establishers of restarts = claim owner / host resource site. Handlers (policy) choose among established restarts only.

Condition Situation Named restarts Class
need-compact Defined-action program: processable log too full compact-then-reenter, abort-turn FAULT_RESTART
need-credential Secret ref unresolved resolved (host credential seat only), abort SYSCALL_BLOCK
need-attach Inference source not ready attached, abort SYSCALL_BLOCK
need-elicit Dangerous adopt / structured confirm confirm, reject, answer SYSCALL_BLOCK
need-interrupt-settle Open work must quiet before new admit barrier complete only SIGNAL_CANCEL
need-fuel-pause Re-entry / lane depth overflow manual-resume only SCHED_STOP

need-quota is not a fifth trap class — Abort via policy, default restart abort only.

Settlement delivers a restart name + payload into the env of the next flat run, not into a suspended stack frame.


Session compact is a CompactJob on the turn queue plus a config-addressed Scheme program. There is no Control entity on that loop, and no executor for the job — the turn loop runs it when the chunk is shifted, after tools have settled. Occupancy that fires compact is config.json compactTriggers. Occupancy that pauses a live completion so the fold can finish is pauseForCompaction (not shipped).

Host gates — not the Scheme file. Empty eat and compact-on-compact (no new work since the last fold) reject; the strategy never sees them. Keep-all (preserve floors leave no fold range and a nonempty keep) skips auto compact, including hard overflow. Compact failure is an execution failure: the job stores a human notice (couldn't compact — try again later) and is not a cutoff. No second emergency algorithm. The notice is not injected into the next model prompt. Strategies are the happy path only.

CompactJob on queue
→ compactRejectReason (empty | already-compacted) → complete, not a cutoff
→ sliceCompactZones(eat, keepFloors) → { fold, squeeze, keep }
→ fold empty + keep nonempty → keep-all, complete, not a cutoff
→ applyCompact(program, fold, full-eat) → card text | failure notice
→ job.card = card; job is the cutoff

The job is not “start here.” A successful job means everything strictly before it is projected: the fold zone is the card string, the squeeze zone is stored tool lines (regeneratable tools omit call and result together), and the keep zone stays verbatim. Cells after the job stay full. Floors and focus are read from the ContextNeed before the job, else compactKeepTurns. reason is stamped at enqueue.

A document that already has a folded TurnCompactChunk still projects representation the old way. New folds do not create that chunk.

The program is a reducer. config.json compact names the file (default @inhuman.tools/harness-server/compact.scm). Owning the fold is repointing, never editing a seeded copy. Dist body:

(define (compact compact-transcript full-transcript)
(let ((xs (transcript/cells compact-transcript)))
(if (null? xs) xs (list (car xs)))))
(define forced-compact compact)

Keep-first stub: the card is the first fold cell. Slash /compact (reason: "user") calls forced-compact when defined, else compact. Auto / overflow always call compact. Transcripts are arguments. The program returns the card; the host stores that text on the job. Empty or non-shrinking cards fail closed (empty-card / did-not-shrink).

CompactJob is a Job, not PEW, and nothing registers an executor for it. Fields: reason (overflow | need | user), card, notice, failed, keepTurns (the dial reading at the fold). Auto enqueue is hard overflow after a completion (keep-all vetoes it). User slash mints a compact turn whose queue is the job, reason: "user". A legacy TurnCompactChunk remains a WorkContainer with representation for old documents.

applyCompact loads the strategy with Arrival execState and calls the named function. Programs stay flat; the host owns the walk. Same hatch as remainder: a local path is the operator’s; the dist token updates with the package.

Must not: dual-write live PEW into log and KV; delete PEW archaeology by default; store full history in KV; treat compact as a Control trap; run compact on the hot paint path.


Mappers (programs) are pure: (event, fold-views, KV-snapshot) → proposals. Reducers consolidate. The host disposes a Done series (mint PEW, apply KV, inbox, log-ops). Introducing a mapper is SAFE; introducing a reducer is DANGEROUS and audited. Law: ../decisions/calls-propose-verdicts-dispose.md.

Budget is data, not a kernel kill-switch: ../decisions/budget-is-data-not-policy.md. Scheme does not run on the hot paint path: ../decisions/scheme-at-structure-cadence.md.


  1. Uniform “re-enter same event” for all trap classes
  2. Conflating FAULT_RESTART re-invoke with SYSCALL settlement event
  3. Process-local host async without durable PEW dual + abandonment law
  4. Partial series dispose without residual obligation for the unapplied tail
  5. Fuel stop only process-local → storm after claim-orphan unless re-derived
  6. Userland await/advice/sagas that invert audit and authority
  7. Session compact reintroduced as Control(need-compact)
  8. Session authorization reintroduced as Control(need-approval) or a Permission PEW/Proposal
  9. Author-facing pause/await under a new name

Doc Role
../canon/grain.md Causal spine; compact is a queue chunk, not this file’s ABI
approval.md LICENSE WHEN; not a trap
session-job.md SessionTurn queue; Job vs PEW
../packages/harness-server/src/daemon/session/compact.ts host gates, compactDecision
../packages/harness-server/src/daemon/session/compact-env.ts applyCompact
../packages/harness-server/compact.scm Dist fold
../packages/harness-sdk/src/models/session/jobs/CompactJob.ts cutoff job
../packages/harness-sdk/src/models/session/turn/compact-chunk.ts legacy TurnCompactChunk