wrighty worker schedules one explicitly eligible item at a time, claims it with a fenced handle,
starts Claude Code, Codex, Copilot, or OpenCode headlessly, renews the claim for a fixed budget, and records
the workspace and vendor session address. Wrighty is the scheduler; the vendor CLI remains the
agent runtime.
Worker mode runs an unattended agent that executes commands and modifies files on this machine under the permission profile you select. Start with a dry run and one item. Worktree mode also requires the selected agent's Wrighty skill to be installed at user scope or committed in the current Git revision; see Agent skills:
wrighty create --title "Automate this" --body "..." --auto --agent claude
wrighty worker --dry-run --once --agent claude
wrighty worker --once --agent claude --workspace-mode worktree --item-timeout 30mDry runs never claim an item or start an agent and do not require confirmation. Before a live run,
Wrighty performs a read-only preflight and reports how many items are currently claimable plus the
first candidate. With --once, no claimable item means it prints the candidate diagnostics and
exits without a risk warning or confirmation prompt. A continuous worker prints the same complete
initial diagnostics, confirms once because it may process future items, and then uses compact
one-line idle events while polling. Non-interactive and JSON live runs must acknowledge the risk
with --yes. Preflight is only a snapshot; the contention-safe atomic pick still occurs after
confirmation.
Eligibility is opt-in. Local Markdown stores managed wrighty.policy.execution: automatic and optional
wrighty.policy.agent; GitHub uses the Project fields Wrighty policy - execution,
Wrighty policy - agent, and Wrighty policy - context approval. Only exact Automatic allowed
authorizes GitHub work, and only exact Approved makes it a potential claim candidate; unset,
missing, and invalid values fail closed. Vendor resolution is --agent,
then the item preference, then worker.defaultAgent; Wrighty errors instead of
guessing. A generic worker started without either --agent or worker.defaultAgent prints an
informational notice that only item-pinned work can run. If automation-enabled items without a
resolved agent later appear during continuous polling, Wrighty reports that changed condition once
and then returns to compact idle messages. --filter key=value adds AND filters, --max-items
bounds spend, --idle-timeout bounds idle waiting, and --json emits one JSON lifecycle event per
line. wrighty worker --check runs a short, read-only vendor probe and verifies a usable session
handle; the probe still invokes the vendor and may incur usage.
The user-scoped enabledAgents allowlist is an additional automatic-selection gate. An agent
resolved from item policy or worker.defaultAgent is skipped while disabled or not detected,
without falling back to another vendor. The same gate applies to queued resumes, continuations,
directed handoffs, and automatic recovery targets. A generic worker refuses to start with
NO_AGENT_ENABLED when no detected agent is enabled. Supplying --agent explicitly is a one-run
override of the saved preference, but the named CLI must still be detected: it expresses current
operator intent without silently enabling that agent for later workers. Manage the allowlist from
the web console's Agents menu; see User settings.
By default, every fresh worker session starts with a separate requirements-only turn under a mechanically restricted read-only permission profile. Wrighty supplies the exact task context over standard input, withholds claim credentials, and denies command execution, file writes, network access, external tools, and tracker mutations. The agent may use only the supplied context and read-only repository file tools. Work-item content cannot authorize a diagnostic, pre-check, package command, or other side effect before its own readiness has been established.
The agent decides whether the approved work-item context states a clear intended outcome, leaves no material user-owned decision unresolved, and provides enough evidence to verify completion. It may inspect code, tests, and repository conventions read-only to resolve ordinary implementation details, and it may make low-risk reversible assumptions. Missing headings or a particular Markdown template do not make an item inadequate.
The first turn must return a bounded, versioned wrighty-readiness JSON verdict. A valid ready
verdict makes Wrighty resume the same vendor session with the configured implementation permission
profile, claim credentials, normal work instructions, and the remaining portion of the item's
timeout. A needs-clarification verdict starts no implementation turn and enters the ordinary
needs-attention lifecycle with the session, claim, and workspace retained. Missing, duplicated,
malformed, or unsupported verdicts fail closed and are reported as protocol errors, not as proof
that the requirements themselves are inadequate. When the provider returned a resumable session,
Wrighty retains it in needs-attention; when no session was created, Wrighty restores the source
status, cleans up any worker-created workspace, and releases the claim so a false resumable state is
not advertised. Provider and timeout failures are reported as assessment-unavailable and never
grant implementation authority.
The gate adds one model turn and one process start/resume cycle, but no second vendor session. The readiness exchange remains visible if an operator later opens the retained session; this is useful context for a clarification resume and part of Wrighty's execution policy. Ordinary resumes, retries after implementation, and cross-agent handoffs do not add another fresh-session gate.
This check is different from the surrounding gates:
- context approval decides which tracker content the agent is allowed to receive;
- automatic-execution policy authorizes an unattended process to start;
- requirements readiness judges whether that content is sufficient to begin implementation; and
finishstill requires the tracked work and its verification to be genuinely complete.
The repository setting defaults to enforced:
{
"worker": {
"requirementsAssessment": {
"mode": "enforced"
}
}
}Use inline to fall back to the lower-cost single-turn behavioral guard, where the implementation
agent is instructed to assess before acting but Wrighty cannot mechanically prevent an early side
effect. Use off only as the operational compatibility escape hatch; it emits one
requirements-assessment-disabled warning at startup (or before an explicit fresh-item run).
Ordinary blocker handling, approved-context checks, authorization, claims, and completion rules
remain active in either fallback. Existing sessions keep the prompt they started with.
Wrighty distinguishes three states:
- Supported means Wrighty has an adapter for the vendor: Claude, Codex, Copilot, or OpenCode.
- Installed means that vendor's executable is currently discoverable on this machine.
- Ready means the stronger
wrighty worker --checkprobe also succeeds.
See Supported agents and surfaces for how this worker support relates to skills, interactive resume, Desktop opening, and cross-agent handoff.
Ordinary worker preflight checks installation only; it does not launch an agent or infer authentication and subscription health. A generic worker skips items whose resolved agent is not installed, without claiming them, and can continue to later items assigned to an installed agent. It reports per-agent unavailable counts in human and JSON candidate diagnostics. A long-running idle worker refreshes discovery, so installing a missing CLI makes newly compatible work eligible without restarting Wrighty.
Vendor intent is never rewritten. Resolution remains --agent, item policy, then
worker.defaultAgent, and Wrighty does not fall back to a different installed vendor. An explicit
--agent, an exact --item, or a recorded session that names an unavailable vendor fails with
AGENT_NOT_INSTALLED and identifies the item and resolution source where applicable. When no
supported CLI is installed, a live general worker fails with NO_AGENT_INSTALLED; a dry run still
prints diagnostics and never invokes a vendor process.
An unavailable-agent lifecycle event includes the aggregate count and a per-agent map:
{
"type": "agent-unavailable",
"candidates": {
"unavailableAgent": 2,
"unavailableAgents": {
"claude": 2
}
}
}Executable discovery is a snapshot immediately before admission. If the executable disappears
between that check and process creation, Wrighty reports AGENT_START_FAILED, releases the claim,
restores the item's prior dispatch state, and removes a worktree created for that attempted run.
wrighty init ensures the Project policy schema and dispatch-state lifecycle labels exist and,
unless --skip-issue-forms is selected in the approved initialization plan, scaffolds one neutral
task form. A Project writer reviews the issue, selects Wrighty policy - agent when needed, and changes
Wrighty policy - execution to Automatic allowed. Provider capacity remains a property of the worker machine;
worker preflight still reports a missing or unsupported local vendor executable. Use
wrighty init --skip-issue-forms when the repository manages its own issue-template experience.
Interactive initialization asks whether to commit and push forms it changed. Automation must opt in
with wrighty init --yes --publish-issue-forms; --yes by itself does not publish repository files.
Wrighty's generated chooser configuration disables blank issues for contributors, although GitHub
continues to expose a maintainer-only blank option to users with Write access or above.
Wrighty is the scheduler; the vendor CLI enforces what the spawned agent may do. worker requests
one of two configurable vendor-neutral implementation permission profiles per agent, and each
adapter maps it onto that vendor's own flags:
| Profile | Intent |
|---|---|
workspace (default) |
The least privilege that still completes the tracked work: command execution and network stay available, file writes are confined to the workspace wherever the vendor can express it. |
full |
The vendor's unrestricted mode. An explicit opt-in, never a silent fallback. |
The enforced requirements gate also uses an internal read-only profile. It cannot be selected as
an implementation profile: Wrighty applies it only to the first assessment turn, then switches to
the configured workspace or full profile after a valid ready verdict.
Network is part of the least-privilege profile on purpose. With the GitHub backend the agent
runs its own wrighty get and the skill runs wrighty init --check, both of which reach the GitHub
API. A sandbox that disables network makes the agent produce no work at all.
Select a profile with worker.agentPermissions, and override one vendor with
worker.agents.<vendor>.permissions (see Configuration). Every live run
prints the effective profile per agent before the confirmation prompt, and --json runs carry it
on the started, resumed, and dry-run events as a permissions object.
Verified through 2026-08-25 with Claude Code 2.1.219, codex-cli 0.145.0, GitHub Copilot CLI 1.0.75, and OpenCode 1.18.23:
| Agent | workspace maps to |
Confines file writes | Network | Enforcement |
|---|---|---|---|---|
| Codex | --sandbox workspace-write -c sandbox_workspace_write.network_access=true |
yes | yes | enforced |
| Copilot | --allow-all-tools |
yes (workspace plus the system temporary directory), for shell commands as well as file tools | yes | enforced |
| Claude | --permission-mode acceptEdits --allowedTools "Bash Edit Write Read Glob Grep NotebookEdit TodoWrite Task" |
no | yes | partial |
| OpenCode | inline permissions allow tools but deny external_directory and interactive questions |
no for shell commands; native file tools are confined | yes | partial |
| Agent | assessment read-only maps to |
File writes | Commands/network |
|---|---|---|---|
| Codex | --sandbox read-only |
denied | network disabled by the sandbox |
| Copilot | deny write, shell, and url; disable built-in MCPs; disallow the temporary directory |
denied | denied |
| Claude | --permission-mode dontAsk --tools "Read Glob Grep" |
mutating tools unavailable | Bash and network tools unavailable |
| OpenCode | inline permissions deny all, then allow read, glob, grep, list, and skill |
denied | denied |
| Agent | full maps to |
|---|---|
| Codex | --sandbox danger-full-access |
| Copilot | --allow-all (all tools, all paths, all URLs) |
| Claude | --dangerously-skip-permissions |
| OpenCode | inline permissions allow all except interactive questions, with --auto |
The asymmetry is real and is reported rather than hidden. Claude Code exposes no verified
headless mode that confines file writes to the workspace: under acceptEdits with a tool
allow-list a -p run completes without a permission stall and reaches the network, but writes to a
parent directory succeed through both Bash and the Write tool, and enabling the CLI's built-in
sandbox through --settings did not confine them either. What Claude's workspace profile does
deliver is tool-level narrowing: tools outside the allow-list are denied instead of auto-approved.
The live-run warning marks this profile as weaker than requested, so nobody concludes a Claude run
is contained to the worktree.
Copilot separates tool approval (--allow-all-tools, documented as required for non-interactive
use) from path and URL access (--allow-all-paths, --allow-all-urls, both included in
--allow-all), so the workspace profile keeps tool approval while leaving path verification in
place. Verified end to end: under --allow-all-tools a parent-directory write was denied through
both Copilot's file tool and the shell ("Permission denied and could not request permission from
user"), while the identical prompt succeeded once --allow-all-paths was added. Copilot applies
path verification to shell commands as well as file tools. Codex was likewise verified end to end:
network reachable, workspace write accepted, parent-directory write denied. OpenCode's native file
tools honor external_directory: deny, but its shell runs with host filesystem and network
authority. Wrighty therefore reports OpenCode's workspace profile as partial and does not claim
that it confines all file writes.
The read-only liveness probe behind wrighty worker --check and the provider capacity probe never
carry the configured profile. They only prove the vendor answers and honors a session handle, so
they run with Codex's read-only sandbox, Claude's tools disabled entirely, no Copilot
tool-approval flag, and OpenCode's deny-by-default read-only permissions.
A permission denial reaches the operator on both of the paths it can take.
If the agent hits the denial, reports it, and ends its turn — the usual outcome, since the vendor
refuses the individual tool call rather than killing the process — the run ends without
wrighty finish. Wrighty retains the resumable claim, marks the item needs-attention, and
records the agent's final message as the run outcome, so the reason is
visible in wrighty get, wrighty status, the web item panel, and the GitHub status comment.
The specificity of that reason comes from the agent: the worker prompt instructs it to explain the
blocker, but Wrighty repeats what the agent said rather than diagnosing the denial itself.
If instead the vendor CLI terminates on the permission error, the adapter classifies it as a
permission-denied failure. Because re-running cannot clear a permission or configuration
problem, Wrighty stops there: it marks needs-attention, posts the status comment, and releases
the claim with that state preserved — it does not return the item to the claimable pool, where the
next poll would spawn the same agent and fail identically. authentication and
billing-unavailable failures are treated the same way. Retryable capacity failures keep their
separate deferred-retry behavior.
full grants the vendor's unrestricted mode: command execution and file access across the whole
machine, under the credentials of the user running the worker. Prefer a worktree workspace and
the default workspace profile, and treat full as a deliberate, per-vendor decision for work
that genuinely needs to reach outside the worktree.
Selecting an item and starting a vendor process are not the same moment, and authoritative state can change in between. Every worker launch therefore passes one internal launch preflight with three ordered stages:
| Stage | When | What it protects |
|---|---|---|
| Pre-claim | While scanning candidates | Avoids claiming work that authoritative Project policy or local agent availability already rules out. |
| Post-claim | After the claim, before the workspace exists | Catches a policy change made between selection and claim, and resolves the effective agent permission profile before anything is created on disk. |
| Pre-spawn | Immediately before the vendor process starts | Catches anything that changed while the workspace and session metadata were being prepared. |
Three built-in checks run today. worker-policy re-reads the item and applies the same authoritative
Project worker policy evaluation the candidate scan used, so an item
can never be admitted by one path under rules the other would refuse; it gates fresh launches only,
because a resume re-enters a session that already exists here and claim ownership is the authority
for that. agent-permissions resolves the effective spawned-agent profile
and refuses rather than falling back — an unresolvable profile would otherwise decide how much
privilege an unattended agent receives, and it now fails before a worktree is created rather than
at invocation time.
The candidate scan reads the GitHub Project item's context-approval value alongside its other
projected fields. It skips anything except exact Approved before claiming, without loading the
issue conversation, and continues to later candidates. This is only a cheap fail-closed filter:
approved-context still runs at post-claim and pre-spawn, and asks a different question at each.
Post-claim asks whether there is an approved context at all; it is the expensive read, placed after
the claim so the answer cannot be raced and before a workspace exists so a refusal costs nothing to
unwind. The full check is deliberately absent from pre-claim, where assembling a context for every
candidate the scan considers would pay a conversation read for items about to be rejected far more
cheaply.
Pre-spawn asks whether the context still holds. For a fresh launch it must be the same revision the post-claim stage admitted. A resume, recovery or retry never runs post-claim — it re-enters an already-claimed item — so it compares against the context recorded with the session it is resuming, and admits an unchanged or purely additive one. It also admits a context that changed only outside what that session was given — a comment it never received being excluded, for instance — since nothing it holds moved and there is nothing new to hand it.
A change to a supplied entry's provenance also refuses an unattended resume, even though no approved text moved — a renamed repository or a deleted commenter's account leaves every comment reading the same while changing who it is attributed to and where it can be found. The agent was told both, so it is reported and left to a person rather than resumed over.
A change that rewrites what the session already saw refuses an unattended resume, because nobody
decided the agent should carry on with superseded content and a resumed agent cannot unsee what it
read. It does not refuse a resume a person asked for: naming the item (worker --item), or
clarifying a paused session and requeueing it, is that decision. An automatic retry is not — Wrighty
scheduled it. A permitted override emits a policy-override event carrying
CONTEXT_RESUME_SUPERSEDED, so continuing across a change never looks like nothing having changed.
Consent is inferred from how the run was started, not from whether you knew about the change. Naming
an item you have not looked at recently admits a resume across an edit somebody else made while the
session was paused, and the policy-override event is what tells you afterwards. Read it when it
appears; it names what changed.
Such a resume is the one that carries the whole approved context again. An ordinary resume sends only what was newly approved, because what the session already holds is still correct; here it is not, and a change that rewrote or withdrew earlier text has no delta to express. The agent is told that the earlier context is superseded by an operator's decision, given the complete current snapshot under the same fencing as a fresh launch, and asked to report what it had already done that the withdrawn requirements called for. It is never told to re-read the item from the tracker — that is the agent self-fetch the launch gate exists to prevent.
A session with no recorded context cannot be resumed at all, including by an operator: accepting a
change requires having been able to read one. Sessions recorded before approved-context support are
in this state and need a fresh session. So are sessions whose context was recorded under an older
revision format: their digest was taken over a different canonical form, so no difference against
the current one can be computed, and an override has nothing to override. Upgrading Wrighty across
such a change therefore ends any session paused at the time — it is reported as
CONTEXT_MANIFEST_UNAVAILABLE, and the item is picked up by a fresh session.
Assembling a context is bounded by worker.context.*; exceeding a bound refuses
the launch rather than truncating, because dropping part of an approved task would change the
requirements while leaving the revision digest looking authoritative.
An enforced fresh launch sends the restricted first turn the rendered approved context: the trust boundary, the item's identity and source, the approved title, description and discussion in order, and the approval instant and revision. It deliberately omits finish, commit, and implementation instructions. After a ready verdict, the second turn restores those operating instructions without re-sending the context the same session already holds. Immediately before that permission increase, Wrighty re-runs pre-spawn admission so a task edit, approval change, or execution-policy withdrawal during assessment invalidates the verdict.
The agent is not told to read the item, because reading it returns whatever is on the tracker at that moment — comments nobody approved, edits made after the approval — which is what the launch gate refused.
The prompt travels on the vendor's standard input, never in its arguments. An argument list is readable by every process on the machine and is printed in worker events, so an approved context placed there would be published on every run. Each vendor asks for a piped prompt differently; Wrighty selects the right form per adapter and passes no prompt flag that would place text on the command line.
For a backend without an execution-context provider, the enforced gate renders the title and body
already held by the claimed worker directly into the assessment prompt. The inline and off
fallbacks retain the older bootstrap behavior.
Wrighty keeps one current status comment on a GitHub issue. It combines what happened, what the agent reported, what input is needed, the continuation controls, and the less common recovery commands. The primary answer is visible immediately; run and session diagnostics and recovery commands are collapsed below it.
The comment is a working note that goes stale, so Wrighty replaces it after each terminal run and
trims it once the item is requeued or archived. The durable session record separately stores the
latest structured run report on both backends. worker.sessionReportMode is accepted only for
compatibility with older configurations and no longer creates separate GitHub report comments.
When Wrighty must fall back to the agent's raw final response, it removes the embedded report block before quoting that response. The structured fields already appear once in the combined comment; leaving the block in would duplicate them, and its inner fence would also close the surrounding code block and spill the rest of the comment into raw Markdown.
A resumed session is expected to still hold the context it was launched with, and every vendor measured did after eight resume turns. Under sustained window pressure one lost it entirely — but reported nothing available rather than inventing an answer, which is what makes recovery safe to offer rather than necessary to guess at.
wrighty context <item> --revision <digest>It serves that exact revision or nothing. The digest is in the resume prompt, and an agent that has lost its context is told to run this before doing anything else.
The refusal is the point. An agent cannot ask for a newer approval, an edited description, or comments nobody has decided on — so this is not the discovery the approval gate prevents, but a cache miss on content Wrighty already approved and pinned for this run. When the digest no longer matches, the approved context moved while the run was in flight: the agent is told to stop and report rather than continue against requirements nobody approved for its session.
Nothing is stored to make this work. The context is read afresh and its digest recomputed, so the approved bodies are never kept in local state — the same guarantee without retaining the content.
The current status comment exists only on GitHub. It carries both the
<!-- wrighty-handover:v1 --> marker and a strict <!-- wrighty-session-report:v1 ... -->
identity marker for the latest terminal run. Wrighty replaces it after each run and trims it once
the item is requeued, archived, or its workspace is cleaned up, so there is only ever one and it
always describes the latest run.
Its content is available everywhere, because the comment is a rendering rather than the source.
wrighty get <item> prints the same next-step actions under Next actions, and the web console shows
them as buttons on the item. On Local Markdown that is the only form they take — nothing is
published, and worker.handoverComment has no effect there.
Wrighty writes claim events and one current status comment to a GitHub issue. Neither is a
requirement, and none reaches an agent as task context. They are recognised by the account Wrighty
posts as: a comment is treated as Wrighty's own only when its author is the login the configured
gh credential authenticates as.
This is an identity rather than a permission level, and deliberately the stricter of the two. GitHub lets a user with write access edit another user's comment without changing its author, so a rule of "any maintainer's marker counts" would let a marker appended to a maintainer's requirement drop that requirement from what the agent receives, while it stayed visible to everyone reading the issue.
Two consequences worth knowing:
- If the login cannot be established — no credential, a rate-limited lookup, no network — nothing is excluded and every marker-bearing comment is decided like ordinary discussion. That costs a re-approval; the alternative would hide content from review.
- A status comment written by a different installation — another machine, or a colleague's account — is not recognised and reads as ordinary discussion, so it blocks a resume until approved. Running Wrighty under its own account makes the recognition exact; on a personal account, its comments and yours share an author.
Reaction-based context approvals are a separate question and remain unavailable: 🚀 and 🎉 are operational continuation controls on the current Wrighty status comment, not decisions that include or exclude task content.
What the status comment suggests depends on the backend, because the backends differ in a way that changes the advice rather than only its wording: only GitHub has a discussion to append to.
On GitHub, a configured trusted author can reply with the clarification and stop there. After the short edit debounce, a continuous worker detects that reply, queues the retained session, and passes the reply as new context. No approval change, command, or reaction is also required.
Do not edit the description: a rewritten description replaces what the paused session already
holds, which is not an addition to it. If the author is not trusted, a context approver must set the
context-approval field to another value and back to Approved — both moves — before the reply may
reach the agent. Then start the named item yourself or explicitly queue it for a continuous worker.
Without adding information, a trusted author may instead react 🚀 on the Wrighty status comment. Reacting 🎉 there asks the retained agent to verify the work and finish through Wrighty's ordinary checks. Reactions placed on a user's reply are inert.
On Local Markdown there is no discussion, so editing the description is the only way to clarify an item. That supersedes what the session holds, and an unattended worker refuses to resume across such a change. Naming the item is what carries the operator's judgement, so the clarification and the run are two steps:
wrighty edit <item> --takeover --yes --body-file requirements.mdwrighty worker --item <item> --yesThe run proceeds despite the change and reports that it did. Combining the two — editing the description and queueing it for a continuous worker — asks for a resume that is certain to be refused.
Every terminal run stores its report on the item's durable session record on both backends. This is
independent of GitHub comment visibility and the legacy worker.sessionReportMode setting.
From the CLI. wrighty get <item> shows it under Last run, after the observed outcome:
wrighty get local:7
wrighty get github:owner/repo#42 --jsonThe JSON form carries it at result.session.lastRun.agentReport. In both forms the final message is
printed with the report block removed, because the same account is already rendered beside it as
fields.
From the web console. wrighty web shows it in the item's last-run block. Local Markdown
only — the board does not serve GitHub items.
From GitHub. The single current Wrighty status comment includes the latest report. Its hidden identity marker lets Wrighty prove that reactions belong to the current waiting run without showing a second comment. A trusted reply alone resumes with that reply as context; no reaction is also needed. A trusted 🚀 on the Wrighty comment resumes without adding information. A trusted 🎉 on the Wrighty comment asks the agent to verify the work and finish through Wrighty's ordinary checks. A reaction on the user's reply does nothing. The comment names the accepted trigger, actor, and content-free consumption key after the resulting run.
Only the most recent run's report is kept locally: the session record holds one and replaces it on the next run. The GitHub comment likewise presents the current state rather than a per-run history.
Wherever it appears, the report is the agent's own account and is labelled as such. The outcome beside it is what Wrighty observed, and nothing an agent reports can change it — including a verification line, which is a claim about a check rather than evidence one ran.
Hidden comments stop a launch
Hiding a comment on GitHub is the one gesture the interface offers for "this should not count", and it is the one Wrighty cannot act on. GitHub advances no timestamp when a comment is minimized and raises no timeline event, so there is nothing to place the hide against an approval.
Both readings would be wrong:
- Honouring it would let anyone who can hide a comment remove approved content from a later prompt, with no signal Wrighty could detect afterwards.
- Ignoring it ships the comment anyway — including the case that makes this matter, where a maintainer hides a drive-by injection as spam and then approves the item.
So a hidden comment refuses the launch with CONTEXT_COMMENT_HIDDEN, naming the comment, and the
remedy is yours, and the two are not interchangeable: delete it if it should not exist, or
unhide it and let the approval decide it like any other comment. Unhiding spam that the current
approval already covers will include it — deleting is the answer there. A configured
github.contextApprovers member can instead make an explicit, timestamped decision: +1 includes
that comment revision and -1 excludes it. Editing the comment afterwards invalidates that
decision.
wrighty context <item> reports what a launch would be given, or the reason there is nothing to
give. It is read-only: it never claims, launches, or mutates, and it does not print the approved text
— the digest, the approval source and instants, the decision counts, and the limits in force.
wrighty context github:owner/repo#42
wrighty context local:7 --json
wrighty context github:owner/repo#42 --prompt--prompt prints the prompt a fresh launch would give an agent, in full — the trust boundary, the
approved title, description and discussion, and the finishing rules. It is the one place the
approved content is printed, because an operator asking to read what an agent will be told is a
different act from the routine summary that lands in terminals and logs. A refused context prints
the ordinary summary instead: there is no prompt to show for a run that would not start.
The approval source distinguishes where the approval came from: project-field for a GitHub Project
field a maintainer set, and backend-local for a store that approves its own content. A Local
Markdown store is machine-local and edited directly by its operator, so an item's own title and body
are the approved content and there is no separate gesture; its discussion is always empty, because
a store with no comments has none to approve. That also means editing a local item is the only way to
clarify one, which is why an operator-requested resume is allowed to carry such an edit.
When a stage refuses, the worker restores the source status (fresh launches only — a refused resume
leaves an already-active item alone), removes any workspace this launch created, releases the
claim, and emits a skipped-policy event naming the stage, the check, and its code. A retained
workspace the launch did not create is never a cleanup target, and a dirty worktree is never
force-removed.
What the item comes out as depends on what the launch was. A refused fresh launch goes back to the claimable pool unchanged, so resolving the refusal is the only thing an operator has to do. A refused re-entry of a recorded session — a queued resume, a scheduled retry — is marked needs-attention instead: something put that run in motion, the refusal is unresolved and needs a person, and leaving the item queued would refuse again on every poll while dropping it to idle would hide both the refusal and the actions for acting on it.
Human worker output uses semantic color on event prefixes when --color auto (the default) detects
that the individual output stream is an interactive, ANSI-capable terminal. Standard output and
standard error are detected independently. Redirected output and writers without declared terminal
capability remain plain text.
Use --color never for durable human-readable logs, or --color always when an explicit consumer
such as less -R should receive ANSI sequences:
wrighty worker --yes --color never >worker.log 2>&1 &
wrighty worker --yes --color always | less -RIn automatic mode, the presence of NO_COLOR or TERM=dumb disables color. Explicit
--color always or --color never overrides those automatic checks. --color always
deliberately writes ANSI sequences even when human output is redirected.
--json always wins over color selection: every standard-output line remains unstyled JSON under
--color auto, always, and never. A background NDJSON worker can be started safely with:
wrighty worker --yes --json >>worker.ndjson 2>>worker-errors.log &Color changes only the trusted event or warning prefix and resets immediately. Event names and
all existing text remain present, while paths, arguments, messages, session IDs, and operator
commands are never wrapped in styling. Color selection does not affect confirmation or --yes.
Worker dispatch state is separate from workflow status and eligibility. Wrighty manages
wrighty.dispatch.state locally and wrighty:dispatch-state=<state> on GitHub; operators should use
the CLI or web controls rather than edit it directly:
| State | Meaning | Continuous-worker behavior |
|---|---|---|
| absent | Ordinary item | Eligible from the configured pick-from status (Worker queue by default) when automatic execution is allowed. |
needs-attention |
A vendor session stopped for clarification or another operator decision | Shown prominently, but never retried automatically. |
queued |
Clarification is saved and the recorded session is ready to continue | Resumed before fresh work from the configured pick-from status. |
retry-scheduled |
The recorded vendor session is parked until a bounded retry time | Ignored before notBefore; when due, reacquired under a new claim generation and resumed before fresh work from the configured pick-from status. |
handoff-queued |
The work is waiting to continue under a different agent in the same retained workspace | Ignored before notBefore; when due, reacquired and launched as a new session under the target agent rather than resuming the source session. |
Wrighty policy - execution remains the durable permission for unattended execution. Queuing is a deliberate one-time dispatch decision; it does not require toggling automation off and back on.
A fresh launch can carry an explicit model and reasoning effort, chosen through a stable profile
name rather than a vendor model identifier. With nothing configured the built-in economy,
balanced and deep tiers set effort only — low, medium, high — and leave the model to each
vendor CLI's own configuration.
Precedence is wrighty worker --profile, then the item's profile, then
worker.defaultExecutionProfile. Resolution fails closed: an unavailable profile is
AGENT_PROFILE_UNAVAILABLE, never a quiet fall back to a different tier in either direction.
Only fresh launches carry a selection. A resumed session keeps the model and effort it started with,
so --profile with --resume is refused rather than ignored, and a same-agent retry reuses the
recorded selection. A cross-agent handoff is a new session and resolves the profile again.
Effort support is a property of the model, not the vendor, and cannot be checked in advance. When a
vendor reports that its model accepts no reasoning effort, the worker emits effort-unsupported,
relaunches once without the effort argument, and records the run as having none. Only that specific
refusal is retried; unrecognized levels, entitlement failures, and exhausted quotas still fail.
Full detail, including per-vendor effort levels and how to pin a model: Execution profiles.
Usage recovery and agent handoff is the authority for how
Wrighty classifies provider failures and recovers from them. In short: adapters distinguish
retryable usage exhaustion and rate limiting from every other failure kind; a retryable failure
preserves the vendor session and workspace, writes retry-scheduled, releases the claim, and
schedules the retry (provider reset > Retry-After > exponential fallback, with grace and
deterministic jitter); and the first capacity failure also opens a per-installation provider
circuit that automatic selection respects until a single leased probe closes it. This section
covers the worker mechanics around that behavior.
Continuous workers skip future retries and providers behind an open circuit.
wrighty worker --item ID --yes is the explicit timer/circuit override when an operator
intentionally wants to process that item now. To test provider capacity without claiming or
changing an item, run:
wrighty provider probe copilot
wrighty provider probe copilot --yes --jsonThe probe's semantics — the lease that stops concurrent probes, the confirmation rules, and how
each result kind affects the circuit — are in
the provider circuit.
When Settings → Repository → Advanced/testing → Capacity probe result is configured for the
agent, web probes, this CLI command, and worker capacity gates use the repository-scoped simulated
result instead. The simulated path starts no vendor process and does not update or clear the
installation-wide real-capacity cache; available can therefore override a real open circuit only
for that repository, while usage-exhausted and rate-limited keep its automatic work blocked
until the simulation is removed or changed.
provider-probe-started, provider-available, and provider-unavailable worker events explain the
result, while candidate diagnostics explain automatic circuit filtering. wrighty list shows the compact retry time,
wrighty get shows the sanitized reason, local and UTC timestamps, attempt count, and installation
ownership, and wrighty status groups scheduled retries and open provider circuits. The web
console shows the same categorical retry badge and detail callout, plus the consolidated
Agents header control immediately before the connection indicator. Its summary reports
available enabled agents, active probes, and states needing attention; an anchored popover uses
one compact row per detected agent for current status and actions. Its Probe action checks every
enabled detected agent concurrently, with one bounded vendor request per agent unless a
repository simulation supplies the result. Otherwise-ready cards assigned to an
unavailable provider say that the provider is unavailable instead of claiming they are immediately
runnable; their item panel explains that automatic workers will leave them unclaimed and shows the
explicit item-run override. Provider opening, probe leasing, and closure participate in the board
and header-fragment refresh revisions, so both update without an item-file change. The popover can
probe any configured agent even when no circuit is open; affected-item actions offer the same check
in context. Another installation can see the portable
retry-scheduled state but cannot invent the machine-local timer or resume address; it reports that
details are unavailable. Provider capacity is keyed by installation and normalized agent;
an account scope is intentionally omitted until a supported CLI exposes a stable non-secret key.
On GitHub, the wrighty:dispatch-state=retry-scheduled issue label is the authoritative categorical
state. Projects initialized with the current schema also receive display-only
Wrighty dispatch - state, Wrighty dispatch - not before, Wrighty dispatch - agent, and
Wrighty dispatch - detail fields. Projection failure does not affect the label or
installation-local schedule. Provider capacity is not GitHub authority and is not copied into
Project fields.
In the web console, claiming a scheduled item for editing does not silently cancel its
timer. Ordinary Save, Release without saving, and Save and release actions preserve the
scheduled retry while allowing instructions to be clarified. The agent-policy selector is
locked because the retry belongs to the recorded vendor session; changing vendors requires an
explicit cross-agent handoff rather than changing wrighty.policy.agent. Turning off execution policy
or moving the item out of the active worker status cancels the schedule. Save and resume
automatically deliberately overrides the timer and queues the recorded session now, while
handback, finish, and archive actions also clear the obsolete deferred-dispatch record.
worker.usageFailure configures the recovery action, retry timing, attempt caps, and cross-agent
handoff; the full schema, defaults, and semantics are in
Configuration. A handoff is a new session
by the target agent in the same retained workspace, seeded with a bounded, redacted handoff
packet; the old vendor session is not resumed or converted and stays independently reviewable on
the recording host, and the item's agent policy field is updated to the target (see
cross-agent handoff). Total automatic
recovery is bounded: retries consume maxAttempts, and handoffs may add at most the configured
fallback count on top before the item moves to needs-attention.
To demonstrate these paths against an installed Wrighty without changing an agent or provider, open Web console → Settings → Repository → Advanced/testing. Per-agent availability can be changed to Pretend not installed; a capacity-probe result exercises the same repository-local web, CLI, and worker capacity decision; and an implementation result enters the normal item recovery policy, including dispatch persistence, GitHub presentation, retry timing, and cross-agent handoff. Neither kind of synthetic usage failure opens the installation-wide provider-capacity circuit. Turn the source agent's simulation off before a same-agent retry that should succeed; leave the target agent off for a handoff demonstration. Synthetic implementation results leave capacity probes, requirements-readiness turns, and diagnostic checks real; capacity-probe simulation is a separate setting, and availability simulation deliberately affects installation-dependent checks. Repository simulations stay enabled until explicitly turned off.
The Local Markdown web editor exposes these managed values as Allow automatic execution and Agent policy. If no item can be claimed, the worker reports how many active items it considered in the source status, how many are manual-only or lack an item-level agent policy, how many have an unapproved projected context, how many filters excluded, how many cannot resolve a supported agent, and how many otherwise eligible items were unavailable because of an active claim or claim contention.
Preassigned Claude and Copilot handles are stable for one claim generation but change when an item is acquired again; deliberate continuation uses the session ID recorded on the active claim.
Workspace handling is a worker setting, not a work-item field. Resolution is the explicit
--workspace-mode option, then worker.workspaceMode in .wrighty.json, then current:
| Mode | Directory | Concurrency behavior |
|---|---|---|
current (default) |
Current repository checkout | Takes an exclusive Wrighty worker lock. A second worker targeting the same canonical directory gets WORKSPACE_BUSY before it claims an item or starts an agent. |
shared |
Current repository checkout | Explicitly disables the worker lock. Multiple workers may run there concurrently. Wrighty warns because it cannot detect or resolve file, staging, build, or commit conflicts. |
worktree |
Fresh directory under the configured worker.worktreeRoot (default <repo>.worktrees beside the repository) |
Gives each item an isolated branch and checkout. Recommended for unattended or concurrent workers. |
shared is an unsafe opt-out for an operator who accepts responsibility for coordinating the
items. Agents may not recognize that a changed or staged file belongs to another concurrent agent.
Select it explicitly for one invocation, or deliberately make it the repository default:
wrighty worker --workspace-mode shared --yes{
"worker": {
"workspaceMode": "shared"
}
}Every live run resolved to shared prints the additional collision warning, including runs using
the configured default.
Wrighty creates a git branch only in worktree mode: each processed item gets a fresh
worktree and a dedicated branch, both created with
git worktree add -b wrighty-worker/<item>-<unique> <path> HEAD. In current and shared
modes the agent works directly on whatever branch is checked out and Wrighty creates nothing.
The branch name is recorded in the machine-local session record: wrighty get <id> shows it,
the finished output prints it, and it survives claim release and expiry.
Three worker settings control where worktrees live and how they are named:
| Setting | Default | Placeholders |
|---|---|---|
worker.worktreeRoot |
{repoParent}/{repo}.worktrees |
{repo}, {repoParent}, {home}, {repoPathHash} |
worker.branchFormat |
wrighty-worker/{id}-{title} |
{id}, {number}, {title}, {unique}, {agent}, {date} |
worker.worktreeNameFormat |
{id}-{title} |
same as branchFormat |
{id} is the full item slug (local-22, github-owner-repo-42); {number} is the bare item
number (22, 42); {title} is a slug of the item title truncated to 30 characters;
{unique} is an 8-character per-acquisition fragment; {repoPathHash} disambiguates
same-named repositories under a shared root such as {home}/.wrighty/worktrees. A CI-friendly
convention like branchFormat: "feature/{number}-{title}" makes the push-PR completion path
rename-free.
Every expansion is sanitized to a valid git ref or directory name and capped in length.
Uniqueness is guaranteed regardless of format: when the format omits {unique} and the branch
or path already exists — retained worktrees from earlier runs are a normal state — Wrighty
appends the unique fragment instead of failing. Keeping worktrees inside the repository
({repo}/...) is discouraged: nested worktrees are picked up by IDE indexers and build globs,
and git clean -xdf in the main checkout can destroy active agent work.
The branch exists from spawn time, but it only contains the work once something is committed inside the worktree. Until the first commit, the branch still points at the spawn-time base commit and the worktree's working directory holds the only copy of the changes.
worker.completion.commit decides who commits, and the worker prompt instructs the agent
explicitly in both directions so the outcome never depends on vendor-agent habit:
| Value | Behavior |
|---|---|
inspect (default) |
The agent is told to leave every change uncommitted. The worktree is always retained as your review queue, and the finished output says so. Until you commit, the working directory is the only copy of the work. |
agent |
The agent is told to commit its work in logical commits referencing the item. A clean worktree is then removed on finish while the branch keeps the work; pass --keep-workspace to retain it anyway. |
In current and shared modes the commit instruction is never added: Wrighty does not direct
commits on the operator's own checkout.
agent mode depends on the vendor agent's environment actually permitting an unattended commit.
Wrighty's prompt asks for the commit, but it deliberately cannot override the agent's own
governance — a global "do not commit unless I ask" instruction, a restrictive permission mode, or
a sandbox that blocks git commit will all veto it. When that happens the agent leaves the change
uncommitted, git's dirty-tree guard retains the worktree, and the item safely lands in
needs-attention rather than being reported done. This is the intended fallback, not a failure:
the work is never lost, and you can commit it yourself or rerun with commits permitted. If you
routinely disallow unattended commits, prefer the default inspect policy.
Wrighty deliberately never merges, pushes, or opens PRs. worker.completion.integration
(none default, merge-local, or push-pr) selects which guidance the finished output and the
agent skill render; execution stays with you. Because main is checked out in your primary
working copy, git will not let the worktree commit onto it directly — the flow is always
commit on the worker branch, then integrate from the main checkout:
# inspect policy: commit first, inside the worktree
cd ../myrepo.worktrees/local-22-validate-user-names && git add -A && git commit
# merge-local, from the main checkout (remove the worktree before deleting its branch)
git merge --ff-only wrighty-worker/local-22-validate-user-names
git worktree remove ../myrepo.worktrees/local-22-validate-user-names
git branch -d wrighty-worker/local-22-validate-user-names
# or push-pr, from any checkout
git push -u origin wrighty-worker/local-22-validate-user-namesArchive the item as the last step, from the web console or with wrighty archive while
holding a claim; archive.onStatuses automates this at finish for fire-and-forget setups.
Retained worktrees and worker branches accumulate by design: inspect-first runs, failed runs, and merged-but-unremoved workspaces are all normal states. Two commands surface and clear them:
wrighty workspaces # list retained worktrees: dirty/clean, merged/unmerged, item
wrighty workspaces cleanup <id> # remove the item's worktree and delete its merged branch
wrighty workspaces cleanup <id> --force # discard uncommitted changes and unmerged commits tooThe two status tokens are orthogonal — they measure different things:
dirty/cleandescribes the working tree (git status): are there uncommitted changes in the worktree?merged/unmergeddescribes the commit graph (git merge-base --is-ancestor <branch> HEAD): are the branch's own commits already contained in the main checkout's HEAD? A branch with no commits of its own is trivially "merged".
Because they are independent, each workflow leaves a characteristic signature, and the completion flow moves the worktree through them:
| After… | State | Why |
|---|---|---|
an inspect run |
[dirty, merged] |
the agent left the work uncommitted (dirty), so the branch still points at the spawn-time base commit and has nothing beyond HEAD (merged). This is the normal resting state, not a contradiction. |
| committing in the worktree | [clean, unmerged] |
the work is now committed on the branch (clean tree) but not yet in main (unmerged). |
merge-local / integrating and removing |
(drops off the list) | the branch is merged into the main checkout and the worktree removed. |
Cleanup delegates every safety decision to git: a dirty worktree is refused
(WORKSPACE_NOT_CLEAN) and an unmerged branch is refused (WORKSPACE_BRANCH_UNMERGED); by default
Wrighty never forces either. This is why an inspect worktree ([dirty, merged]) is refused on
the worktree-remove step — the uncommitted work is protected — while its branch would delete
cleanly if the tree were clean.
--force overrides those two git refusals — git worktree remove --force and git branch -D —
discarding uncommitted changes and unmerged commits. Use it only when you know the leftover
files are disposable (for example, tool artifacts such as .memsearch/); for anything recurring,
prefer .gitignore, since ignored files never block a normal cleanup. --force deliberately does
not override an active claim: an item whose claim is still held always reports CLAIM_HELD,
because forcing there could pull a workspace out from under a live worker or editor. Both commands
support --json.
wrighty get <id> and the web item viewer show the same working-tree and branch state for the
one item, calculated on demand from git on the machine that holds the worktree. When the recorded
worktree is not present on the current host (or git cannot be read), the state is reported as
unavailable rather than guessed — the recorded branch and path are still shown.
After an item is genuinely finished, Wrighty prints a review: command that opens the completed
vendor session interactively when its workspace still exists, plus a suggested completion prompt
that asks the agent to walk the diff, propose a commit, integrate, clean up, and archive with
your approval. The finished: line uses the agent's short structured report summary when present;
the fuller closing explanation and report fields remain available in the saved run and session.
Older agents without a usable summary retain their closing prose on that line. The review command
invokes the vendor directly, carries no Wrighty claimant ID or token, and does not reacquire the
completed item. It is always available in current and
shared modes while the checkout exists; under the inspect commit policy the worktree is
retained too. With commit: agent, use --keep-workspace to retain a clean successful worktree
for later review:
wrighty worker --once --workspace-mode worktree --keep-workspace
# finished: ...
# branch: wrighty-worker/local-22-validate-user-names
# review: cd '...' && claude --resume '...'Wrighty passes the absolute original tracker configuration path to the child agent as
WRIGHTY_CONFIG_PATH. Consequently, Local Markdown get, mutation, renewal, and finish
commands operate on the authoritative original store rather than a stale copy checked out in
the agent worktree.
Renewal occurs at lease half-life and has a fixed spawn-time budget equal to --item-timeout. It
can never renew past that deadline, so the maximum hold after a hung run is
--item-timeout + leaseMinutes. On CLAIM_STALE or CLAIM_EXPIRED, the default
--on-fenced kill stops the process tree. detach is available for deliberate operator use, but a
detached process can keep editing files and is unsafe in a shared checkout.
While a vendor process is running, the worker emits a single-line operational heartbeat every five minutes. It reports elapsed time, the current claim-expiry time, remaining fixed timeout budget, and workspace mode:
2026-07-19T14:20:00.0000000+00:00 running: local:22 [claude] — 20m elapsed; claim valid until 2026-07-19T15:00:00.0000000+00:00; timeout in 40m; workspace worktree
This is intentionally process-level visibility rather than an agent transcript. Wrighty does not
stream model responses, tool calls, or reasoning, and the optional web console does not become an
agent frontend. In another terminal, use wrighty get <id> to inspect the durable claim, session,
workspace, and lease state. When the worker runs under a service or with redirected output, ordinary
process logs retain the same heartbeat and lifecycle lines.
Vendor process success is not item completion. An item is finished only when the agent calls
wrighty finish and the configured completion state is observed. If a successful agent turn exits
while its exact claim remains active, the worker emits needs-attention, leaves the item
In Progress, sets its dispatch state to needs-attention, stops renewing, and retains the
session/workspace claim until its finite lease expires. A continuous worker does not retry that
state automatically. --once returns exit code 10 for this outcome.
The needs-attention footer is organized by what the operator wants to do. In wrighty web, choose
Queue for worker directly when fixing an external permission or configuration problem requires
no work-item edit. Wrighty ends the retained same-installation claim and marks the recorded session
queued, including after that claim expires. When the requirements need clarification, choose Take
over for editing while its claim is active or Claim for editing after expiry, edit the title or
body, then choose Save and resume automatically. To continue the session yourself instead,
open More actions… and choose Save and show manual resume command. Choose
Finish when the tracked work is already complete. To close the item without further agent work,
save it and choose
Archive from the item view. The web claim path preserves a complete local recorded session
across expiry.
The CLI equivalent is atomic and does not require copying claim environment variables:
wrighty edit <id> --takeover --yes --body-file requirements.md --requeue--requeue requires a complete recorded agent/session/workspace address. It clears active human
ownership, rotates the terminal fencing generation, and marks the session queued. A normal
continuous wrighty worker scans queued In Progress items before fresh candidates from the
configured pick-from status (Worker queue by default) and resumes the recorded vendor session.
wrighty requeue <id> is available when the caller already holds and supplies the exact claim
handle.
After saving the clarification, continue headlessly with:
wrighty worker --item <id> --yesThat command works both while the current claim is active and after it expires. Wrighty infers
whether to take over the active local session, recover an expired session under a new claim
generation, or start a new session when no recorded address exists. Claim expiry invalidates
authorization, not the vendor's durable session; an expired token is never revived or reused.
Automatic recovery is limited to the installation that created the session, where its recorded
workspace and vendor state are meaningful. Another installation must use --fresh explicitly
after expiry.
For CLI editing while the current claim is still active, use either the interactive editor or direct edit options:
wrighty edit <id> --takeover
wrighty edit <id> --takeover --yes --title "Clear title" --body-file requirements.mdThe first command prompts before displacing an active claimant; the scripted example uses --yes.
Both also work after expiry, acquiring a new human editing claim without a takeover prompt while
preserving a recoverable local session. They apply the edit with the resulting handle inside one
Wrighty process, retain human ownership, and print the headless continuation command. No environment
variables need to be copied.
--item <id> processes exactly that item and chooses from claim state: an active same-installation
session is taken over and resumed; an expired session is reacquired under a new claim and resumed;
an item with no recorded session starts new. It never takes over another installation's active
claim, and it refuses to silently discard an incomplete or missing-workspace session address.
Use Boolean intent assertions when inference is not desired:
wrighty worker --item <id> --resume # require a recoverable existing session
wrighty worker --item <id> --fresh # require an unclaimed item and start a new session
wrighty worker --item <id> --handoff # hand the recorded work to a fallback agent
wrighty worker --item <id> --handoff --agent codex # hand it to a named agent--handoff is the operator's "switch target agent" action: it requires a complete recorded
session on this installation, and starts a new session by a different agent in the same
retained workspace, launched with the bounded handoff packet as supplementary context — exactly
what the automatic usage-failure handoff does, but on demand. An ended session's retained claim
(the lease a needs-attention ending keeps so the session stays resumable) is superseded: the
worker takes it over, fences the ended claimant, and proceeds; only another installation's claim
refuses the handoff. The explicit
command is the consent, so it needs no allowCrossAgentHandoff opt-in; without --agent the
first supported, installed, circuit-closed entry in usageFailure.fallbacks for the recorded
agent is chosen. The recorded session is not resumed or converted and stays reviewable; like
every handoff, the item's agent policy field is updated to the target agent so the board names
the agent now responsible (see
cross-agent handoff).
--resume, --fresh, and --handoff are mutually exclusive and fail when current state does not
match the requested intent. Fresh starts still require normal execution policy and accept the
configured source or active status. Add --dry-run to print the inferred or asserted action
without claiming, taking over, or spawning.
The item-level agent policy field (Wrighty policy - agent on the GitHub Project, Agent in
the web console's editor) is also the board-native handover control: setting it to a different
vendor than the item's recorded session directs the next worker scan to hand the work off there,
and every handoff writes the field back to the target. The full semantics — what directs a
handover, what every handoff writes, and how worker.defaultAgent differs from a direction — are
in cross-agent handoff.
For takeover, run:
wrighty takeover <id> --yes --print-resume-commandThis rotates the fencing token and preserves the recorded vendor session/workspace address. With
--print-resume-command, an agent takeover prints both interactive and headless-worker alternatives;
a human takeover prints the safe headless-worker continuation. The separate
wrighty resume-command <id> prints only the recorded interactive vendor address without rotating
the claim; it reads the durable session record, so it also works after the item is finished or the
claim released — which is how you reopen a completed session for guided completion. It prints a
command you run in your shell; add --exec to launch the structured vendor invocation directly in
the current terminal instead of copying and re-running the printed command. The executable,
arguments, working directory, and environment are passed independently rather than through
$SHELL -c. Once the session is open, paste the
guided-completion prompt Wrighty prints (a separate copy block) to have the agent summarize the
diff, commit, integrate, clean up, and archive with your approval at each step. Takeover is
limited to the same Wrighty installation. A worker elsewhere cannot be
seized on demand; wait at most
--item-timeout + leaseMinutes for expiry or coordinate with that installation.
The web UI provides the equivalent flow: Take over for editing, clarify, then choose Save and
resume automatically for continuation by a continuous worker. To continue it yourself, open
More actions… and choose Save and show manual Agent resume command. The manual action
rotates the claim to a fresh agent claimant and displays the environment-prefixed interactive
command plus the headless alternative. On macOS or native Windows, that agent-owned view can also
Open Agent CLI in a new Apple Terminal or Windows Terminal window. Plain Save keeps human
ownership, displays only the headless command, and—when the vendor has a qualified deep link and
its app is installed—can Open Agent Desktop under the retained human claim. Desktop does not
receive Wrighty's fencing environment: stop or idle it before handing the session back. For an
interactive continuation, enter the adjacent vendor-specific follow-up prompt to explicitly load
the Wrighty skill, re-read the clarified item, and continue.
Release ends ownership without discarding recovery state: the recorded session/workspace address
is a durable machine-local record that survives release and expiry, so a released item can still
be resumed later with wrighty worker --item <id> on the installation that recorded the session.
When a run ends — finished, needs-attention, failed, timed-out, rejected, or
interrupted — Wrighty records the outcome (succeeded / failed / rejected /
interruptedByOperator / interruptedByHostShutdown), the agent's final message or block
reason (truncated), and the end time onto the durable session record.
The item's outcome and the session's ending condition are separate. An agent can call
wrighty finish and then have its own session end badly — hitting a usage limit immediately
afterwards, for example. Because the tracked work landed, the run reports finished and the
recorded outcome is normally succeeded; when an operator or host interruption raced with the
finish, the item still reports finished while the recorded process outcome remains interrupted.
The ending condition stays attached to the run so it remains visible. Wrighty does not schedule
recovery for such a run: the agent
released its claim when it finished, and the item is not waiting on anything. This is backend-neutral
and overwrite-only: it survives release, expiry, takeover, and archive, exactly like the recorded
session address. It surfaces as a Last run block in wrighty get (human and --json), in the
web item panel above the resume/requeue actions, and in wrighty status. This makes the local
clarify → requeue loop self-contained: read the block reason in the web UI or wrighty get, edit
the description, and requeue — without opening the vendor session first.
The captured outcome also distinguishes a completed item from a paused one. An unclaimed
item whose recorded session succeeded and whose status reached the configured finish state
(defaultFinishTo) reports operational status completed — the work landed; its primary next action
is finalize/archive, not resume. An item whose session is merely retained for later resumption
reports paused-session. Both keep the durable resume address; only the presentation differs, and
a completed item can still be reopened deliberately if its worktree is present. (The
resumability half is separate: wrighty get's resumableHere is false and
wrighty resume-command refuses with RESUME_WORKTREE_ABSENT once the recorded worktree directory
is gone.)
wrighty status is the machine-side "what needs me?" surface and the CLI counterpart to the web
console. It groups active items by the operator's next action:
wrighty status # human-readable, grouped
wrighty status --json # same groups for scripting- Needs attention — blocked items, each with the last-run outcome and final-message excerpt and the clarify → requeue / continue commands.
- Completed — retained worktree — finished items whose worktree is still present, each with the
branch, its
dirty/mergedgit state, and the integration commands for the configured policy. - Paused — resumable session — retained sessions waiting to be resumed, with the resume command.
- Active — items with a live claim. A worker-run claim reports Worker preparing while the workspace and launch are being prepared, then the selected agent as working after its operating-system process starts; human editing and automation claims retain their own labels.
- Resume queued — items marked to be resumed by a continuous worker.
- Retry scheduled — retained sessions waiting for their bounded retry time.
- Handoff queued — retained workspaces waiting for a due cross-agent continuation by the recorded target agent.
- Provider unavailable — installation-local provider circuits, including whether automatic
work is paused or another worker owns the single due capacity probe. Use
wrighty provider probe AGENTto test it immediately without selecting a work item. - Local worker processes — one installation-local heartbeat record per worker invocation, including CLI-process/web-hosted origin, PID, verified/stale/unknown liveness, lifecycle state, current item title and agent, cooperative-control version, startup configuration revision, and a sanitized invocation summary. The web console orders Running, then Unknown, then Stale workers, then uses immutable start time and run ID within each group so heartbeat updates never make cards trade places. Stale rows are visually de-emphasized.
Stopping a worker with Ctrl-C (or SIGTERM) interrupts an active agent, runs the bounded item
finalizer, removes the instance record, and exits with the conventional interrupted code. A second
Ctrl-C stops waiting and forces the exit. The web console's normal Stop after current item action
is different: it closes intake but deliberately does not cancel the current agent or its bookkeeping.
A worker that was killed
outright — or crashed — leaves its record behind on purpose: that is what the stale liveness state
detects, and the record expires a day after its last heartbeat.
An unfinished immediate stop leaves the item's workflow status unchanged, marks its dispatch
needs-attention, retains any recorded session/workspace, suppresses retry and handoff scheduling,
and stops renewing the exact claim. That claim remains only until its current finite lease expires;
an explicit resume, requeue, or takeover can supersede it. A fresh bounded cancellation-independent
finalizer performs those writes. If finalization itself cannot complete, Wrighty retains a
machine-local interruption breadcrumb and never uses it as authority to auto-queue the item.
A worker process, a tracker claim, an agent process, and a retained session are four different facts. An idle continuous worker can be live with no claim. A crashed worker can leave a valid claim until its lease expires. The agent subprocess may exit while its fenced claim and resume address remain deliberately retained. Wrighty therefore never derives local process liveness from a claim.
Live workers heartbeat every 15 seconds in the machine-local cache and become stale after 45
seconds without a heartbeat. Wrighty also compares the PID's process-start identity to prevent PID
reuse from appearing live. Platforms that cannot verify that identity report unknown, not
running. Normal exit removes the record; stale cleanup never releases or changes a tracker claim.
When the recorded process no longer exists, that direct observation is reported even if its last
heartbeat is also old.
When a worker's startup configuration revision differs from the current .wrighty.json, human
and JSON status output reports configuration drift and the need to restart that worker.
The retained-worktree git state is calculated on demand, bounded and timeout-guarded, only for the
items in the first three groups and only on the machine that holds the worktree (it degrades to
"unavailable" off-host) — the same posture as wrighty workspaces. The at-a-glance
[worktree] marker in wrighty list (and the board badge in the web console) flags which items
have a retained worktree without any git call; drill into wrighty get, the web item viewer, or
wrighty workspaces for the per-item dirty/merged detail.
For the GitHub backend the "UI" is github.com, so an issue left In Progress with the
wrighty:dispatch-state=needs-attention label tells the operator nothing on its own. When a run ends
in needs-attention, or finishes with a retained worktree, the worker posts one combined,
marker-identified status comment on the issue:
- Current result — the requested input or agent summary, shown once at the top.
- Continuation choices — a trusted reply alone continues with the reply as context; 🚀 and 🎉 are accepted only on this Wrighty comment and respectively resume without new information or ask the retained agent to verify and finish normally.
- Run and session details — collapsed diagnostics: the outcome Wrighty observed, the agent's
explicitly unverified account, trigger, host label, branch, and (only when
shareLocalPathsis enabled) the workspace path. - Other recovery options — collapsed copy-paste commands for manual resume, takeover, and the configured completion route.
Here the status comment asks for missing requirements and explains the trusted reply, resume, and verify-and-finish choices. The operator's reply becomes new continuation context for the retained session; no separate reaction is needed.
For a scheduled retry, the same comment also shows the sanitized installation-local provider circuit state and the authoritative Project worker policy observed for the run. It offers two distinct commands:
wrighty provider probe claude
wrighty worker --item github:owner/repo#42 --yesThe first performs one confirmed, bounded capacity probe without claiming or changing the item; the second explicitly overrides the retry timer/provider circuit for that item while preserving claim fencing and the recorded vendor session. The comment never includes raw provider payloads, account details, or transcript content.
It is a single comment per issue, found by the <!-- wrighty-handover:v1 --> marker. After a
terminal run Wrighty deletes and reposts it so the current result returns to the bottom of the
discussion and produces a fresh notification; if deletion fails, it safely falls back to editing
the existing comment rather than creating a duplicate. It is trimmed to a short "resolved" form
when the item is requeued, archived, or its workspace is cleaned up, so stale instructions do not
linger. Posting is best-effort — a failure to write the comment never fails the run.
Configure the exposure with worker.handoverComment:
| Value | Behavior |
|---|---|
full (default) |
includes the branch and the host label (and the workspace path when shareLocalPaths is enabled) |
minimal |
omits local machine details (host, workspace path); keeps the branch |
off |
posts nothing |
Wrighty defaults to the least-disclosure posture, so on a fresh install neither the workspace path nor the real machine name leaves the machine:
-
Workspace path —
worker.shareLocalPathsdefaults tofalse. The absolute path (which embeds the OS username) is not published on any of the three GitHub surfaces:- the claim marker carries no workspace path (the real path stays only in the machine-local work-item runtime store, which is authoritative for resume on the recording host — resume is unaffected);
- the Project workspace-path field is not written;
- the status comment omits the workspace path from its run details and uses path-free
completion commands (
wrighty resume-command <id> --exec,wrighty workspaces cleanup <id>), which resolve the retained worktree locally on the recording host.
Set
worker.shareLocalPaths: trueonly when every collaborator with repository access is trusted to see local machine paths; then the rawcd '<path>' …/git worktree remove '<path>'commands are published instead. The branch name (e.g.wrighty-worker/local-5-…, no username) is always published, sincegit merge --ff-only <branch>needs it.shareLocalPathshas no effect on the Local Markdown backend, whose paths never leave the machine. -
Host — with no configured label the comment shows the placeholder
anonymous; the real machine name (Environment.MachineName, which often embeds a person's name) is never published by default. To publish a symbolic name that is meaningful to you but reveals nothing, set a user-scoped host label:wrighty config user host set "workstation-alpha" # published instead of 'anonymous' wrighty config user show # show the label and its source file wrighty config user host clear # revert to the 'anonymous' placeholder
The label is stored in a durable, user-scoped settings file (macOS
~/Library/Application Support/wrighty/settings-v2.json, Linux~/.config/wrighty/…, Windows%APPDATA%\wrighty\…; override the directory withWRIGHTY_CONFIG_DIR), not in the per-repo.wrighty.json. It applies to every repository this installation works with. See user settings for the full reference.
Independent of shareLocalPaths, minimal hides the host label and workspace details from the
comment but keeps the branch, and off suppresses the comment entirely.
A recorded vendor session is bound to the host that ran it. From that machine, resume it
(wrighty resume-command <id>, or continue headlessly with wrighty worker --item <id> --yes).
From any other machine, the recorded workspace and vendor session are not meaningful, so
coordinate the release of the active claim (or wait for it to expire), then start a fresh
session instead:
wrighty worker --item <id> --fresh --yesThe status comment states the bound host label explicitly (and the paths when shareLocalPaths
is enabled), turning the common "which machine?" confusion into an explicit choice.
Do not hand-edit the
wrighty:dispatch-statelabel on GitHub. Flippingneeds-attention→queuedin the GitHub UI bypasses the claim protocol (no claim event, no token rotation). Always requeue withwrighty requeue <id>(orwrighty edit … --requeue).
Verified through 2026-08-25 with Claude Code 2.1.219, codex-cli 0.145.0, GitHub Copilot CLI 1.0.75, and OpenCode 1.18.23:
| Capability | Claude | Codex | Copilot | OpenCode |
|---|---|---|---|---|
| Headless start | -p |
exec |
-p |
run --format json --auto |
| Machine output | JSON | JSONL (--json) |
JSONL | JSONL (--format json) |
| Session handle | preassigned UUID | parsed from thread.started |
preassigned name | parsed from sessionID |
| Headless resume | -p --resume |
exec resume |
-p --resume= |
run --session |
| Working directory | process cwd | -C |
-C |
--dir |
| Autonomy | permission mode plus tool allow-list | sandbox mode | tool-approval flag | --auto plus inline permission rules |
| Workspace confinement | not available headlessly | workspace-write |
default path verification | native tools only; shell is unconfined |
Per-profile flags are in Spawned-agent permissions.
Handoff context comes from each vendor's own local session surface. Verified on 2026-08-06 with codex→claude and claude→codex handoffs on Local Markdown and copilot→codex through automatic fallback selection; store layouts re-confirmed 2026-08-08 against Claude Code 2.1.222, codex-cli 0.145.0, and GitHub Copilot CLI 1.0.78. OpenCode export was verified on 2026-08-25 with OpenCode 1.18.23.
| Capability | Claude | Codex | Copilot | OpenCode |
|---|---|---|---|---|
| Export surface | local transcript store | local rollout store | --share Markdown export |
opencode export <sessionId> JSON |
| Location | ~/.claude/projects/**/<sessionId>.jsonl |
~/.codex/sessions/**/rollout-*-<sessionId>.jsonl |
copilot-shares-v1/ in Wrighty's cache root |
OpenCode's local data store, accessed through its CLI |
| Written by | the vendor, for every session | the vendor, for every session | only when Wrighty requests it at launch | the vendor, for retained sessions |
| Available as handoff source | yes | yes | worker-owned sessions only | yes |
| Available as handoff target | yes | yes | yes | yes |
| Retrospective export | yes | yes | no | yes |
The asymmetry in the last three rows is the operationally important part. Claude and codex both
write their session to disk unconditionally, so any recorded session on this host can be exported
after the fact. Copilot has no equivalent store: Wrighty requests --share at launch of every
worker-owned run, so an export exists only for sessions Wrighty started after that behavior
shipped, and only when the session ended normally. A copilot session started outside Wrighty, or
killed mid-run, has no transcript to hand over.
Two vendor quirks are handled in the exporters rather than left to the reader: codex injects Wrighty's launch scaffolding as ordinary user messages (filtered out so the packet carries real conversation), and copilot names its export by its own session UUID rather than the handle Wrighty requested (resolved by matching the export's metadata note).
OpenCode's --sanitize export removes conversation text that Wrighty needs for a useful handoff.
Wrighty instead reads the bounded raw local export, selects only user/assistant text parts, and
then applies the same handoff packet redaction and size limits used for every vendor.
Export failure never blocks a handoff. Every exporter returns "not available" with a reason instead of throwing, and the handoff proceeds with a workspace-only packet: the target agent gets the work item and the retained workspace, but no conversation history. This is the documented fallback, not an error path. It applies when the vendor store is absent, the recorded session ID is unparseable, the file exceeds the export size limit, the file cannot be read, or — for copilot — no share export was ever written. An agent with no known export surface at all falls back the same way, so adding a vendor never requires a new failure mode.
The reason is written into the handoff packet itself, under Source session excerpts, so the target agent is told why it has no history instead of silently assuming there was none.
These CLI surfaces are version-sensitive. Validate vendor upgrades in a throwaway repository before unattended use.
