Commit Graph
100 Commits
Author SHA1 Message Date
dongarandClaude Opus 5.5 7a57cef9ff Benchmark: the seed is a git repository's first commit
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m28s
CI / checks (pull_request) Successful in 3m33s
The agent works in a clone, but benchmark workspaces were plain
directories, so a model that starts with `git log` failed two commands
in a row and was stopped as stuck within seconds (Flash-Next, 10 of 13
failures on 2026-10-24). The seed is now committed on main, locally and
on the build VM, and how a workspace is set up is part of each task's
hash, so earlier runs are reported as not comparable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-24 02:44:22 +00:00
dongarandClaude Opus 5.5 073e0caf1a Docs: rewrap the identity lines; M6.3 names only today's directory
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m12s
CI / e2e (pull_request) Successful in 4m0s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-20 23:11:47 +00:00
dongarandClaude Opus 5.5 31c97ccfb5 Log every model call's time and tokens
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m42s
CI / checks (pull_request) Successful in 2m57s
Each call through BudgetedClient emits llm.call: seconds, input,
output and cached tokens, and the answering model when a fallback
answered; a failed call logs how long it ran before it failed.
Usage now carries the server's cached-prompt count on both routes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-20 23:07:18 +00:00
dongarandClaude Opus 5.5 bc25f54356 Plan: the status table says nisse-review runs from its image
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m53s
CI / checks (pull_request) Successful in 1m48s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-20 03:14:25 +00:00
dongarandClaude Opus 5.5 2fb654f92b M7.6: nisse-review's image
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m0s
CI / checks (pull_request) Successful in 1m49s
A `review` target in the Dockerfile: its own user (nisse-review, uid
10002), git for its clones, state and clones in /var/lib/nisse-review,
configuration at /etc/nisse-review, and only its own agent installed.
release.yml publishes gitea.dongar.net/dev/nisse-review beside nisse-dev,
at the same version. review-bringup.sh builds and runs the image, as
bringup.sh runs nisse-dev's.

