A plan is a markdown Artifact edited live by a person in the browser and by
your local seat: the archdev plan collab start process, which joins the
Artifact channel as an agent actor and relays signals to you. The plan lives
only in the Artifact; nothing is written to disk. You never touch the channel directly. You drive the
seat with short-lived commands that find it through a handle file.
browser <--OT ops + signals--> Artifact channel <--> seat (archdev plan collab start)
^ loopback + handle file
you: signals / read / suggest / complete / apply / run
archdev plan collab --help and each verb's --help list the commands; the
CLI is the authority when it disagrees with this file. Every verb prints one
JSON object per line. Everything needs a signed-in user session.
Activity and risk ownership
Follow the archdev skill for activity reporting. Plan/task lifecycle events
retain their risk.plan / risk.task assessments. Server automation owns hosted
PR and code-region grading: report PR events without fetching, waiting for,
reusing, or attaching a server grade. No local assessment, annotation or stop
hold is required.
1. Start the seat
archdev plan collab start --file plan.md [--handle plan.collab.json] [--artifact <id>] [--agent <id>] [--model @alias]
startcreates a plan Artifact seeded once from--file(or reuses--artifact). The seat never reads or writes that file again; it is a starting draft, not a copy of the plan. Read the plan withread.startprints astartedevent with the pageurl, then keeps running. Run it in the background and read its stdout; stop it with Ctrl-C.- The handle defaults to
<file>.collab.json, beside the seed file. It holds a loopback port and a capability token. Never print, commit or share it. The seat deletes it on exit;startrefuses to run if the handle already exists. - With
--model @alias(a configured model alias) the seat answers ghost completions itself and you only see the signals. Without it the seat tells the page it has no completer and ghost suggestions are off; anycomplete.requestan older page still sends is yours to answer. - Tell the person the
urlfrom thestartedevent.
Seat stdout events: started, signal (every relayed signal),
completion.required (you must answer a completion) and report.required
(plan approved, see section 4).
2. The loop
archdev plan collab signals --handle plan.collab.json
With no --input, signals waits for the next signal, prints it and
acknowledges it, so a signal is not lost if you restart. Handle one, answer
it, call signals again. Envelope:
{"kind":"ask.request","to":"…","from":"<actor id>","revision":12,"payload":{…}}
Rules that apply to every answer:
- Positions are Unicode code points, not bytes, UTF-16 units or
graphemes.
🌍is one position. - Revisions go stale.
readreturnscontent,revisionand the parsedsuggestions.apply,suggestandcompletefail with a stale-revision error if you pass any revision but the current one: runread, redo the position math against the new text, retry. Never reuse positions from an olderread. - Any edit, from you or the person, cancels pending completions.
- Reply to a requester with
"to":"actor:<from>"using the request'sfrom.
3. Signal kinds
Received signals and what to do:
| Signal (payload) | Do |
|---|---|
complete.request {request, position, revision} |
Ghost text for the cursor. Compose a short continuation, then complete. |
complete.cancel {request} |
Drop that request. The seat already cancelled it. |
ask.request {id, intent, range:[from,to], quote, text} |
Person asked about a selection (intent:"edit") or asked a question (intent:"chat"). Edit: write a suggestion over range with suggest. Both: then agent.reply. |
agent.request {id, text, section?} |
A task for you. Do it (run status, then suggest or apply), then agent.reply. |
run.stop {run} |
The person pressed Stop. Stop writing for that run and send its run.status with state:"stopped". |
plan.tasks.request {request, revision} |
The person asked to break the approved plan into tasks. Reply with plan.tasks.result (section 4). |
run.undo {run} |
Revert that run: apply the inverse of what it wrote (section 5). |
run.reviewed {run} |
The person reviewed the run. Nothing to send. |
plan.approved {revision} |
Plan frozen. Report it (section 4). Stop editing. |
plan.reopened {revision} |
Editing is allowed again. |
Sent by you:
| Signal | Command |
|---|---|
complete.suggestion {request, position, text, alternatives[]} |
complete |
run.status {run, state, steps[], ranges[]} |
run |
agent.reply {request, text} |
signals --input |
plan.tasks.result {request, revision, tasks[]} |
signals --input |
Every ask.request and agent.request ends with an agent.reply whose
request is the payload's id. The page shows the prompt as "Queued" and
keeps the plan from being approved until that reply arrives; a suggestion
alone does not clear it.
Ghost completion (complete.request)
Ghost text is never written into the document; it only travels as a
signal. Read the document, choose text to insert at position, then:
archdev plan collab complete --handle plan.collab.json --input \
'{"to":"<from>","request":<payload.request>,"revision":<payload.revision>,"position":<payload.position>,"text":"…","alternatives":[]}'
Pass request, revision and position back unchanged. It fails with
"canceled, replaced or unknown" when the request was cancelled or the document
moved on; drop it, do not retry. Keep text short and do not repeat what is
already after the cursor.
The next keystroke cancels a request, usually within a second. An agent that
handles one command at a time rarely answers in time, so for working ghost
text start the seat with --model @alias and let the seat answer them.
Ask about a selection (ask.request)
intent:"chat" is a question from the agent panel with no selection: answer
it in the agent.reply text and do not edit. intent:"edit" asks for a
change. Answer by editing, not chatting. Rewrite range as a CriticMarkup
suggestion the person accepts or rejects:
archdev plan collab suggest --handle plan.collab.json --input \
'{"revision":<current>,"range":[<from>,<to>],"text":"<replacement>","id":"s_<8 chars a-z0-9>","author":"<your name>"}'
- Empty
rangeinserts, emptytextdeletes, both non-empty replaces. idiss_plus eight lowercase base-36 characters, fresh each time.authoris a one-line label for the suggestion.- It fails if the range overlaps pending markup, sits in a fenced code block,
or
textcontains{++ {-- {~~ {>> ++} --} ~~} <<}or~>. Split the edit or shorten the range; there is no escape syntax. quoteis the selected text at ask time; ifreadshows it moved, find it again before using positions.- An empty
rangewith an emptyquote(Ask with no selection) inserts at that position.
Then clear the prompt with a one-line reply, edit or chat:
archdev plan collab signals --handle plan.collab.json --input \
'{"kind":"agent.reply","to":"actor:<from>","payload":{"request":"<payload.id>","text":"…"}}'
Agent task (agent.request)
-
Send
run.statuswithstate:"working"(section 5). -
Read, then change the plan. Prefer
suggestwhen the person should review each change; useapplyfor whole-section rewrites they asked for.applytakes the complete new document and sends the smallest diff:archdev plan collab apply --handle plan.collab.json --input '{"revision":<current>,"text":"<full document>"}' -
Send
run.statuswithstate:"done"and the ranges you wrote. -
Reply with a one-line summary:
archdev plan collab signals --handle plan.collab.json --input \ '{"kind":"agent.reply","to":"actor:<from>","payload":{"request":"<id>","text":"…"}}'
Review suggestions are plain text in the markdown, not channel messages:
{~~old~>new~~}, {++add++} or {--del--}, immediately followed by
{>>id:s_xxxxxxxx by:Name<<}. OT anchors them and checkpoints persist them.
read lists the pending ones. Do not hand-write markup with apply; use
suggest, which validates it.
4. Approval
On plan.approved the seat prints a report.required event naming
risk.plan and log post plan.updated. Follow the archdev skill's plan
reporting rules for that post. After approval do not edit until
plan.reopened.
Break into tasks (plan.tasks.request)
After approval the person can ask for a draft task graph. Read the plan, draft
tasks from its steps, and reply to the requester with the same request and
revision:
archdev plan collab signals --handle plan.collab.json --input \
'{"kind":"plan.tasks.result","to":"actor:<from>","payload":{"request":"<payload.request>","revision":<payload.revision>,"tasks":[{"key":"t1","title":"…","risk":"low","area":"…","dependsOn":[]}]}}'
risk is low, medium or high; dependsOn lists other task keys. The
page shows the draft and offers archdev tasks review start; follow the
tasks skill from there.
5. Runs and undo
A run is one unit of agent work the person can review and undo.
archdev plan collab run --handle plan.collab.json --input \
'{"to":"all","payload":{"run":"r1","state":"working","steps":[{"text":"Reading plan","done":true}],"ranges":[[120,180]]}}'
-
stateisworking,doneorstopped. Update the samerunid as you go;stepsare{text, done},rangesare the[from,to]code-point spans you wrote, in the document at send time. -
Use
stoppedif you abandon the work. -
On
run.undo,read, locate the text that run wrote (its last reported ranges, shifted by later edits), andapplythe document with exactly that text removed. Do not undo more than that run. Then sendrun.statusfor it with"undone":trueand"reviewed":true, keeping itsrunid; the page keeps the run open and approval blocked until it sees them:archdev plan collab run --handle plan.collab.json --input \ '{"to":"all","payload":{"run":"r1","state":"done","steps":[{"text":"Undone","done":true}],"ranges":[],"undone":true,"reviewed":true}}'
Gotchas
- The seat is one process per plan. Keep it running while you work; if
signalssays the handle is invalid, the seat has exited, so start again. - Handle one signal at a time and answer each; unanswered requests make the page look stuck.
- Never fabricate a signal the person did not trigger.
- Editing the seed file changes nothing in the plan. Change the plan with
suggestorapply.