agent: create and manage agents from the terminal - #8
Merged
Conversation
Creating a DWService agent from the terminal and getting the code that drives an unattended installation, plus the lifecycle around it: read the code again, regenerate it, delete an agent, move it between groups. The protocol was established without creating anything on the account: the read path is a call dwshell already makes for `list`, and the write path was derived from the client's own datasource module. Two facts shape the design — `tempCode` already arrives in the listing dwshell reads today, and a commit returns the created item, so creation and code retrieval are a single round trip. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
Eight tasks, each ending in something testable, with the protocol now
confirmed live rather than derived: an agent was created and deleted
against the account to fix the wire form of the commit envelope.
Two facts that only the live call could give, both of which would have
shipped broken:
- tempCode is a JSON number, not a string, so a string field would
have failed to unmarshal;
- it is never shown or typed as a bare number. The client groups it in
threes (281-407-902) and the installer forwards the code stripping
only whitespace, keeping the dashes, so the dashed form is what the
service expects. It is padded to nine digits first, since a leading
zero cannot survive a JSON number.
The plan also carries the licence constraint as a test rather than as a
habit: the download page is where the terms are accepted, so nothing may
print a download-and-run one-liner, and the direct file URLs stay
unused even though they are known.
Windows is deferred: Linux and macOS are validated live, and the Windows
run line ships marked as read from the installer's source, not executed.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
DWService edits a datasource by submitting a batch of pending changes, one module at a time. This is that envelope, written once for both the agent and the group module. A rejection arrives inside a successful HTTP response, as a status other than "ok" with a message, so the service's own wording is dug out and surfaced rather than replaced with a generic failure. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
An agent created but never installed arrives as state "W" with an installation code and a null osType. dwshell mapped state to online/ offline only, so such an agent showed up as "Linux offline" — wrong twice over: it is waiting for the installer, and the service has not reported an OS for it at all. Machine now carries Pending, InstallCode and IDGroup, all of which already arrive in the listing `list` fetches; only the decoding was missing. `list` shows them as pending with an empty OS column. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
`dwshell agent create <name>` registers the agent and prints the code that binds an installation to it. The service mints the code as part of creating the record, so this is one round trip. The code is rendered the way the web client shows it and the way the installer expects it — three groups of three, dash-separated, padded to nine digits because a leading zero could not have survived a JSON number. What is deliberately absent is a download-and-run one-liner. The DWService download page is where the licence is accepted, and piping the installer into a shell would route around that, so the output names the page and gives a run line that assumes the installer is already there. The direct file URLs are known and stay unused; a test enforces it rather than a comment. Group lookup comes along because --group needs it: reading and resolving groups, not creating them, so a typo is an error listing the groups that exist rather than a new group on the account. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
Completes the lifecycle: read back the code of an agent still pending
installation, delete an agent, mint a fresh code, and move an agent
between groups.
Both irreversible operations confirm first, naming the agent, and refuse
outright when there is no terminal unless --yes was passed, so a script
cannot delete a machine or invalidate a code by accident.
Two things live testing settled that reading the client could not:
- agent/reinstall works as derived, and does not echo the record, so
the new code is read back from the listing;
- an update must carry the whole record. Sending only the changed
field earned a java.lang.NullPointerException from the service —
the browser client merges its edit into the loaded item and sends
that, so SetAgentGroup reads the record back before writing it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
Silent installation is refused by the service, not by the installer. Tested live in a Debian container with a real code: the installer takes -silent key=<code>, downloads, and the server answers with a #SILENTFORBIDDEN marker that surfaces as "Silent installation forbidden. Please contact the support." (ui/installer.py:2587). What gates it was not established — this account has no subscription, which is a plausible but unverified explanation. So the run lines are gone, from the output and from the documentation both. Promising an unattended setup that the service declines would send people down a path that fails, and a comment saying "this may not work" in the middle of a copy-paste command is worse than not printing it. What is left is still the point of the feature: the code is obtained, read back, regenerated and managed from the terminal instead of from a browser. Only entering it into the installer stays manual. The test now enforces both exclusions — no download-and-run line, because the page is where the licence is accepted, and no mention of the unattended mode, because it does not work. README and PROTOCOL.md document the commands and the datasource protocol, including the two things live testing corrected: tempCode is a number rendered in dashed groups of three, and an update must carry the whole record or the service answers NullPointerException. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
ale-rinaldi
force-pushed
the
feat/agent-management
branch
from
September 5, 2026 21:37
d5f2f22 to
dc26bc5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements
docs/superpowers/specs/2026-09-05-agent-management-design.md.Depends on #7 — this branch is rebased on it, and the error path here does not work without it.
What it does
Plus
agent code,agent reinstall,agent rm,agent group <agent> <group>|--none, each with--json.dwshell listnow shows an agent created but never installed aspendingrather than as an ordinary offline machine — the service reports it with no OS at all, so that column stays empty.Scope changed during the work, twice
Unattended installation is gone. The spec set out to print a
-silent key=<code>line. The installer does accept it, but the service refuses to serve it — tested live in a Debian container with a real code:The refusal is server-side (
#SILENTFORBIDDEN, surfaced atui/installer.py:2587); what lifts it was not established. Rather than document a path that fails, the output gives the code and the page, and a test enforces that nothing mentions the unattended mode.No download-and-run one-liner either, by design from the start: the download page carries the licence acceptance ("By selecting the 'Download' button I accept the Terms and Conditions…"). The direct file URLs are known, verified reachable, and deliberately unused.
What live testing corrected
Reading the client was not enough; each of these would have shipped broken:
tempCodetype281-407-902— the client groups it in threes and the installer keeps the dashesupdatepayloadjava.lang.NullPointerExceptionosTypeon a new agentagent/reinstallwas also derived rather than observed; it works, and answers with a bare acknowledgement, so the new code is read back from the listing.Verification
Every subcommand was exercised against the live account, and every test agent and the temporary group were deleted afterwards —
dwshell listshows no leftovers.listshowspendingcodeon a pending agent; refused on an installed one (pointing atreinstall) and on a sharereinstall→ a different code (416-417-549→588-100-579)rmwithout a terminal and without--yes→ refused--none→ both workL'agente 'x' già esiste.(needs session: count framed payload lengths in characters, not bytes #7)gofmt,go vet,go test -race ./...green.🤖 Generated with Claude Code
https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG