Commit Graph

4 Commits

Author SHA1 Message Date
2f49e191c6 feat: propose-work — push, open the PR, and move the issue, in one command
All checks were successful
CI / Native — linux (pull_request) Successful in 34m50s
CI / WASM — Web (pull_request) Successful in 29m34s
CI / Formatting (pull_request) Successful in 55s
GITEA-SETUP.md has listed this under "Still to build" since 2026-09-04:

  `propose-work`, superseding `push-work` -- push the branch AND open the PR
  with `Closes #N` AND set the label, in one step. Today `push-work` does the
  first third; the other two thirds being manual is how they get skipped.

PROTOCOL.md 145-149 requires all three:

  * branch `auto/<agent>/<issue#>-<topic>`, one item per branch
  * open the PR with `Closes #<issue>` in the body
  * label the issue `state/needs-human` and say, in one line, what to look at

Until now those were three things to remember, and PR #20 had to add a warning
to `port-loop.md` about the two that get forgotten. A rule enforced by memory
decays; this makes the sequence structural.

WHAT IT DOES NOT DO is reimplement push-work's refusals -- it CALLS push-work,
so `main`, shared branches and force-push stay refused in exactly one place.
Duplicating them would let the copies drift, and the copy that drifts is the
one that matters.

Three design choices worth stating:

  * THE ISSUE NUMBER IS DERIVED FROM THE BRANCH NAME, which PROTOCOL already
    specifies as `auto/<agent>/<issue#>-<topic>`. So a PR cannot cite a
    different issue than the branch was cut for -- a mismatch no reviewer would
    catch. `-i` overrides.
  * `-m` IS MANDATORY. PROTOCOL says an issue in `state/needs-human` must say
    what to look at and what pass and fail look like, "so a person can judge it
    in under a minute". Refusing without that line is cheaper than letting the
    label carry an empty promise and costing a human a round trip.
  * IT MOVES THE LABEL RATHER THAN ADDING IT -- other `state/*` labels are
    removed. Leaving `state/in-progress` attached makes the board lie about
    what is waiting on a person.

The token is read from a file and handed to curl through a `--config` document
on stdin: never an argument, never exported. Arguments are world-readable in
/proc and this token can push. Verified that the mechanism actually delivers
the header rather than silently dropping it -- with a bogus token the API
answers "invalid username, password or token", while the same URL with no
header returns the list anonymously, so the header is demonstrably being read.

Also verified: issue derived from the branch (#42 from
`auto/decoder/42-widget-census`), refusal without `-m`, refusal when the branch
carries no number, `--dry-run` sends nothing, and repo/API derivation from the
remote. `--dry-run` deliberately does NOT require a credential -- it exists so
an agent can check the command it is about to run, and demanding a token it
never sends would make the check unavailable exactly where it is cheapest.

Identical in both agents' bin/ on purpose: the agent name comes from the
branch, so there is nothing per-agent to diverge.

UNVERIFIED, and stated as such: no end-to-end run. That needs a real token and
a real issue, which this desktop does not have -- it is the second machine, and
the agent credentials live on the agent box. Everything above the network call
is exercised; the POSTs are not.

Refs #11

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 19:17:41 +02:00
MechaCat02
3a1721abe7 docker: stop the wrapper typing into live sessions, and support per-agent logins
Both agents stopped, and the decoder diagnosed it itself:

  "I received '2' and '1' but I don't have a pending question those would
   answer -- I was in the middle of setting up the /loop cron job."

claude-autonomous matched the BARE SUBSTRINGS 'Choose', 'trust' and 'accept' to
answer Claude Code's one-time first-run gates. The /loop prompt is echoed into
the terminal, and that day's briefs contain 'accepted as-is' and 'least
trustworthy' -- so expect matched the agent's OWN INSTRUCTIONS and typed 2\r and
1\r into a running session, which then sat waiting for a human to explain them.

The old comment argued a multi-word pattern 'never matches' because the gate
text wraps. True of a literal string, false of a whitespace-tolerant regex, which
is what these now are: \s+ spans the wrap, and the terminal is 200 columns wide.

Measured, old against new, against the real brief text and a real gate:

  {accept}                     brief 0  gate 1   (case-sensitive; briefs say 'accepted')
  {Yes,\s*I\s+accept}          brief 0  gate 1
  {trust}                      brief 1  <- the trigger
  {Do\s+you\s+trust\s+the\s+files} brief 0

Two defences, because one is not enough for something that can type: patterns
prose cannot match, and gates skipped ENTIRELY on resume (SYLPH_SKIP_GATES) --
a resumed session cannot show a first-run gate, so there is nothing to answer
and everything to lose. Timeout cut 90s -> 25s for the same reason.