The test stage adds an agent package its base lacks, without its
dependencies, since the suite's own code imports every agent's. Without
that, the tests over nisse-dev's image no longer import since
test_review_live arrived. tests/deploy/test_images.py checks the image and
that the test stage leaves what it installed as it was.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-20 00:05:31 +00:00
dongarandClaude Opus 5.5 0694b47c34 nisse-dev: a settled reply posted top-level is no point to answer either
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m15s
CI / e2e (pull_request) Successful in 5m6s
nisse-review's review of #169: a reply quoting a top-level point, or one
Gitea refused on its line, arrives through list_comments, so answering it
would start the thank-you loop again. Also records what the live Gitea
showed: resolving is per comment, and resolving again is a 204.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 23:32:43 +00:00
dongarandClaude Opus 5.5 210f681ee5 CI: one e2e at a time across every PR
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m54s
CI / checks (pull_request) Successful in 2m21s
Gitea now has two runners but there is one 4090. The e2e job joins one
concurrency group without cancel-in-progress, so a second PR's e2e waits
for the first. checks still run in parallel.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 22:23:39 +00:00
dongarandClaude Opus 5.5 7cd2de6ab6 Merge main into agent-host-vault
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 6m27s
CI / checks (pull_request) Successful in 2m0s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 22:10:22 +00:00
dongarandClaude Opus 5.5 33d030590d nisse-review marks a reply that settles its thread; the PR's author resolves it
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 7m41s
CI / checks (pull_request) Successful in 1m58s
The reviewer is read-only (R3), so Gitea refuses it resolving a thread.
Now each reply says whether it settles its thread, and a settled reply
carries <!-- nisse-review: settled -->. nisse-dev, as the author, resolves
every comment on that thread's line when it watches the PR, and doesn't
take the settled reply as a point to answer. scripts/resolve_settled.py
does the same for PRs made by hand.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 22:06:56 +00:00
dongarandClaude Opus 5.5 26ef125d1e nisse-review: a review refused for an open point posts once the point is answered
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 5m10s
CI / checks (pull_request) Successful in 1m54s
On #166 the reviewer called post_review with two points unanswered, was
refused, answered them, then only skipped already-answered points until the
40-turn cap: the review went out as partial, its summary lost. A refused
ending call is now held, and the driver ends the run with it as soon as the
last point is settled (a new due hook in nisse-core's driver; nisse-dev
doesn't use it). A held call that is wrong for another reason by then
(sign_off over drafted comments) is left to the model. With nothing open,
reply and skip say which tool ends the run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 20:11:40 +00:00
dongarandClaude Opus 5.5 bf98445fcb Agent host: SilverBullet serves the vault as its one space, behind Authelia
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m25s
CI / checks (pull_request) Successful in 2m0s
The operator chose single-space mode: the vault is mounted at /data, with
memory/ read-only over it, and there are no accounts or setup wizard;
Authelia in the operator's Caddy is the login. SB_NAME forces the mode, so
an empty vault can't boot the wizard. SilverBullet's own data folder is
gone. check now fails if the wizard could be open. The README says how to
make memory/ writable for a while and back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 16:41:29 +00:00
dongarandClaude Opus 5.5 1d33d50907 Agent host: SilverBullet boots into accounts and the vault, not a bare space
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m20s
CI / checks (pull_request) Successful in 2m10s
SB_NAME alone put SilverBullet 2.11 in single-space mode: no accounts, no
setup wizard, and its /data folder served instead of the vault. The compose
file sets only PUID and PGID now, a test holds it to that, and the host
check fails on a single-space boot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 16:28:09 +00:00
dongarandClaude Opus 5.5 e287eea2d3 Agent host: the site is nisse-memory.dongar.net, inside garrett-hall.com
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 5m25s
CI / checks (pull_request) Successful in 2m30s
The operator renamed the site from vault.dongar.net.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 16:08:30 +00:00
dongarandClaude Opus 5.5 5ab0861e8e Agent host check: run every step, and say so
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m59s
CI / checks (pull_request) Successful in 2m7s
The check went to the host on stdin, and the first docker compose exec read
the rest of it: the memory and firewall checks never ran, and it passed.
It is now its own file, sent as an argument, and ends with "all checks
passed" only when every check has run. Tests fake the host with a docker
that reads stdin.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 14:48:31 +00:00
dongar be81adc185 Merge pull request 'Development Plan: every agent is its own container image' (#167) from agent-images into main
CI / checks (push) Successful in 2m41s
CI / e2e (push) Skipped
Reviewed-on: #167
2026-10-19 14:27:45 +00:00
dongarandClaude Opus 5.5 75958aef28 Development Plan: every agent is its own container image
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m3s
CI / checks (pull_request) Successful in 1m49s
Each agent ships as its own image, since that's how it runs on the agent
host. nisse-review's image becomes M7.6, ahead of the agent host; its
deployment moves to M7.7. nisse-ops' image comes with its first slice.
Also fixes two stale references to M7.6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 13:16:07 +00:00
dongarandClaude Opus 5.5 454326e750 Agent host README: rewrap the introduction
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m17s
CI / checks (pull_request) Successful in 2m20s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 13:08:16 +00:00
dongarandClaude Opus 5.5 e7a0c0f1db Agent host: the VM is now nisse, not nisse-host
CI / checks (push) Skipped
CI / e2e (pull_request) Canceled after 0s
CI / checks (pull_request) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-19 13:08:06 +00:00
dongarandClaude Opus 5.5 eafe1b9f4a The agent host's vault and SilverBullet (M5.4)
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m45s
CI / checks (pull_request) Successful in 1m50s
deploy/agent-host sets up the vault on nisse-host (infra/tofu#77) and
SilverBullet over it: the operator writes the vault, the agents' memory/
is read-only to it, and only the operator's Caddy reaches it, which
serves vault.dongar.net to the LAN. scripts/agent-host.sh installs and
checks it from here.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-18 20:55:21 +00:00
dongarandClaude Opus 5.5 369b6ed52f test_expert: a declined consult hands back the clients' real wording
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m5s
CI / checks (pull_request) Successful in 1m40s
The fake client raises _refused() from llm.py, and the test checks the
whole handback, so a change to the real message fails it (review on #165).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-18 14:01:15 +00:00
dongarandClaude Opus 5.5 9d2992c20a A model's safety-filter refusal is an error that says so, not an empty reply
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m41s
CI / checks (pull_request) Successful in 2m25s
Opus's provider filter declined one of nisse-review's ask_expert
questions (which repositories a Gitea token can list) every time. It came
back as a 200 with no text, and the expert reported "unparseable reply:
empty reply". Both LLM clients now raise ModelRefused for
finish_reason content_filter (chat route) and stop_reason refusal
(passthrough). The expert's handback says what happened and what to do
instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-18 13:39:26 +00:00
dongarandClaude Opus 5.5 a1eeab1bc7 test_trust_live: ride out a transient Gitea error while waiting for the dismissal
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m45s
CI / checks (pull_request) Successful in 1m55s
A dropped connection or a 5xx inside the wait is retried until the
deadline; any other error fails at once (review on #164).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-18 13:16:10 +00:00
dongarandClaude Opus 5.5 63ad679c8b Trust test: wait for Gitea to dismiss a stale approval before merging (#162)
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m40s
CI / checks (pull_request) Successful in 2m9s
Gitea dismisses an approval made stale by a push in a job it queues on the
push (services/pull AddTestPullRequestTask, v28.1.0), and its merge check
counts approvals not yet dismissed. Measured here, the approval stands for
0.15-0.23 s after the push; the test's merge came a couple of API calls
later, so it usually lost the race, and once won it. The test now waits for
the dismissal, with a timeout, before expecting Gitea's refusal, and checks
the agent's own rule at once: the PR's head is read from the branch, so the
approval is for an older commit straight away.

Also notes in the Development Plan why the helper loop doesn't compact.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-18 04:27:59 +00:00
dongarandClaude Opus 5.5 cb638fe687 Compaction: the model's notes replace the middle of a long run (#158)
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m57s
CI / checks (pull_request) Successful in 1m57s
Before each driver call, over compact_at (0.6) of the prompt budget, the
same model is asked for notes over the very request it was about to get,
plus one message, so the server's cache covers all but the notes. The notes,
with code's list of the calls they replace, become a compaction event that
renders in place of its range, keeping word for word what started the work
and the last few turns. Rare, large steps keep the prompt's start unchanged
between them. A later compaction folds in the earlier; one from an earlier
run's view is hidden.

Failed notes are logged and fitting takes over. Fitting now applies
compactions before it measures, records what it cuts (Trim) and logs each
cut as context.trimmed. Wired into both agents' drivers and the benchmark;
nisse-dev's helper loop is a follow-up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 22:51:36 +00:00
dongarandClaude Opus 5.5 3813ad55d6 nisse-review: read code from a git clone kept between runs
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m5s
CI / checks (pull_request) Successful in 2m3s
Archives were downloaded and unpacked afresh for every run, which doesn't
scale to very large repositories. Each repository is now cloned once, with
its whole history, under scratch/repos/<owner>/<name>/ (head/ the clone,
base/ a worktree when read). A run fetches every branch and the PR's
refs/pull/N/head, checks out the head exactly, and fails if that head is
gone. Only the latest repository is kept for now.

Git runs walled off: none of the host's git configuration, links checked
out as plain files, no hooks, submodules or LFS, only the Gitea URL's
protocol, and the token as an http.extraHeader for that URL in git's
environment only. A stop ends a git command; a cut-short first clone is
removed. The reader never shows .git. GiteaClient.archive is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 20:37:12 +00:00
dongarandClaude Opus 5.5 a85e855fc2 nisse-review: a stop ends a review in progress; quieter repo failures
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m20s
CI / checks (pull_request) Successful in 2m22s
SIGTERM used to stop only new rounds: a review in progress ran on until
systemd killed it 90s later. Now the scheduler's stop sets the driver's
cancel flag, so the run ends at its next turn, and a model call in flight
is broken off (Interruptible, raising Stopped from the signal handler, only
ever inside a model call). The item goes back to ready, with nothing
drafted posted and no failure counted; replies already posted carry their
markers and are not repeated.

intake.repo_failed is logged when a repository starts failing or the reason
changes, and intake.repo_ok when it lists again, not every round.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 20:37:12 +00:00
dongarandClaude Opus 5.5 76d8e36f43 nisse-dev: a VM lost as a helper's work is taken back ends the run
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m54s
CI / checks (pull_request) Successful in 2m0s
The property walk (seed 468, a CI run's moving seed) found it: when the
build VM went out of reach while _delegate moved the helper's work back
onto the milestone's branch, git's HostUnavailable came back as a
GitError, was noted as a fact, and the driver's run went on, calling the
model with its VM gone (no_model_on_a_lost_vm). Now a GitError caused by a
lost VM raises VMUnreachable once the delegation's intents are settled, so
the run ends and the VM is blocked, as for any other lost-VM call (#137).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 16:00:31 +00:00
dongarandClaude Opus 5.5 e107484042 nisse-review: a commit diff Gitea can't give falls back to the whole diff
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m56s
CI / checks (pull_request) Successful in 1m55s
From nisse-review's review of #156: only a failed listing fell back to
the PR's whole diff; one commit's diff failing (a force-push dropped it)
failed the run. Tests also pin down its other three points, which the
code already handled: a command is taken in once, a second review at the
same head has its own marker, and `review` beside a comment is one fresh
review.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 07:47:48 +00:00
dongarandClaude Opus 5.5 fca4c41d95 Later review test: its earlier summary is in the brief too
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m49s
CI / checks (pull_request) Successful in 2m8s
From nisse-review's draft on #156: it thought points made in a review's
summary never reached a later review. They do, as "your review: ...".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 07:06:10 +00:00
dongarandClaude Opus 5.5 37bbac7e72 nisse-review: later reviews, replies, rounds, stop and review (M7.5)
Back on a PR it reviewed: a new head gets a later review of the commits
since (each one's diff, through the API) and of its earlier points; what
was said since gets replies, answered where it was said or skipped with a
reason. It signs off once and stays quiet while still happy. Past
tuning.review.max_rounds it sums up for the operator and goes quiet,
until a push or someone other than the PR's author joins in. An
operator's `stop` quiets it; `review` asks for a fresh review.

Reviews carry numbered markers and replies their point's key, so
recovery posts nothing twice.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 07:06:06 +00:00
dongar 7e73b8fb37 Merge pull request 'nisse-review: read long files in parts; a retry starts afresh' (#157) from review-reads-and-retries into main
CI / checks (push) Successful in 3m6s
CI / e2e (push) Skipped
Reviewed-on: #157
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-17 07:03:43 +00:00
dongarandClaude Opus 5.5 1a7836167d nisse-review: read long files in parts; a retry starts afresh
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m38s
CI / checks (pull_request) Successful in 1m51s
From its failed review of #156: read_file stopped at 20,000 characters
with no way on, so the reviewer hunted each file's end a definition at a
time and ran out of turns; its retry carried every read before it, and a
model call timed out. Both drafts were lost.

- read_file takes a start line and says where the next part starts.
- Each run's view starts at its brief (Transcript.since); the log keeps
  the rest.
- A run out of turns with points drafted posts them as a partial review.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 06:48:47 +00:00
dongarandClaude Opus 5.5 637761ba5a nisse-review: a retried or recovered review keeps its head's CI wait
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 7m41s
CI / checks (pull_request) Successful in 1m41s
From nisse-review's own review of #155: a failed run, a posted one and
crash recovery rebuilt the item without seen_at, so a retry skipped the
CI wait. seen_at now carries through, renewed when the head moved during
the review. Also: a test for checks Gitea can't report, and the example
config lists ci_wait_s and ci_grace_s.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 04:35:05 +00:00
dongarandClaude Opus 5.5 59fba3bb76 Tuning: the CI waits may be zero
CI / checks (push) Skipped
CI / e2e (pull_request) Canceled after 0s
CI / checks (pull_request) Canceled after 1m18s
The live review test sets ci_grace_s: 0, which the shared tuning parser
refused (every number had to be positive). A field now opts in to zero
with metadata {"zero": True}; ci_wait_s and ci_grace_s do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 04:33:52 +00:00
dongarandClaude Opus 5.5 096eeea56b Live review test: no CI grace in its CI-less repository
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m33s
CI / e2e (pull_request) Failing after 54m48s
The CI wait made a head with no checks wait ci_grace_s (2 min), so the
live test's single round reviewed nothing. Its fresh repository has no
CI: the test sets the grace to 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 00:02:18 +00:00
dongarandClaude Opus 5.5 be5ea5f170 nisse-review: a new head's review waits for its CI
CI / checks (push) Skipped
CI / e2e (pull_request) Failing after 49m46s
CI / checks (pull_request) Successful in 1m59s
A review judges the CI result (R1), so a new head's review now waits while
its checks are pending, and starts once they finish, passed or failed. It
also keeps reviews off a GPU that CI is using: #154's first review ran
beside CI's end-to-end job and took about 30 minutes.

- Pending: waits up to tuning.review.ci_wait_s (1 h), then reviews anyway.
- No checks: waits tuning.review.ci_grace_s (2 min) for them to appear,
  then reviews.
- A PR waiting on CI holds up no other: the oldest settled one goes first.
- The head's first sighting is kept on the item (seen_at).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-16 23:08:46 +00:00
dongarandClaude Opus 5.5 6e697b55a8 nisse-review: one unlistable repository holds up no other
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m13s
CI / checks (pull_request) Successful in 2m2s
Found at bring-up: an empty private repository's /pulls answered 404, and
intake stopped there, so the whole round failed and nothing was reviewed.
A repository whose PRs can't be listed is now logged (intake.repo_failed)
and skipped for the round; its items stay as they are.

Also, from nisse-review's review of this PR: a two-page test pins the
search listing the patterns rest on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-16 22:56:43 +00:00
dongarandClaude Opus 5.5 faf973be6c nisse-review: match repository patterns against Gitea's search
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 28m44s
CI / checks (pull_request) Successful in 1m48s
At bring-up, `dev/*` failed: listing an organization's repositories
(/orgs/dev/repos) needs the read:organization token scope, which the
reviewer's token doesn't have and shouldn't need. Patterns are now matched
against /repos/search, which needs only the repository scope and already
lists every repository the account can read. A pattern that matches
nothing is logged (access.no_match). owner_repositories goes.

A Gitea error at startup is now reported like a config error, not a
traceback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-16 22:17:47 +00:00
dongarandClaude Opus 5.5 2139ddd05c nisse-review: repository patterns, and the wall checked every round
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 9m52s
CI / checks (pull_request) Successful in 1m56s
At bring-up, nisse-review read no repositories: Gitea's /user/repos leaves
out a public repository's Read collaborators, so dev/nisse never showed up.
`repositories` now holds patterns (dev/nisse, dev/*, */*) matched against
every repository the account can read, public ones included (an
organization's or user's list, or the instance's search); unset, it is
/user/repos as before. A configured repository it can't read is left out
with an oplog line.

The read-only wall (R3) is checked every round, not only at startup: an
account granted push or admin while running stops (exit 3) rather than
review on.

nisse-core's Gitea client gains GiteaNotFound for 404s, owner_repositories
and visible_repositories.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-16 21:41:53 +00:00
dongarandClaude Opus 5.5 fe9c7c19a1 CI runs nisse-review's live test
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 12m10s
CI / checks (pull_request) Successful in 2m25s
scripts/e2e.sh ran only test_end_to_end_loop.py, and the integration step
excludes e2e-marked tests, so test_review_live.py (M7.3) never ran in CI.
The script now runs every e2e-marked test under tests/integration, and the
review test is a smoke test: about a minute on the local model, and the only
check that the review account, its token and its read-only refusal work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-16 20:25:54 +00:00
dongarandClaude Opus 5.5 ecff719f1a M7.4: nisse-dev answers a peer
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m30s
CI / checks (pull_request) Successful in 2m5s
Anyone's comment on an item's PR but an operator's (and its own) is a
peer's point (D5, L11): kept on the item with an id, in the transcript as
a `peer` message labelled with its author, and never an instruction. The
driver answers each with answer_peer, inline on its line or quoting it,
or skips it with skip_peer and a recorded reason. A run going back to
waiting with points open is reminded once; then the harness lists them
on the PR. A peer's comment wakes the item; a wake by peers alone goes
back to the same wait quietly, with one line in the thread. The
operator's inline comments are now answered inline too.

Also corrects ci.yml's header: a push to main runs no end-to-end tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-16 19:42:19 +00:00
dongar 5126f0717d Merge pull request 'M7.3: nisse-review's first slice' (#149) from nisse-review into main
CI / checks (push) Successful in 2m32s
CI / e2e (push) Skipped
Reviewed-on: #149
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-16 19:19:42 +00:00
dongarandClaude Opus 5.5 f59d974a95 M7.3: nisse-review's first slice
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m18s
CI / checks (pull_request) Successful in 1m49s
A new package over nisse-core: it reviews every ready PR in the
repositories its account can read, and posts one COMMENT review.

- scheduler: each round, open PRs per readable repository (or the
  configured repositories); an item per PR, ready unless draft or WIP;
  closed with the PR. One review at a time, oldest first.
- reader: the repository at head and base from Gitea archives, unpacked
  and read in Python (read_file, list_files, search_code), and its issues;
  bound to the item's repository; no git, no shell.
- review: starts from the PR, commits, diff, comments, closed issues, CI;
  comment drafts a point on any existing line; post_review posts one
  COMMENT review at the head, inline where Gitea takes it, else as links in
  the summary. ask_expert; vault and memory (memory/nisse-review).
- L8: posting is an intent; recovery asks Gitea for the marked review and
  never posts twice; with Gitea down, the item waits.
- Failures retried, then said on the PR until the next push.
- Startup refuses an account that can push to or administer any repo.
- nisse-core gitea: readable_repositories, open_pull_requests, diff,
  commits, archive, issue, search_issues, reviews, post_review; PR fields.
- Bring-up: scripts/review-bringup.sh; nisse-review.example.yaml.
- Tests: unit over a fake Gitea and scripted model; a live test
  (NISSE_REVIEW_GITEA_TOKEN) reviews a PR in a fresh test repository.
- Docs: Development Plan M7.3, Tuning Guide (tuning.review.*).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 22:34:02 +00:00
dongar fc71d3da75 Merge pull request 'M7.2: share the loop's parts' (#148) from share-loop into main
CI / checks (push) Successful in 2m12s
CI / e2e (push) Skipped
Reviewed-on: #148
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-13 21:52:34 +00:00
dongarandClaude Opus 5.5 f32ccf9881 M7.2: share the loop's parts
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 11m45s
CI / checks (pull_request) Successful in 2m21s
Shared parts, not a shared scheduler (agreed): nisse-review will have its
own small scheduler; a common one waits until two exist to compare.

- nisse_core.loop.driver: the driver run, moved; the agent names its
  waiting tools per run (nisse-dev's are WAITING_TOOLS), and execution
  goes to any Executor (ToolOutcome moves with it).
- nisse_core.loop.tools: the tool helper and the tools any agent offers
  (notes, memory, memory_done, ask_expert); nisse_core.loop.expert.
- nisse_core.knowledge.handlers: Knowledge, the vault and memory as
  control tools, the index and prompt text, out of LoopAgent.
- nisse_core.config: file reading, references, people, tuning sections of
  the agent's own, LLM numbers, the vault.
- New core tests: a driver run with a non-workspace executor and the
  agent's own waiting set; Knowledge alone; the shared config pieces.

Docs: the Development Plan marks M7.2 done; Architecture Part L and the
nisse-core README name the new parts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 21:31:29 +00:00
dongarandClaude Opus 5.5 e351d0a74e nisse-core: group modules into packages by architecture section
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m5s
CI / checks (pull_request) Successful in 1m37s
A pure move; behaviour unchanged.

- models (L9, L7): llm, catalog, probe, render, render_anthropic, context
- log (L7, L6): canonical, views
- state (L8): store (was state), reconcile, worker, cancel, dump
- knowledge (L10, L6.4): vault, memory
- conversation (L5): transport, matrix
- execution (L13): ssh (was execution), testing
- authority (P3, P4, L4): actions
- gitea, oplog, envfile stay at the top until each gains a second module.

Every import, monkeypatch target, script and doc reference rewritten.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 21:16:09 +00:00
dongarandClaude Opus 5.5 3013a83167 Part R: a push also resets the round count
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m15s
CI / checks (pull_request) Successful in 1m44s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 19:49:46 +00:00
dongarandClaude Opus 5.5 175ed0771d Spec: nisse-review (Part R) before nisse-ops
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m42s
CI / e2e (pull_request) Successful in 3m59s
A review agent comes second: it reads every ready PR in the repositories
it can see and discusses its points on the PR, never changing code.

- Architecture Part R: duty and items (R1), rounds capped and reset by
  anyone else commenting (R2), read-only reach bound to the item's
  repository, no approvals, no build VM for now (R3), peers (R4), local
  model with ask_expert, no Matrix room for now, own identity (R5).
- D5 and L11: a peer's review is constructive criticism, answered point by
  point; never an instruction, approval or go-ahead; nisse-dev doesn't
  wait for it before asking to merge.
- Part Q: letting nisse-review approve a limited subset of PRs.
- Development Plan: Phase 7 is nisse-core and nisse-review (M7.2-M7.6);
  nisse-ops moves to Phase 8 (M8.1-M8.5), with the MCP decisions so far.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 19:39:15 +00:00
dongar 873f1f5bf2 Merge pull request 'Architecture X3: avoid, not reject, search-then-dispatch meta-tools' (#145) from avoid-meta-tools into main
CI / checks (push) Failing after 2m46s
CI / e2e (push) Skipped
Reviewed-on: #145
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-13 19:18:48 +00:00
dongarandClaude Opus 5.5 e2d55e1caf Architecture X3: avoid, not reject, search-then-dispatch meta-tools
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m55s
CI / checks (pull_request) Successful in 1m52s
A special-purpose MCP server that works that way may still be worth it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 19:11:49 +00:00
dongar 5ea6e4610f Merge pull request 'M7.1: extract nisse-core' (#143) from extract-core into main
CI / checks (push) Successful in 2m32s
CI / e2e (push) Skipped
Reviewed-on: #143
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-13 19:01:58 +00:00
dongarandClaude Opus 5.5 154a0c3fa8 M7.1: extract nisse-core
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m42s
CI / e2e (pull_request) Successful in 4m0s
What isn't the dev agent's moves to nisse-core, unchanged in behaviour,
with its tests: canonical, render, render_anthropic, llm, catalog, probe,
context, views, state, reconcile, worker, cancel, dump, transport, matrix,
execution, testing, vault, memory, oplog, envfile and gitea (whole).

- nisse_core.state.StateStore holds the tables every agent has; an
  agent's subclass adds its tables (version, migrations), roles and
  views. nisse_dev.state.StateStore adds the build VM blocks (v5).
- views.project takes what each role is shown, rather than a fixed table.
- Vault takes the agent's name (agent=), for provenance.
- A test that nisse_core never imports an agent.
- The loop, config, git, delegation, expert, tools and evals stay in
  nisse-dev until nisse-ops has a loop to share.

Docs: the Development Plan marks M7.1 done and notes that Phase 6 waits on
the agent host and Terraform; Architecture Part L names the package.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 18:37:01 +00:00
dongarandClaude Opus 5.5 fe4548459f M5.3: scope memory
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m49s
CI / checks (pull_request) Successful in 1m57s
A project's notes have their own folder, memory/nisse-dev/projects/<project>/,
with its own index; remember and forget take scope (general, or project:
always the current item's own, so no item writes another project's notes,
and a conversation has none). At an item's start the transcript gets the
general index and its project's, never another's. Index links are pages
from the vault's root, as SilverBullet names them.

The closing pass's memory_done can name repository documents the item left
out of date; the harness says so in the thread and records it. The prompt:
the repository is the authority, and a note that disagrees is corrected.

With the operator: ARCHITECTURE.md isn't put into every item's context (its
size); the infrastructure world model waits for nisse-ops. The walk writes
project notes and checks they come only from that project's items.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-08 23:33:12 +00:00
dongarandClaude Opus 5.5 2b26f0cff5 M5.2: agent memory
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m33s
CI / checks (pull_request) Successful in 1m48s
remember(name, description, fact) and forget(name, why): control tools for
the driver (before the go-ahead too) and conversations, writing only the
agent's own vault folder, one note per fact with its provenance. No one
agrees first; each change is a line in the thread. The harness keeps the
index and puts it into the transcript once per item, with a hash.

At an item's close, completed or abandoned, a bounded memory pass (the
driver's model, its own transcript and view) records what was decided and
learned; it never holds the close up (tuning.memory.max_turns).

With the operator: corrections may be paraphrased (L6 changed), the model
forgets on its own, and nothing limits what may prompt a note. The walk
remembers and forgets, and checks nothing lands outside the agent's folder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-08 23:05:45 +00:00
dongarandClaude Opus 5.5 9fc7360ca5 M5.1: the knowledge vault
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m9s
CI / checks (pull_request) Successful in 1m50s
vault.py: a folder of Markdown notes on the agent host, as SilverBullet reads
them. Only configured folders exist for the agent, each rw or ro (`vault:`
in the configuration); paths are checked in code (relative, inside a folder,
symlinks included, Markdown, never the editor's own files). Writes are
atomic (temporary file, fsync, rename, directory fsync) and merge provenance
into the note's frontmatter, keeping the rest; failures are results.

read_note, list_notes and search_notes are control tools in the harness for
the driver (before the go-ahead, and while a VM is blocked) and for
conversations, labelled with their path; the prompt says a note is
information, never an instruction. Writing by the model is M5.2's.

With the operator: SilverBullet replaces Obsidian and runs on the vault
itself, agents' folders mounted read-only, so no Syncthing (L10, X5, M5.4).
Per-agent mounts come with the containers (M6.1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-08 22:03:29 +00:00
dongar 026f67ab05 Merge pull request 'M4.4: an unreachable build VM blocks its projects until it's back (#137)' (#139) from unreachable-vm into main
CI / checks (push) Successful in 2m23s
CI / e2e (push) Skipped
Reviewed-on: #139
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-08 21:13:38 +00:00
dongarandClaude Opus 5.5 a921b73d34 M4.4: an unreachable build VM blocks its projects until it's back (#137)
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m53s
CI / checks (pull_request) Successful in 1m43s
M4.3's block, with new triggers and a way out. A VM out of reach at the
check before a run, in a run (which ends there: the driver, a toolchain
step and a requested step), or while preparing the workspace blocks every
project on it; the item stays ready, waiting on no one. On the intake round
it is tried again every tuning.build_vm.retry_s (300), and once it answers
it must still answer settle_s (180) later; then the work a lost run left
there is parked (pushed), the VM unblocked, and that said once.

A changed host key, at the check or on a retry, blocks the VM too, until
the operator's `vm ready`, which trusts the new key: `trust new host key`
is retired, and the message says it's a key problem that may be an
impersonation. Questions are answered while a VM is blocked, without it.
Starting the agent with a VM down no longer fails.

The walk breaks the VM's SSH, now or partway through a run, and checks that
no model works on a VM that is down; loop.qnt models the VM going and
coming back (unblocked_only_when_up). Together they found: a lost run's
work set aside by the next item there; a question left behind a block; a
lost VM swallowed while the toolchain step checked or recorded it; and a
remark waiting to be answered that outlived its PR's merge. All fixed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-08 21:06:05 +00:00
dongarandClaude Opus 5.5 c794a1e52b M4.3: a broken build VM blocks its projects until it's ready
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m20s
CI / checks (pull_request) Successful in 1m38s
The driver can request_reimage(reason) when its build VM is broken past the
usual fixes (before the go-ahead too: it asks, it doesn't act). The harness
parks the work in progress (pushed to Gitea), then blocks the VM in the state
store (schema 5, vm_blocks): no item on any project on it is taken up, across
restarts. The operator hears once at top level, with the command to clear it,
and each blocked item's thread gets a line. `vm ready <project>` lifts the
block and trusts the VM's new host key; a reimaged VM is set up again by the
toolchain step, and the item that found it goes first.

loop.qnt gains the block (blocked_vm_runs_nothing) and a scenario; it found
that a reimage lost work in progress, hence parking first. The walk requests
reimages and recovers them, reimaged or not. The unreachable case reuses the
block in #137. Architecture P5 is softer, and D3 describes the block.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-08 19:27:28 +00:00
dongarandClaude Opus 5.5 d9f2ebd5aa M4.2: the toolchain step
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 11m13s
CI / checks (pull_request) Successful in 2m36s
The preflight becomes the toolchain step (D3, as #135 describes it): a
bounded run of the item's driver model, in a transcript of its own,
that makes the build VM and the project's files agree.

- Started by the harness when the VM isn't set up for the project
  (M4.1's checks): it sets the VM up from the repository and edits
  nothing there (no write_file; anything a command changed is reported,
  left for the work).
- Requested by the driver with toolchain_step(reason) once work is
  agreed: it installs, then brings pins, setup docs and CI
  configuration in step; its edits land in the current milestone. The
  driver gets the step's report as the tool's result, not its turns.
- D1 made structural: until the go-ahead the fast path refuses what it
  knows changes something (and anything dangerous), with a pointer to
  the restatement; unknown commands still go to the classifier, so a
  bug can be reproduced before it's restated.
- A `toolchain` actor and view; the walk asks for steps now and then.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 17:37:21 +00:00
dongar 97ea451629 Merge pull request 'Architecture: the toolchain step (D3), and only looking before the go-ahead (D1)' (#135) from toolchain-step-arch into main
CI / checks (push) Successful in 2m33s
CI / e2e (push) Skipped
Reviewed-on: #135
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-08 17:28:56 +00:00
dongarandClaude Opus 5.5 66c8ded409 Architecture: the toolchain step (D3), and only looking before the go-ahead (D1)
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m1s
CI / checks (pull_request) Successful in 1m54s
D3's preflight and install recording become the toolchain step: a
bounded run of the item's driver model that makes the build VM and the
project's own files (pins, setup docs, CI configuration) agree, with
the repository as the record. The harness starts it when the VM isn't
set up for the project (and then it edits nothing in the repository);
the driver requests it once work is agreed, and its edits land in the
current milestone. Versions stay facts the harness reads itself; a
rebuilt VM's new host key is trusted only on the operator's word.

D1 gains the rule that until the go-ahead the agent only looks:
anything that changes something is refused by the fast path, so what
the work needs goes in the restatement.

The Development Plan's M4.2 follows: the toolchain step, in place of
recording installs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 17:18:30 +00:00
dongarandClaude Opus 5.5 5e1086c501 Expert and integration roles on claude-haiku-5-5
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m58s
CI / checks (pull_request) Successful in 2m6s
claude-haiku-4-5 is being replaced by claude-haiku-5-5 in LiteLLM: the
integration tests' roles and the example config's expert move to it.
Its profile keeps the predecessor's benchmark figures, marked as such
until it's measured. The plan's models-by-role line also gains the
local classifier (M3.2). Results measured on 4.5 stay as they were.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 16:38:06 +00:00
dongar b6eaceefa2 Merge pull request 'M4.1: toolchain preflight' (#133) from preflight into main
CI / checks (push) Successful in 2m43s
CI / e2e (push) Skipped
Reviewed-on: #133
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-08 16:29:41 +00:00
dongarandClaude Opus 5.5 9968a0922a M4.1: toolchain preflight
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 10m33s
CI / checks (pull_request) Successful in 2m59s
A model works out and sets up what a project needs on its build VM;
code makes sure that happens, bounds it and checks the result (D3).

- Before each driver run on a project, one round trip checks the build
  VM: the harness's own baseline (missing: the item waits, a template
  problem), the workspace clone (missing: cloned again and said; the
  item resumes from what it pushed) and a per-project marker.
- No marker (a new project, a new or wiped VM): the item's driver model
  runs a bounded preflight first, with its own prompt, the execution
  tools (screened as ever) and toolchain_ready. The harness runs each
  reported version command itself (version-shaped commands only, even
  from the marker, which the model's commands could edit) and writes
  the marker. Gaps and an unfinished preflight are facts and work goes
  on; the thread hears only of installs and gaps. A rebuilt VM starts
  from the last toolchain.
- Later runs check the versions again; a change is a fact and a thread
  line. The PR body says what the work was tested with.
- A changed host key is its own failure (HostKeyChanged): the item
  waits for the operator's `trust new host key`, which drops the pin;
  then the VM is set up again.
- A prompt line: install what the work needs on the build VM, say why,
  and put lasting needs in the project's own pins.
- tuning.preflight: max_turns, command_timeout_s.
- The property walk gains a VM wipe and ready_before_work (checked
  against a mutant that skips preflight); parked_work_safe accepts that
  a wipe loses what wasn't pushed (D3).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 15:43:30 +00:00
dongar 4564cd6f3f Merge pull request 'Say a local commit publishes nothing (#129)' (#132) from commit-note into main
CI / checks (push) Successful in 2m18s
CI / e2e (push) Skipped
Reviewed-on: #132
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-08 14:42:24 +00:00
dongarandClaude Opus 5.5 c4726d5673 Say a local commit publishes nothing (#129)
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m15s
CI / checks (pull_request) Successful in 1m54s
The local driver sometimes ran `git commit` itself instead of calling
finish_milestone, then told the operator a PR was waiting when none had
been opened. A commit still runs (it's contained, and publishing takes
whatever is there), but when the driver's command commits anywhere in
its line, the harness adds to its result: committed locally only;
nothing is published until finish_milestone, and there is no PR until
then. Structural, at the moment the model would draw the wrong
conclusion (P6), rather than left to it noticing.

Closes #129

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 14:28:59 +00:00
dongarandClaude Opus 5.5 ab469b60bb M3.3: the trust dial (the agent merges approved PRs)
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 11m23s
CI / checks (pull_request) Successful in 2m29s
A project's trust.merge is operator (the default) or approved (L3, D5,
#65). Set to approved, the harness, never the model, merges a PR in the
PR watch once an approving review from a gitea.operators account is on
its current commit, its checks pass (none at all counts; pending
waits) and it's mergeable; the item then goes on as after any merge.
Each reason to hold off (failed checks, conflicts, a refused merge) is
said once per commit.

Gitea enforces it: protection requires approvals from an operators-only
whitelist and dismisses stale ones. At start, for each approved
project, the agent checks by behaviour that its own unapproved merge is
refused, with a throwaway branch and PR made through the API (never the
workspace); if it isn't refused, the operator merges until a restart.

- gitea: approvals, combined check status, a PR's head commit and
  mergeability, API-made branches and their deletion.
- Test and bring-up repositories get the protection.
- Tests: the decision (unit), the hard wall on a real Gitea
  (test_trust_live), an e2e test where the operator only approves, and
  a walk property, merges_only_approved, checked against a mutant that
  ignores which commit was approved.

Closes #65

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 13:48:16 +00:00
dongarandClaude Opus 5.5 089af0c391 M3.2: the action classifier
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m20s
CI / e2e (pull_request) Successful in 12m31s
What the fast path calls unknown is judged by an independent model call
on the item's classifier role (L4), before it runs.

- nisse_dev.loop.classifier: it sees the action and the agreed scope
  (project, repository, issue number, the restatement the operator said
  go to), built in code; never the transcript, the issue's text, tool
  output or the driver's reasoning. Its verdict is a tool call: safe
  runs; in_scope runs and is reported in the thread; needs_discussion
  asks, as a dangerous action does. An error, no verdict or an unknown
  one fails closed. Verdicts are cached per item, agreed revision and
  action. A helper is refused what needs discussion.
- action_verdict gains source, model and the classifier's exact input;
  details shows the source. An allow after the classifier asked is an
  override. scripts/classifier-evals.py exports the judgements from a
  store, read-only; tests/integration/classifier_cases.yaml is the seed
  set, run per model with --capability-models.
- The fast path's delete rule: deleting on the build VM is contained;
  only what breaks the host (system and home directories) stays
  dangerous, since in tests the build VM is the test machine.
- The classifier role is the local model in the example config.
- The property walk gains a classifier answering at random, junk and
  failures included; dangerous_only_when_allowed now covers its
  verdicts (checked against a mutant that runs them all).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 12:09:38 +00:00
dongar 5044af371b Merge pull request 'M3.1: action classes and the fast path' (#127) from action-classes into main
CI / checks (push) Successful in 2m9s
CI / e2e (push) Skipped
Reviewed-on: #127
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-08 11:25:48 +00:00
dongarandClaude Opus 5.5 ea4376eaa1 M3.1: action classes and the fast path
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 10m30s
CI / checks (pull_request) Successful in 2m10s
Every execution tool call, the driver's and a helper's, gets a verdict
from code before it runs (P3, P4, D6).

- nisse_core.actions, nisse-core's first code: Action and Verdict, and
  reading shell commands statically, segment by segment (cd, wrappers,
  sh -c). What can't be read that way is unknown; a line is as bad as
  its worst command.
- nisse_dev.loop.actions, the rule table: reading, checks and tests are
  harmless; workspace edits, local git and installs on the build VM are
  contained; pushing, other hosts, uploads, publishing, the forge's own
  tools, deleting outside the workspace and wrecking the VM are
  dangerous; changing branches by hand is refused (the harness owns
  branches) with a pointer to the milestone tools.
- A dangerous call isn't run. The item waits on the operator with a
  question rendered from the action, and `allow` lets exactly that
  action run once. A helper can't ask, so it is refused and told to
  hand back. Unknown actions run, logged, until the classifier (M3.2).
- Every verdict is an action_verdict event, which no model sees, and an
  oplog line; `details` shows the counts and every action not simply
  run.
- The property walk gains dangerous_only_when_allowed (checked against
  a mutant that doesn't use the allow up); the slice e2e checks every
  call was judged and none needed an allow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 04:32:13 +00:00
dongarandClaude Opus 5.5 8840facceb CI: drop the temporary event job
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 4m33s
CI / checks (pull_request) Successful in 1m50s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 03:41:17 +00:00
dongarandClaude Opus 5.5 b80225d821 CI: tell e2e:full label events by the PR's labels
CI / checks (push) Skipped
CI / event (pull_request) Successful in 3s
CI / e2e (pull_request) Skipped
CI / checks (pull_request) Skipped
Gitea leaves github.event.label empty.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 03:40:36 +00:00
dongarandClaude Opus 5.5 8bfc1e5f24 CI: Gitea's label events are label_updated in the payload
CI / checks (push) Skipped
CI / e2e (pull_request) Canceled after 27s
CI / event (pull_request) Successful in 2s
CI / checks (pull_request) Successful in 1m50s
Temporarily prints the event, to check what Gitea sends.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 03:38:05 +00:00
dongarandClaude Opus 5.5 9c181d973a Development Plan: superseded runs are cancelled by CI now
CI / checks (push) Skipped
CI / e2e (pull_request) Canceled after 0s
CI / checks (pull_request) Canceled after 1m33s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 03:36:19 +00:00
dongarandClaude Opus 5.5 ef29a1271e CI: a newer run cancels the one it supersedes
CI / checks (push) Skipped
CI / e2e (pull_request) Canceled after 0s
CI / checks (pull_request) Canceled after 23s
Workflow-level concurrency, per PR or branch, with cancel-in-progress.
The earlier attempt (job-level, on the e2e job) didn't cancel anything;
Gitea documents the workflow-level form. A label other than e2e:full
starts a run whose jobs all skip, so it gets a group of its own and
can't cancel the real run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 03:36:02 +00:00
dongarandClaude Opus 5.5 6ae2375194 M2.8: switch over to the new loop
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 10m51s
CI / checks (pull_request) Successful in 2m16s
nisse-dev now runs only the conversational loop. The gated state machine
it was built alongside is gone:

- Removed control.py, planner.py, executor.py, approvals.py, gates.py
  (command_form moves to loop/reader.py), publish.py and poller.py, the
  old assembly in agent.py (its shared helpers move to loop/build.py),
  the store's state-machine view (get_state, transition), their tests,
  the old end-to-end and live planning/milestone/gate tests, and
  specs/orchestrator.qnt with its scenarios.
- Roles are driver, helper, expert and classifier; driver is required.
  loop:, roles.planner, roles.executor and tuning.executor are refused
  with what to use instead; tuning.driver takes the turn cap and failure
  limit.
- The benchmark and the capability matrix measure the driver: the
  loop's own system prompt and tools, the task's plan as the agreed
  restatement. Task plans end with finish_milestone, so earlier runs
  show as (old); stored runs' executor tuning reads as the driver's.
- A delegation on the loop records what its helper's alias resolved to
  (L9), as the old path's did.
- The dump shows a work item's revision and what it waits for.
- Behaviour the old tests covered and the loop shares stays covered:
  driver runs that fail on a model call, stop mid-turn, reset the
  failure count; go-aheads ignoring case and punctuation; a request too
  big to fit is never sent; the assembly tests, now on build_loop_agent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 03:12:58 +00:00
dongar 09232dcbdd Merge pull request 'Fixes from the rest of the live session' (#122) from live-findings into main
CI / checks (push) Successful in 2m45s
CI / e2e (push) Skipped
Reviewed-on: #122
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-08 02:36:51 +00:00
dongarandClaude Opus 5.5 0064ec7696 Fixes from the rest of the live session
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m13s
CI / e2e (pull_request) Successful in 3m22s
- A near miss for abandon ("abort", "cancel", "drop it", ...) in an item's
  thread is asked about, and changes nothing.
- The driver is told only the operator stops or closes an item, so it
  doesn't claim to have done either.
- An issue assigned again after its item closed starts a new item that
  notes the one before (Gitea's timeline gives the assignment time);
  item_for_issue returns the latest item.
- Abandoning an issue Gitea won't close because of open dependencies says
  so, naming them.
- An agreed item isn't offered restate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 02:16:51 +00:00
dongarandClaude Opus 5.5 659915d309 Fixes from the first live session of the new loop
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 10m38s
CI / checks (pull_request) Successful in 2m33s
Found live on 2026-10-07 (bring-up on nisse-testing-operator/nisse-bringup,
Opus as driver):

- The driver's prose nudge went into the system prompt, so the retry ended
  on the model's own reply. Opus 5.5 refuses that ("does not support
  assistant message prefill. The conversation must end with a user
  message"). The nudge is now a harness fact in the conversation. The
  delegation harness's prose path did the same and is fixed likewise.
- A helper's first request ended with its own delegation_started event,
  rendered as an assistant message: every delegation would have failed on
  Opus. The helper now sees it as the harness handing over its task.
- model_contract checks that every request ends on the user's side, so
  scripted tests catch this from now on (each fix is mutation-checked).
- `status` (and `details`) in an item's thread were taken as replies: they
  woke the driver and cancelled a bug's countdown. They now answer at once
  and change nothing.
- The driver had no way to answer a remark without changing the item, so it
  answered in prose. Every run woken by a message (or a review comment) now
  offers `reply`, which answers where asked and returns to the very wait
  that was interrupted.

Also: scripts/bringup-repo.sh makes the bring-up's test repository
(make_repo/delete_repo, split out of fresh_repo), and made repositories get
the agent's labels (bug, discuss, design).

Tests: test_loop_live_fixes.py (one per finding); the walk sends `status`
into threads (status_changes_nothing) and its scripted driver sometimes
answers remarks with reply.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-08 00:02:50 +00:00
dongarandClaude Opus 5.5 d7e4fd0938 M1.5 accepted: the tests run over a published image
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 2m54s
CI / checks (pull_request) Successful in 2m10s
- The Dockerfile's test stage stacks on TEST_BASE: this build's agent by
  default, or a published image. It adds only the test tools (uv export
  of the dev group into the image's environment), never reinstalling the
  agent, so the suite tests the image as published.
- scripts/e2e.sh --image passes NISSE_E2E_MODEL into the container.
- The plan marks M1.5 done: v0.1.0's published image passed the whole e2e
  suite (9 of 9, 11m37s), with the release's own tests over it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 23:09:08 +00:00
dongarandClaude Opus 5.5 28092194fe E2E on qwen3.8-27b without thinking
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 3m6s
CI / checks (pull_request) Successful in 2m14s
The e2e roles move to qwen3.8-27b-nothink, a LiteLLM alias for the same
model with chat_template_kwargs enable_thinking false. With thinking, a
growing share of answers ran to 2-8k tokens (two hit the 8192 cap), which
took one test from 3.5 to 20 minutes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 22:49:13 +00:00
dongarandClaude Opus 5.5 ff5a7a644f CI: install sshd from a fast mirror, or not at all
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 9m32s
CI / checks (pull_request) Successful in 2m10s
archive.ubuntu.com has been crawling (a few KB/s), taking the sshd install
in every job from 8 seconds to 3.5-9.5 minutes (another agent measured it;
the runner and inference-4090 were healthy). scripts/ci-sshd.sh skips the
install when the image has sshd, and otherwise points apt at
mirrors.edge.kernel.org first (both source layouts, 22.04 and 24.04).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 22:29:41 +00:00
dongarandClaude Opus 5.5 e35aea5045 Test levels: smoke e2e on PRs, the full set on e2e:full and before a release
- Every PR: lint, unit tests across the runner's cores (pytest-xdist), 16
  property walks from a seed that moves with the run number, integration
  tests, and the end-to-end smoke tests (the slice, a conversation filed
  as work, a question; `smoke` marker).
- A PR labelled e2e:full, a push to main, or a manual run: all 48 walks and
  the whole end-to-end suite. Adding e2e:full to a PR runs it; any other
  label runs nothing.
- A published release runs the full set before its image is built.
- The walk's seeds come from NISSE_WALK_SEEDS / NISSE_WALK_FIRST (48 from 0
  by default).

Measured here: the unit tier on 2 workers took 3m49s against 6-8 minutes
serially.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 22:29:41 +00:00
dongarandClaude Opus 5.5 ad8a07fcb7 Wait out LLM outages with backoff (L5: outages delay, never fail)
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 30m8s
CI / checks (pull_request) Successful in 6m14s
A 502, 503 or 504 from the LiteLLM proxy, or a dropped connection, is
retried after 5, 15 and 45 seconds before the call fails. Twice on
2026-10-07 a short burst of 502s stopped work outright: the integration
tier at 16:39 and an e2e conversation at 20:43, where the item waited on
the operator after one refused call.

Not retried: a 500 (it may be the request's fault), a timeout (it already
had its request_timeout_s), or a budget refusal. Rate limits keep their
single 20-second retry. The time spent waiting is reported with the rate
limit's (last_rate_limit_wait_s), so it isn't counted as the model's.

Tests: an outage waited out, one that outlasts the backoff, a dropped
connection, a 500 and a timeout not retried, a budget refusal not waited
on. The offline agent in test_agent_build skips the backoff (its proxy
doesn't exist).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 21:11:45 +00:00
dongarandClaude Opus 5.5 6af54790a9 CI: no image build on PRs; images are built only on release
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 34m0s
CI / checks (pull_request) Successful in 12m10s
The build-only image job on every PR goes: images are built when a
release is published (release.yml), so PRs don't wait on it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 21:00:21 +00:00
dongarandClaude Opus 5.5 2766cf1e8e Release: the dev organization's registry, pushed by registry-push
CI / checks (push) Skipped
CI / image (pull_request) Successful in 21s
CI / e2e (pull_request) Canceled after 1m27s
CI / checks (pull_request) Successful in 6m28s
The repository moved to the dev organization: images go to
gitea.dongar.net/dev/nisse-dev, pushed by the registry-push bot with the
organization's REGISTRY_TOKEN.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 20:24:51 +00:00
dongarandClaude Opus 5.5 4d42040a34 Release: build on a published release, as in inlaw_outlaw
- on: release (published), not on any v* tag: only a deliberate release
  builds an image; the version is its tag without the v, plus latest.
- docker/login-action, setup-buildx and build-push-action, as the runner
  already serves them for inlaw_outlaw; the version as a build argument
  and image label.
- Comments on why the registry needs a personal token (the job token's
  empty package scope) and that packages belong to the repository owner.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 20:14:56 +00:00
dongarandClaude Opus 5.5 0660fa3c51 The agent as a container image (M1.5)
CI / checks (push) Skipped
CI / e2e (pull_request) Failing after 49m48s
CI / checks (pull_request) Successful in 10m0s
CI / image (pull_request) Successful in 27s
- Dockerfile: built with uv into /opt/nisse (installed, not editable),
  run on python:3.12-slim with openssh-client as user nisse; stateless,
  with state in a /var/lib/nisse-dev volume, config mounted at
  /etc/nisse-dev/nisse-dev.yaml, secrets as environment variables, keys
  mounted read-only. 202 MB. Its `test` stage adds the tests and their
  tools over the installed package (UV_NO_SYNC, so `uv run` never swaps
  in the source tree); the default target is the agent.
- scripts/e2e.sh --image REF runs the suite inside an image's test stage,
  on its own sshd.
- CI builds both images on every PR (nothing pushed); release.yml pushes
  gitea.dongar.net/dongar/nisse-dev:<tag> and :latest on a v* tag, with
  the REGISTRY_TOKEN secret.
- scripts/bringup.sh builds the code under test into an image and runs it
  in the systemd unit (root, $E2E mounted at its own path); `plan` prints
  what it would run; the build-VM set-aside reads the projects: config.

Checked: the image refuses to start without a config, runs as uid 10001;
one e2e test passed from the test stage (2m45s, $0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 19:50:59 +00:00
dongarandClaude Opus 5.5 8994b3fb4b Conversation on the new loop (M2.7)
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 38m44s
CI / checks (pull_request) Successful in 13m26s
- Questions never change state (L5, #77): a message ending in "?" to a
  waiting project item starts a question run (reply and look-only tools,
  an instruction to change nothing), and the item goes back to the very
  wait it was in: a restatement up stays up and a go binds to it; the
  agent's own question stays open.
- Explanations: a restatement gives its reasons and the main alternative;
  a revision says what changed (restate(changed), sent back without it);
  a milestone's summary says what was done, verified and left open.
- Progress in prose (#42): the driver's and a helper's own words for a
  turn are posted, trimmed, at most once a minute per item; `details`
  shows the last tool calls; a message arriving mid-run is acknowledged at
  once ("Noted; ...").
- CI: the e2e job's comments and the plan no longer claim a newer push
  cancels the run it supersedes (Gitea here doesn't); the concurrency
  block goes.

Tests: conversation unit tests (questions in a go-ahead and an answer wait,
an unanswerable question, revisions, progress rate and trimming, details,
mid-run notes), the helper's progress, the walk asking questions in every
state with a questions_change_nothing property (routing questions the old
way fails 32 of 48 seeds), and an e2e question while a restatement is up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 18:01:51 +00:00
dongarandClaude Opus 5.5 ba001a9cec CI: run the e2e job only after the checks pass
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 21m52s
CI / checks (pull_request) Successful in 5m48s
A PR that's already red doesn't spend ~25 minutes of the RTX 4090.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 16:40:49 +00:00
dongarandClaude Opus 5.5 59fd00ce8c Roles, capability profiles and help on the new loop (M2.6)
CI / checks (push) Skipped
CI / e2e (pull_request) Canceled after 21m42s
CI / checks (pull_request) Failing after 5m23s
- roles.driver (unset: the planner's model) and roles.classifier (unset:
  the expert's); the mapping is pinned when an item opens, and the driver
  and the go-ahead reader run on the item's own (L9).
- profiles: in configuration (tools, strengths, weaknesses), used in
  briefings: the driver's instructions say what its helper and expert are
  good at, the helper's brief who reads its handback; never a model name.
- delegate(task) and ask_expert(question) for the driver. A helper works
  on a branch off the milestone's; on a handback its work comes back as
  uncommitted changes, otherwise it is discarded, so harness branches
  never hold commits only the clone has. ask_expert keeps its budget.
- Triggers: failure_limit failed commands in a row (Driver's new "stuck"
  outcome) or the turn cap, mid-milestone, call a helper in once per
  milestone; if it fails too, the item waits on the operator. A
  delegation cut off by a crash is discarded at boot.
- Evaluation capture: delegations and ask_expert as before, and the
  reader's verdicts as classifier pairs.

Tests: help tests (merge-back, discard, needs a milestone, the stuck
trigger and its once-per-milestone limit, the expert and its budget,
pinning, briefings by profile, boot discarding a cut-off delegation),
config tests for roles and profiles, and the walk delegating with a
scripted helper that sometimes fails, with a delegations_settle property.
The walk found helper commits stranded on a merged PR's branch, fixed by
returning a helper's work uncommitted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 16:33:26 +00:00
dongarandClaude Opus 5.5 88c7d2e8c9 A design milestone holds documents only, enforced
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 24m22s
CI / checks (pull_request) Successful in 5m4s
Found by the first local e2e run: qwen3.8-27b put a new project's code in
the same PR as its ARCHITECTURE.md, with no build plan. D1 says a `design`
item agrees its design before any code, and that rested on wording only.

- Until a PR of a `design` item has merged, finish_milestone is sent back
  if the milestone changes anything but ARCHITECTURE.md and docs/, naming
  the paths to take out. The merge is recorded (design_merged) and code
  may follow.
- The label's instruction and the new project's brief say so plainly:
  documents first, in a PR of their own; code after it has merged.

Tests: a design milestone with code is sent back and only the documents
are published, then code after the merge; the walk's model follows the
rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 15:39:00 +00:00
dongarandClaude Opus 5.5 ad2d090143 New projects from conversation (M2.5, PR B)
- propose_project(name, purpose, decisions) in a conversation goes up for
  a go-ahead (checked: a slug, not an existing project, no trailing
  question).
- On go, the provisioning checklist (D1): a repository with a README,
  protection on its default branch, the agent as a collaborator, a build
  VM, a deploy key, and the project entry as YAML with <...> to fill in.
- On "done", the agent reads its configuration again and adds the new
  project live (build_loop_agent(load=...); only additions), then checks
  what was provided (D4, verified, not assumed): permissions and
  protection as it sees them, then on a throwaway branch with an empty
  commit, a branch push must succeed and a push to the default branch and
  merging its own PR must be refused. The check's PR is closed and its
  branch deleted. Failures are said per item; nothing starts.
- Checked: the tracking issue (the distilled decisions, carrying the new
  item's ID), and the project's item, agreed and labelled `design`, whose
  first milestone is ARCHITECTURE.md and the build plan. A conversation
  waiting on provisioning doesn't close as idle.
- The walk replays exactly from its seed (item IDs too), and event IDs
  stay unique across simulated restarts, as a server's are.
- The test config holds output to 8192 tokens, under the test key's
  per-request cost cap.

Tests: new-project unit tests (checklist and its YAML parse as config, done
before the entry, what isn't set up, a checked project's record and first
PR, an existing name), protection enforced by a hook on the fake forge,
the walk proposing projects and saying done, and an e2e new project in a
repository the test operator provisions from the agent's checklist.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 15:39:00 +00:00
dongar 9a86ccb2a4 Merge pull request 'End-to-end tests on a local model, and in CI on every PR' (#110) from e2e-local-model into main
CI / e2e (push) Skipped
CI / checks (push) Successful in 5m26s
Reviewed-on: dongar/nisse#110
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-07 15:38:45 +00:00
dongarandClaude Opus 5.5 6db80dfb4b End-to-end tests on a local model, and in CI on every PR
CI / checks (push) Skipped
CI / e2e (pull_request) Successful in 17m27s
CI / checks (pull_request) Successful in 5m26s
- Every e2e role runs on qwen3.8-27b on the RTX 4090 (e2e_roles();
  NISSE_E2E_MODEL overrides, e.g. claude-haiku-4-5). The first full run
  passed 7 of 8 in 26 minutes for $0.0000; the eighth was a real finding,
  fixed with the new-project PR.
- scripts/e2e.sh reports a run's spend on the test key and the day's total
  against its budget: above $0 on the local model means a paid fallback.
- CI runs the suite as its own job on pull requests (not pushes to main),
  on a throwaway sshd (--local), cancelling a run a newer push supersedes,
  so the checks job still reports in minutes.
- The plan says so: e2e on every PR; locally only -k / --lf.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-07 15:14:43 +00:00
dongarandClaude Opus 5.5 679ace1fd8 A repository per test run instead of the shared sandbox
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 5m17s
Every live test that touches a repository gets one of its own from
e2e_support.fresh_repo, made by the test operator as an operator makes a
project's (D4, D8): seeded from tests/integration/seed/ (the sandbox's
package, without past runs' leftovers), the agent's account a collaborator
with write access, main protected (pushes only by the owner, an approval to
merge), and a deploy key generated for the run. It is deleted afterwards
(NISSE_E2E_KEEP_REPOS keeps it); scripts/e2e-cleanup.sh deletes leftovers.

- Both e2e fixtures use it; per-run base branches are gone. The merge
  helper approves first, as the protection now requires.
- test_gitea_live uses it, so CI needs NISSE_OPERATOR_TOKEN and no longer
  writes the sandbox deploy key (NISSE_PUSH_KEY).
- A live test of the helper: seeded, shared with the agent, protected
  against it, deleted.
- nisse-test.yaml's project entry is a placeholder; pytest and ruff skip
  the seed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-06 05:10:27 +00:00
dongarandClaude Opus 5.5 3f54896f65 Conversations on the new loop (M2.5, PR A)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 5m14s
- A top-level message that isn't a command is a conversation: an item in
  the `general` scope (L1), threaded under the operator's message, with
  `reply`, `ask_human` and `propose_work` and no workspace or execution.
  `general` is reserved as a project name.
- propose_work(project, title, summary) goes up as a revision for a
  go-ahead (or a plain yes). On go, under a file_issue intent (L8), the
  record issue is opened in that project, assigned to the agent and
  carrying the new item's ID; its item starts agreed, with the summary as
  its brief. Intake skips any issue carrying an ID (D1); boot adopts a
  filed issue or puts the proposal up again.
- A conversation closes after tuning.loop.general_idle_s (default a day);
  a reply in its thread then starts a new conversation that continues it.

Tests: conversation unit tests (threading, filing, the marker, an unknown
project, changes, idle close and continuation, a plain yes, both crash
cases, commands at top level); the walk with conversations, proposals and
idle time, and records_have_one_item / conversations_do_no_work; an e2e
conversation that becomes filed work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-06 04:26:40 +00:00
dongarandClaude Opus 5.5 6aa1439a7f Entry-point test: a config with no projects is named in the startup errors
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 5m28s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-06 02:36:06 +00:00
dongarandClaude Opus 5.5 064d9bf554 Projects as configuration entries (M1.4, D8)
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 5m7s
- Config: `projects: {name: entry}` with repo, base_branch, workspace,
  build_host, push credential, labels (defaults added to every issue),
  secrets by ${VAR} reference with visibility (commands | harness), and
  egress (recorded, not enforced). Every problem in every entry is
  reported at once; an inline secret, a bad visibility, a repeated repo or
  a non-slug name is an error.
- The old loop runs exactly one project (only_project) and refuses more.
- The new loop is per project: each its own clone, workspace (with its
  commands-visible secrets, redacted on return), push credential and
  milestones; intake polls every project's repository; items are scoped
  to their project for every run, publish and PR watch. Issues are named
  project#14 when there is more than one project, and a bare #14 that
  matches two is asked about. Switching projects parks the one worked on
  last (all others after a boot).
- A restatement that ends in a question is sent back to the driver, not
  posted: the e2e driver kept asking "Is this the right interpretation?",
  which a plain yes can't answer.

Tests: config entries and errors, two-project assembly, two-project loop
tests (intake, own workspaces and repos, #14 in both, do-next, label
defaults, parking across projects), the property walk over two projects
with a new own_repository_only property (an item using the first
project's repo fails 15 of 16 seeds), and the restatement check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
2026-10-06 01:12:57 +00:00
dongar 4ecff19933 Merge pull request 'Architecture: the general scope and the new-project sequence (for M2.5)' (#104) from arch-general into main
CI / checks (push) Successful in 3m41s
Reviewed-on: dongar/nisse#104
Reviewed-by: Don Garrett <dgarrett@acm.org>
2026-10-06 00:29:48 +00:00