Skip to content

agent: create and manage agents from the terminal - #8

Merged
ale-rinaldi merged 7 commits into
mainfrom
feat/agent-management
Sep 5, 2026
Merged

agent: create and manage agents from the terminal#8
ale-rinaldi merged 7 commits into
mainfrom
feat/agent-management

Conversation

@ale-rinaldi

Copy link
Copy Markdown
Member

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

$ dwshell agent create web-01
Agent "web-01" created.

Installation code: 281-407-902

Download the agent on the target machine and enter this code when the installer
asks for it:
     https://www.dwservice.net/download.html

Plus agent code, agent reinstall, agent rm, agent group <agent> <group>|--none, each with --json.

dwshell list now shows an agent created but never installed as pending rather 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:

Downloading file distr.json...
Silent installation forbidden. Please contact the support.

The refusal is server-side (#SILENTFORBIDDEN, surfaced at ui/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:

Assumed Actually
tempCode type string JSON number — a string field fails to unmarshal
code format the bare number dashed, 281-407-902 — the client groups it in threes and the installer keeps the dashes
update payload the changed field the whole record — a partial update earns java.lang.NullPointerException
osType on a new agent a real OS null until installed

agent/reinstall was 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 list shows no leftovers.

  • create → code; list shows pending
  • code on a pending agent; refused on an installed one (pointing at reinstall) and on a share
  • reinstall → a different code (416-417-549588-100-579)
  • rm without a terminal and without --yes → refused
  • unknown group → error listing the groups that exist
  • group assign and --none → both work
  • duplicate name → the service's own message, L'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

ale-rinaldi and others added 7 commits September 5, 2026 23:37
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
ale-rinaldi force-pushed the feat/agent-management branch from d5f2f22 to dc26bc5 Compare September 5, 2026 21:37
@ale-rinaldi
ale-rinaldi merged commit b84be49 into main Sep 5, 2026
1 check passed
@ale-rinaldi
ale-rinaldi deleted the feat/agent-management branch September 5, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant