docker: two ways to talk to the loose agent
Remote Control, so a detached run is not a one-way trip: ./sylph-agent remote -> https://claude.ai/code/session_... ./sylph-agent attach -> the container's own terminal `loose` now starts Claude Code with `--remote-control <name>`, registering the session with your account so you can chat with it from claude.ai or a phone. The name is passed EXPLICITLY because the flag's value is optional — a bare `--remote-control` swallows the /loop prompt that follows as the session name. Off with SYLPH_REMOTE=0, renamed with SYLPH_REMOTE_NAME. The pty is forced to 200x50. A detached `docker run -t` gives 80x24, and Claude Code hard-wraps to the terminal width — which truncated the Remote Control URL to ".../session_01..." in the one place you actually need to read it, and made `docker logs` nearly unusable besides. `remote` waits up to six minutes and reports progress: registration lands a minute or two after launch, so answering "not found" immediately would be answering a different question than the one being asked. Verified: a loose run registers and `./sylph-agent remote` prints the full URL. `attach` is NOT verified end to end from here — `docker attach` refuses a piped stdin ("cannot attach stdin to a TTY-enabled container"), which is a property of this harness rather than of the container, so it needs a real terminal to try. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@ the emulator, reads its guest memory and photographs its screen.
|
|||||||
./sylph-agent shell # poke around
|
./sylph-agent shell # poke around
|
||||||
./sylph-agent agent # Claude Code, --dangerously-skip-permissions
|
./sylph-agent agent # Claude Code, --dangerously-skip-permissions
|
||||||
./sylph-agent loose # turn it loose: detached, /loop, self-paced
|
./sylph-agent loose # turn it loose: detached, /loop, self-paced
|
||||||
|
./sylph-agent remote # link to chat with it from anywhere
|
||||||
|
./sylph-agent attach # chat with it locally
|
||||||
./sylph-agent logs -f # watch it
|
./sylph-agent logs -f # watch it
|
||||||
./sylph-agent stop # stop it
|
./sylph-agent stop # stop it
|
||||||
```
|
```
|
||||||
@@ -26,8 +28,28 @@ fixed cadence instead of letting it self-pace.
|
|||||||
|
|
||||||
It runs `-d` **without** `--rm`, so the transcript survives the container
|
It runs `-d` **without** `--rm`, so the transcript survives the container
|
||||||
exiting — for an unattended run that is the only record of what happened.
|
exiting — for an unattended run that is the only record of what happened.
|
||||||
`./sylph-agent attach` joins the live session (Ctrl-P Ctrl-Q to leave it
|
|
||||||
running).
|
### Talking to it
|
||||||
|
|
||||||
|
Two channels, both live while it works:
|
||||||
|
|
||||||
|
* **`./sylph-agent remote`** prints a `https://claude.ai/code/session_…` link.
|
||||||
|
`loose` starts Claude Code with `--remote-control`, so the session registers
|
||||||
|
with your account and you can chat with it from claude.ai or your phone —
|
||||||
|
which is the point of a detached run. Disable with `SYLPH_REMOTE=0`, rename
|
||||||
|
with `SYLPH_REMOTE_NAME`.
|
||||||
|
|
||||||
|
Registration takes a minute or two after launch, so `remote` waits for it
|
||||||
|
rather than reporting "not found" to what is really "not yet".
|
||||||
|
|
||||||
|
* **`./sylph-agent attach`** joins the container's own terminal. Type to talk to
|
||||||
|
it; **Ctrl-P Ctrl-Q** detaches and leaves it running. Do not press Ctrl-C —
|
||||||
|
that goes to the agent.
|
||||||
|
|
||||||
|
The pty is forced to 200×50 (`stty_init` in `bin/claude-autonomous`). A detached
|
||||||
|
`docker run -t` is 80×24, and Claude Code hard-wraps to the terminal width,
|
||||||
|
which truncated the Remote Control URL to `…/session_01…` in the one place you
|
||||||
|
need to read it — and made `docker logs` almost unreadable besides.
|
||||||
|
|
||||||
**It cannot push.** No git credentials are mounted, deliberately: a human
|
**It cannot push.** No git credentials are mounted, deliberately: a human
|
||||||
reviews before anything leaves the box. Review with
|
reviews before anything leaves the box. Review with
|
||||||
|
|||||||
@@ -28,6 +28,12 @@
|
|||||||
set timeout 90
|
set timeout 90
|
||||||
log_user 1
|
log_user 1
|
||||||
|
|
||||||
|
# Give the pty a wide, tall geometry. A detached `docker run -t` defaults to
|
||||||
|
# 80x24, and Claude Code hard-wraps to the terminal width — which truncates the
|
||||||
|
# Remote Control URL to "https://claude.ai/code/session_01…" in the one place
|
||||||
|
# you need to read it, and makes `docker logs` nearly unusable generally.
|
||||||
|
set stty_init "rows 50 cols 200"
|
||||||
|
|
||||||
set answered_theme 0
|
set answered_theme 0
|
||||||
set answered_trust 0
|
set answered_trust 0
|
||||||
set answered_bypass 0
|
set answered_bypass 0
|
||||||
|
|||||||
@@ -115,6 +115,15 @@ if [ "${SYLPH_AUTONOMOUS:-0}" = "1" ]; then
|
|||||||
fi
|
fi
|
||||||
# The flag the user asked for. It is refused under root, which is why this
|
# The flag the user asked for. It is refused under root, which is why this
|
||||||
# image runs as `agent`.
|
# image runs as `agent`.
|
||||||
|
# Remote Control registers the session with your account so you can chat with
|
||||||
|
# the agent from claude.ai or your phone — the point of a detached run being
|
||||||
|
# that you are not sitting in front of it. The name is passed EXPLICITLY: the
|
||||||
|
# flag's value is optional, so a bare `--remote-control` would swallow the
|
||||||
|
# /loop prompt that follows as the session name.
|
||||||
|
if [ "${SYLPH_REMOTE:-1}" != "0" ]; then
|
||||||
|
set -- --remote-control "${SYLPH_REMOTE_NAME:-sylpheed-agent}" "$@"
|
||||||
|
log "Remote Control enabled as '${SYLPH_REMOTE_NAME:-sylpheed-agent}'"
|
||||||
|
fi
|
||||||
# claude-autonomous wraps `claude --dangerously-skip-permissions` in a pty and
|
# claude-autonomous wraps `claude --dangerously-skip-permissions` in a pty and
|
||||||
# answers the one-time first-run gates. The Bypass Permissions disclaimer in
|
# answers the one-time first-run gates. The Bypass Permissions disclaimer in
|
||||||
# particular has no config key that skips it, so unattended it hangs forever.
|
# particular has no config key that skips it, so unattended it hangs forever.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
# ./sylph-agent agent [prompt] Claude Code, --dangerously-skip-permissions
|
# ./sylph-agent agent [prompt] Claude Code, --dangerously-skip-permissions
|
||||||
# ./sylph-agent loose [task] turn it loose: detached, /loop, self-paced
|
# ./sylph-agent loose [task] turn it loose: detached, /loop, self-paced
|
||||||
# ./sylph-agent logs [-f] what the loose agent is doing
|
# ./sylph-agent logs [-f] what the loose agent is doing
|
||||||
|
# ./sylph-agent remote print the Remote Control link (chat from anywhere)
|
||||||
# ./sylph-agent attach attach to the loose agent's session
|
# ./sylph-agent attach attach to the loose agent's session
|
||||||
# ./sylph-agent run <cmd...> one-shot command
|
# ./sylph-agent run <cmd...> one-shot command
|
||||||
# ./sylph-agent stop stop it
|
# ./sylph-agent stop stop it
|
||||||
@@ -18,6 +19,8 @@
|
|||||||
# SYLPH_CLAUDE_HOME host dir mounted as the agent's ~/.claude
|
# SYLPH_CLAUDE_HOME host dir mounted as the agent's ~/.claude
|
||||||
# (default: $HOME/.claude — shares auth AND memory with you)
|
# (default: $HOME/.claude — shares auth AND memory with you)
|
||||||
# SYLPH_VULKAN=sw force software Vulkan (lavapipe) even if /dev/dri exists
|
# SYLPH_VULKAN=sw force software Vulkan (lavapipe) even if /dev/dri exists
|
||||||
|
# SYLPH_REMOTE=0 do NOT enable Remote Control (default: enabled for `loose`)
|
||||||
|
# SYLPH_REMOTE_NAME Remote Control session name (default: sylpheed-agent)
|
||||||
# SYLPH_CPUS / SYLPH_MEM_GB override the computed half
|
# SYLPH_CPUS / SYLPH_MEM_GB override the computed half
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -124,6 +127,8 @@ docker_args() {
|
|||||||
|
|
||||||
[ -n "${ANTHROPIC_API_KEY:-}" ] && _out+=(-e "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY")
|
[ -n "${ANTHROPIC_API_KEY:-}" ] && _out+=(-e "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY")
|
||||||
[ -n "${SYLPH_VULKAN:-}" ] && _out+=(-e "SYLPH_VULKAN=$SYLPH_VULKAN")
|
[ -n "${SYLPH_VULKAN:-}" ] && _out+=(-e "SYLPH_VULKAN=$SYLPH_VULKAN")
|
||||||
|
[ -n "${SYLPH_REMOTE:-}" ] && _out+=(-e "SYLPH_REMOTE=$SYLPH_REMOTE")
|
||||||
|
[ -n "${SYLPH_REMOTE_NAME:-}" ] && _out+=(-e "SYLPH_REMOTE_NAME=$SYLPH_REMOTE_NAME")
|
||||||
[ -n "${SYLPH_ISO:-}" ] && _out+=(-e "SYLPH_ISO=$SYLPH_ISO")
|
[ -n "${SYLPH_ISO:-}" ] && _out+=(-e "SYLPH_ISO=$SYLPH_ISO")
|
||||||
|
|
||||||
# ── GPU ──
|
# ── GPU ──
|
||||||
@@ -196,8 +201,9 @@ case "${1:-}" in
|
|||||||
docker run -d -i -t "${ARGS[@]}" "$IMAGE" "/loop ${INTERVAL:+$INTERVAL }$TASK" >/dev/null
|
docker run -d -i -t "${ARGS[@]}" "$IMAGE" "/loop ${INTERVAL:+$INTERVAL }$TASK" >/dev/null
|
||||||
echo
|
echo
|
||||||
echo " running detached as '$NAME'."
|
echo " running detached as '$NAME'."
|
||||||
|
echo " ./sylph-agent remote link to chat with it from anywhere"
|
||||||
echo " ./sylph-agent logs -f follow it"
|
echo " ./sylph-agent logs -f follow it"
|
||||||
echo " ./sylph-agent attach attach to the session (Ctrl-P Ctrl-Q to detach)"
|
echo " ./sylph-agent attach chat with it locally (Ctrl-P Ctrl-Q to leave it running)"
|
||||||
echo " ./sylph-agent stop stop it"
|
echo " ./sylph-agent stop stop it"
|
||||||
echo
|
echo
|
||||||
echo " It commits to auto/* branches and cannot push — no git credentials"
|
echo " It commits to auto/* branches and cannot push — no git credentials"
|
||||||
@@ -205,7 +211,35 @@ case "${1:-}" in
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
logs) shift; exec docker logs "$@" "$NAME" ;;
|
logs) shift; exec docker logs "$@" "$NAME" ;;
|
||||||
attach) exec docker attach "$NAME" ;;
|
|
||||||
|
remote)
|
||||||
|
# Fish the Remote Control link out of the session's own output. Claude Code
|
||||||
|
# prints it once, when the session registers with your account — which takes
|
||||||
|
# a minute or two after launch, so this waits rather than answering "not
|
||||||
|
# found" to a question that is really "not yet".
|
||||||
|
printf 'waiting for the session to register' >&2
|
||||||
|
for i in $(seq 1 90); do
|
||||||
|
url=$(docker logs "$NAME" 2>&1 \
|
||||||
|
| sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\r//g' \
|
||||||
|
| grep -oE 'https://claude\.ai/code/[A-Za-z0-9_-]+' | tail -1)
|
||||||
|
if [ -n "${url:-}" ]; then
|
||||||
|
printf '\n' >&2
|
||||||
|
echo "$url"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
docker ps -q -f "name=$NAME" | grep -q . || {
|
||||||
|
printf '\n' >&2
|
||||||
|
echo "container is not running — start it with: ./sylph-agent loose" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
printf '.' >&2
|
||||||
|
sleep 4
|
||||||
|
done
|
||||||
|
printf '\n' >&2
|
||||||
|
echo "no Remote Control URL after 6 minutes." >&2
|
||||||
|
echo " Launched with SYLPH_REMOTE=0? Or check: ./sylph-agent logs | tail" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
shell|agent|run)
|
shell|agent|run)
|
||||||
mode=$1; shift
|
mode=$1; shift
|
||||||
|
|||||||
Reference in New Issue
Block a user