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
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
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
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
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
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
Development Plan M0.1. Every section reference in code, tests, specs/,
the example config, the tuning guide, and the CLAUDE.md and README files
now cites docs/Architecture.md (P, L, D, O, X, Q) instead of the old
spec's A-H, and docs/Architecture-Build-Spec.md is deleted (git keeps
it). The plan's old-to-new table goes with it.
Mostly mechanical, with a per-site choice where one old section split:
A4 became L2 where it meant stop/abandon/the loop and L3 where it meant
approvals; A10 became L6 in the context-budget code and L7 elsewhere; B2
became D4 for outbound Gitea and publishing and D5 for review intake;
'A3 step N' became the matching step of L8's boot sequence. Comments
describing behaviour the architecture drops (planning always escalating,
thresholds tuned at an old build step) now describe the current code and
point at the Development Plan.
docs/Architecture.md gains one line the rewrite missed: intake outages
delay but never fail work (old B2), now in L5, where the poller and
Matrix code cite it.
Comments and docs only; no behaviour change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
- uv workspace tying nisse-core, nisse-dev, nisse-ops together
(src layouts, workspace-sourced nisse-core dependency)
- Placeholder hello-world entry points for both agents, calling
through nisse-core to exercise the package boundary
- pytest unit tests per package; integration tests (marker-gated)
running the installed console scripts as subprocesses
- ruff lint + format, nisse_* declared first-party
- Gitea Actions workflow running lint, format check, unit and
integration tests; scripts/check.sh mirrors it locally
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nisse-core's history becomes the repo root; the spec moves to docs/ at
top level; nisse-dev and nisse-ops fold in as package directories, their
seed histories (pointer files only) discarded. Pointer READMEs and
CLAUDE.mds are rewritten for in-tree paths.
Rationale: for a solo operator the three-repo split tripled admin
surface (tokens, CI, webhooks, permissions, backups) and imposed a
version-pinning contract on nisse-core, while the repo boundary was
never a trust boundary — the VM is. The spec's Part H is updated in the
next commit to record the decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>