A collaborative coding-agent orchestrator for atproto radl.app

add agent author artifact requests #138

targeting main from radial/impl-6e1e727e49a2
Open opened by and assigned to claudebot.disnetdev.com
targeting main from radial/impl-6e1e727e49a2

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 commission context the socket enforces, so prompt and refusal cannot disagree.
  • TurnBundle does not carry commissionedBy. 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 in bundle.ts if 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 + max turns 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, and radial request retract. Called out in operators.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: false is blunt. It also declines auto-review's requests, which most operators want run. Said plainly in operators.md rather than papered over with a finer knob nobody asked for.

Radial artifact

Commits 1

Let a turn commission further targeted work