Also: SYLPH_OWN_LOGIN. Remote Control stopped registering under the long-lived
token, and the likely reason is scope -- `claude auth login` requests
user:sessions:claude_code and the token's auth status reports no email, org or
subscription. A per-agent `claude auth login` restores Remote Control AND avoids
the rotation collision, because each agent holds its own grant rather than a copy
of one. The flag stops the entrypoint seeding the host's credentials over it.
2026-09-04 15:37:15 +02:00
MechaCat02
f173382fd3 docker: the expect wrapper swallowed both the signal and the exit status
Some checks failed
CI / Native — ubuntu-latest (push) Failing after 8m8s
CI / WASM — Web (push) Failing after 6m58s
CI / Formatting (push) Failing after 1m3s
CI / Native — macos-latest (push) Has been cancelled
CI / Native — windows-latest (push) Has been cancelled
A tooling review predicted a PID-1 signal problem from two symptoms we could not
explain: `OOMKilled: true` with **ExitCode 0**, and `--continue` failing to find
a conversation that plainly existed. Traced it, and the prediction was right --
though the culprit is not PID 1, it is one level below.

The path is  tini (PID 1) -> entrypoint.sh (exec'd) -> expect -> spawn -> claude

`spawn` CANNOT be an exec: expect has to stay alive to drive the pty. So expect
is the process Docker signals, and everything depends on it passing things on.
It did neither, in two lines:

1. NO SIGNAL FORWARDING, no trap of any kind. `docker stop` sent SIGTERM to
   expect, which died and took the pty with it. Claude Code never got a SIGTERM,
   so it never ran SessionEnd hooks and never wrote lastSessionId/history --
   which are written ONLY at a graceful shutdown. That is the entire reason
   `claude --continue` answered "No conversation found to continue" with 33 MB of
   transcripts in the volume beside it, and why we resume by scraping a session
   id off a transcript filename.

2. `eof { exit }` RETURNED 0 FOR EVERY DEATH. A bare `exit` in expect is exit
   ZERO. When the OOM-killer took the child, expect saw EOF and reported a clean
   exit. `OOMKilled: true` with `ExitCode 0` was never Docker being odd -- it was
   this line. It also meant `--restart on-failure` would read a memory kill as
   success, which is why the policy had to be `unless-stopped`.

Fixed and MEASURED, old against new, in a container:

  child exits 7        old -> 0    (the bug)      new -> 7
  SIGTERM to wrapper   old -> 143, child's trap NEVER RAN
                       new -> 42,  child trapped and cleaned up

Same file in both images; they were byte-identical, so the port copy takes the
same change.

Consequences worth stating: a kill now reports 137 rather than 0, so exit codes
mean what they say; `docker stop` gives Claude Code a real SIGTERM, so it runs
SessionEnd and writes the session index -- which may make the transcript-filename
resume unnecessary. That is not assumed here: the resume path stays as it is
until it is verified redundant.
2026-09-03 21:07:19 +02:00
MechaCat02
06676d3dc0 containers: each agent clones the monorepo into its own volume
Some checks failed
CI / Native — ubuntu-latest (push) Failing after 7m48s
CI / WASM — Web (push) Failing after 7m16s
CI / Formatting (push) Failing after 1m15s
CI / Native — macos-latest (push) Has been cancelled
CI / Native — windows-latest (push) Has been cancelled
The last structural fix for the collision class that has bitten three times. Both
containers now clone the repository into their OWN named volume instead of
bind-mounting a human's working tree, so an agent's local git config cannot
capture a human's commits, a credential helper cannot leak a container-only path
onto the host, and a `git add -A` cannot sweep another party's in-flight files.

Cloned once at startup and never auto-pulled: pulling under a running agent
moves files out from under whatever it is mid-edit, which is the same bug again.

Accepted knowingly: Claude Code keys per-project memory off the working
directory, so moving off the host path starts that memory empty. The corpus in
docs/ is the memory that matters and it travels with the clone.

Other changes:
* docker/agent -> docker/decoder; the launcher is sylph-decoder. Roles, not
  "the agent", now that there is more than one.
* /reborn is gone -- one repository now, so the port reads HANDOFF from its own
  checkout rather than through a live read-only mount of someone else's tree.
* Canary mounts separately at /canary; it stays a fork tracking upstream.
* A shared `sylpheed-exchange` volume at /exchange, with tools/ on PATH so
  `share` is available in both.
* The decoder's credential file gets the .host-copy treatment the port already
  had -- `credential.helper=store` rewrites by rename-over-target, which is
  EBUSY on a bind mount and reports a fatal that is not one.
* Budget split deliberately: decoder 5 cpu / 6 GB, port 3 / 4, leaving room for
  the planned Referee. "Half the host" was right when there was one agent.

Prompts move to docs/agents/ and are rewritten around the protocol: the oracle
is the running game, dynamic RE stays with the decoder, each iteration must
attempt to refute one claim of the other, and neither may verify its way out of
its own role.
2026-08-29 11:48:30 +02:00