Agents commission further targeted work #
A turn may now write artifactRequest records of its own through the turn socket — "this chunk
needs three plans" — opt-in per operator, bounded one hop deep at the write path rather than in
the fold. Implements the based-on plan (plan-y2gfiuxiweu55g2s254mgp25) steps 1–8; step 9 (UI) is
the plan's own separable follow-up and is not in this round (see Not in this round).
Branch radial/impl-6e1e727e49a2, commit 8e1babed050bf9803b0b733350705540c232cc36, on main.
What landed #
Lexicon (step 1). com.disnetdev.radial.artifactRequest gains an optional commissionedBy
strongRef; pnpm codegen regenerated core/src/generated/records.ts in the same commit, and
packages/lexicons/README.md gains a section stating the three things a second implementation needs:
nothing branches on it, it is not the iteration bound (so omitting it launders nothing), and it is
not an authorization.
Core (step 2). RequestArtifactRpc (non-terminal; basedOn carries locators, not strongRefs,
because a container reads bare URIs out of its bundle and cannot be trusted to pin a cid);
TURN_LIMITS.maxTurnRequests (10), requestBriefChars, turnRequestBasedOnMax; and
agentMemberDids(index) — the fold's own active-agent set, exported so the one-hop bound reads that
set rather than a second spelling of it.
Sidecar (step 3). radial request create --type T (--brief TEXT | --brief-file PATH) [--based-on REF]... maps to requestArtifact in turn mode. --goal/--project/--subject/--assignee
are refused, not ignored (the message post --parent precedent). The direct-PDS request create
path is untouched.
Daemon turn socket (step 4). #requestArtifact writes the record open, anchored to the
commissioning request's own goal/project, with commissionedBy from turn context and never the wire,
at reqn-<24 base32 of sha256(uri#cid#ordinal)>. Four refusals carry the design, in order: a
review/answer turn commissions nothing (§7, §14C); no commission context means the operator
did not opt in; an agent-authored commissioning request means no (the one-hop bound); review and
answer are not commissionable types. Then edge validation: registered type of the anchor's own
scope, non-blank brief within the lexicon ceiling, per-turn cap, and every --based-on locator
resolved against the daemon's own index. Ordinals are reserved synchronously (they are the rkey), so
two concurrent connections cannot collide.
Invariant renegotiated (step 5). daemon/test/auto-review.test.mjs's "the daemon writes no other
request" now allows exactly ['auto-review.ts', 'turn-socket.ts'] and adds source assertions pinning
what keeps §14C alive: review/answer refused as types, the review/answer mode refusals, the opt-in
check, the agent-author gate, and no assignee on the record. The CLAUDE.md bullet was rewritten in
the same commit.
Config + dispatch knob (step 6). run.agentRequests: author (default false), dispatch
(default true), max (default and ceiling 10 — a larger value is refused at parse time rather
than clamped silently). dispatch: false is read by selectDispatchable and selectClaimable, so
this daemon never claims work it would then refuse to run. TurnObservation.requests →
TurnResult.requests → a requests_json ledger column (additive ALTER TABLE, like branch/
pull_uri) plus one log line per turn.
Prompt. buildPrompt offers the verb only when the socket will accept it, and states the cap
and the one-hop bound; a review or answer turn is never offered it. Without this the feature would
ship with no agent knowing it exists — and an unconditional mention would spend turns discovering a
refusal.
Docs (step 8). design.md §7 (a new "Commissioned work" paragraph with the bound and the rejected
fold-rule alternative), §9 (what a turn may emit), §13 (the "cannot commission new work" sentence
rewritten honestly: default-off, and what an injected turn can do when it is on), §15 (three resolved
decisions), §16 (open question 2 partly answered); packages/daemon/README.md (a new section + the
turn-type table); docs/radial-json.md (run.agentRequests); docs/operators.md (a new
"Somebody else's agents can commission work you pay for" subsection under Claims).
Checks #
pnpm codegen && git diff --exit-code clean (the generated file is committed current), pnpm lint
(which includes typecheck, the type-strip lint, the NUL scan and the codegen --check) and
pnpm test all pass: core 417, atproto 35, ingest 50, sidecar 133, daemon 623, ui 93,
transport-iroh 7, transport-iroh-wasm 13 — 0 failures.
New coverage: core/test/commissioned-request.test.mjs (a commissioned request folds as an ordinary
open request, is claimable and fulfilled like any other, is fold-inert — same index and same
digest with the field stripped — and is invariant under 100 arrival permutations); 15 turn-socket
tests (happy path, every refusal, the cap, wire-supplied anchors ignored, retry adoption and the
different-type refusal, project scope); dispatch tests for the dispatch knob; config and
resolveRunConfig default tests; harness prompt tests; and a private-space smoke test that drives the
real turn socket over privateActorRegistry and asserts the request is sealed into an envelope, never
posted, and folded by the peer replica with matching digests.
Deviations from the plan, and why #
- Scenario coverage (step 2b) is a dedicated permutation-covered file, not records added to
goldenScenario(). 86 assertions across 12 test files read the golden scenario, and adding a request + claim + artifact to it perturbs list assertions everywhere for a field that changes nothing. The precedent the plan itself cites —privateScenario()as "a second fixture rather than more records in the golden one, because the most valuable thing it says is that nothing changed there" — applies exactly. The new file carries the same 100-permutation invariance the CLAUDE.md rule asks for, plus a with-field/without-field digest comparison the golden scenario could not have expressed. - The prompt change is not in the plan's ordered work. It is required for the feature to be
reachable at all, and it is gated on the same
commissioncontext the socket enforces, so prompt and refusal cannot disagree. TurnBundledoes not carrycommissionedBy. D4 lists "the bundle" among the field's purposes, but no ordered step asks for it and a commissioned turn does not need to know it was commissioned. One line inbundle.tsif it is wanted.
Not in this round #
Step 9, the UI: the commissionedBy provenance line on request cards and the "agent-requested" badge.
The plan scopes it as separable follow-up ("No fold changes, so the UI ships behind everything above
without blocking it"), and it is the natural next request. Until it lands, a commissioned request
renders exactly like any other agent-authored request — the provenance is in the record and in the
daemon log, but not yet on screen.
Remaining risks #
- Spend. One human click can now buy
1 + maxturns per opted-in operator (default cap 10), times the operators who dispatch agent-authored work. Four things bound it: default-off authoring, the one-hop gate, the per-turn cap, andradial request retract. Called out inoperators.md. - Retry ordering. A retried turn that commissions its requests in a different order strands or duplicates within the cap — deliberate, bounded, attributable, and documented at the rkey helper; the same class of corner auto-review's removed-writer case accepts. Ordinals count attempts, so a turn that keeps failing mid-write also spends its cap rather than looping.
- One DID as both human and agent. Such an operator forfeits commissioning from their
hand-written requests, because the bound reads membership kind. Consistent with every other
agent-vs-human question in the fold; documented on
agentMemberDids. dispatch: falseis blunt. It also declines auto-review's requests, which most operators want run. Said plainly inoperators.mdrather than papered over with a finer knob nobody asked for.