Write the agreed dev workflow into the architecture
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m19s

Issues assigned to dev-agent are the intake; the agent restates its
understanding and either proceeds (bug label) or waits for a go-ahead.
Labels steer and are never required. Build plans and ARCHITECTURE.md
are working documents reviewed in PRs, not gates; scope is the issue
and its conversation. Work items can be blocked on Gitea dependencies,
each gets a Matrix thread, milestones are started and finished by
control tools, and the next milestone waits for the previous merge.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-02 18:26:02 +00:00
1 parent 3e7edce6dc
commit cd2ff6d769
1 file changed
+170 -74
+170 -74
View File
@@ -93,12 +93,13 @@ Every action any agent can take falls into one of three classes:
| Class | What it is | Examples | Rule |
|---|---|---|---|
| **1. No side effects** | Observes; changes nothing outside a disposable sandbox | Reading files, querying APIs, searching, viewing logs and metrics, running a build or tests in a throwaway environment | Always allowed |
| **2. Contained side effects** | Changes something, but the change stays within the agent's own area or has a known undo | Editing the workspace, committing to the agent's branch, opening a PR, writing the agent's own notes, starting a test container; for ops, a step with a known undo inside an approved plan | Allowed when in scope; logged and reported |
| **3. Dangerous** | Irreversible, visible beyond the agent's own area, or outside what was agreed | Merging to a default branch, deleting data, changing a firewall, installing software on a host the agent doesn't own, contacting third parties, spending beyond a budget | Needs discussion, unless an approved plan names the exact action |
| **2. Contained side effects** | Changes something, but the change stays within the agent's own area or has a known undo | Editing the workspace, committing to the agent's branch, opening a PR, writing the agent's own notes, starting a test container; for ops, a step with a known undo inside an approved step sequence | Allowed when in scope; logged and reported |
| **3. Dangerous** | Irreversible, visible beyond the agent's own area, or outside what was agreed | Merging to a default branch, deleting data, changing a firewall, installing software on a host the agent doesn't own, contacting third parties, spending beyond a budget | Needs discussion, unless an approved step sequence (O2) or the operator in conversation names the exact action |
**"In scope"** means within what the operator agreed to: the work item's task,
an approved plan, a pre-approved milestone (D2), or an approved step sequence
(O2). An action can be class 2 in one work item and class 3 in another.
**"In scope"** means within what the operator agreed to: the work item's task
and what was agreed in its conversation (for `nisse-dev`, the assigned issue,
D1), or an approved step sequence (O2). An action can be class 2 in one work item
and class 3 in another.
**Move actions down the classes wherever it is cheap.** Reversibility is designed
in, not hoped for (P5).
@@ -191,7 +192,7 @@ Every agent's work is organised into three kinds of unit.
| Concept | What it is | Lifetime | `nisse-dev` | `nisse-ops` |
|---|---|---|---|---|
| **Duty** | A standing responsibility that produces work items | Recurring | None yet; the operator is the only source | Scheduled checks, log monitoring, alert intake |
| **Duty** | A standing responsibility that produces work items | Recurring | Gitea issues assigned to `dev-agent` (D1) | Scheduled checks, log monitoring, alert intake |
| **Work item** | One effort, opened and eventually closed | Opened → closed | Building a feature in a repo | Diagnosing a failure; making one change |
| **Scope** | What a work item operates on, and the context that persists across work items there | Indefinite | A repo | A site and service, e.g. `garrett-hall` Proxmox |
@@ -217,6 +218,22 @@ Every agent's work is organised into three kinds of unit.
- **Scopes hold context shared across work items** (L6): for a repo, its documents
and conventions; for infrastructure, the world model of that site or service.
**Work item states.**
| State | Waiting for |
|---|---|
| **active** | nothing; it is being worked on |
| **ready** | its turn |
| **waiting on the operator** | an answer, a review or an approval |
| **blocked** | something else to finish, e.g. another issue to close |
- **Blocked is read from external truth,** such as a Gitea issue dependency (D1),
and becomes ready only when that dependency actually closes, never by a model's
judgement (P6). The operator can unblock by removing the dependency.
- **An agent that finds a blocker mid-work** says so and proposes recording it
(e.g. "blocked by #12"); with the operator's agreement it records it, parks the
item as blocked and moves on.
**Park-and-switch: one active work item per agent.**
- Other work items are *parked*, with their full position held durably.
- **Switching happens only at safe points,** when the active item is waiting on
@@ -225,10 +242,12 @@ Every agent's work is organised into three kinds of unit.
item (one it can act on without the operator) when the active item is waiting on
the operator, after a grace period (default 10 minutes, so a watching operator
can answer first), or has closed.
- **The driver chooses which ready item,** from code-rendered summaries (source,
age, severity, state, title), never from other items' transcripts. A failed or
invalid choice falls back to the oldest ready item. Choosing grants nothing: every
item still meets its own rules.
- **Which ready item comes next:** the operator's order when given (e.g. "do #14
next", or a priority label); otherwise the oldest ready item. An agent may instead
let the driver choose from code-rendered summaries (source, age, severity, state,
title), never from other items' transcripts, where judgement helps (`nisse-ops`,
where severity matters); a failed or invalid choice falls back to the oldest.
Choosing grants nothing: every item still meets its own rules.
- **Parking gives up authority.** Elevated credentials an item's plan mounted
(O2, X3) are unmounted when it parks, and remounted only if its approval hasn't
expired.
@@ -268,17 +287,21 @@ Rules for the loop:
### L3. Authority: approvals and permissions
An approval grants a **permission with a scope**: "this build plan may change
this repo", "these ops steps may run", "this milestone may proceed without
discussion". Actions are checked against the permissions in force (P3); the loop
does not stop at gates for their own sake.
An approval grants a **permission with a scope**: "work on this issue",
"these ops steps may run". Actions are checked against the permissions in force
(P3); the loop does not stop at gates for their own sake.
**What needs discussion before it happens:**
- **Architecture documents and build plans are always discussed** with the
operator (D2). Their approval is the scope for the work that follows.
- A build plan marks each milestone **pre-approved** (proceed when reached) or
**needs discussion** (stop and talk first).
- **Any class-3 action** not named by an approved plan (P3).
**Where scope comes from.**
- **For dev work:** the assigned issue and what the operator agreed in its
conversation (D1). Build plans are the agent's working documents, not approvals
(D2); the operator's real check is reviewing the PR, and nothing merges without
it unless the trust dial says so.
- **For ops work:** an approved step sequence (O2), which is exact.
- **The operator steers with labels and words, never required ones** (D1): a label
or a request can ask the agent to talk first; leaving one off is never an error.
**What always needs discussion first:** any class-3 action not named by an
approved step sequence or by the operator in conversation (P3).
**Approvals bind to exactly what was approved.**
- Every plan revision carries a tag, numbered across the whole work item, so
@@ -300,10 +323,9 @@ allowlist. The first valid approval wins and is echoed to the other channels. Th
knowledge vault (L10) is **never** an approval source or an instruction channel.
**The trust dial.** How much the operator reviews after the fact is **data the
operator sets**, per project and per milestone: for example, whether PR review is
required before merging, or whether the agent may merge a pre-approved milestone
itself once checks pass. The operator turns it up as confidence grows. The agent
never changes it.
operator sets**, per project: for example, whether PR review is required before
merging, or whether the agent may merge its own PRs once checks pass. The operator
turns it up as confidence grows. The agent never changes it.
### L4. The action classifier
@@ -317,7 +339,8 @@ An independent check that sorts an action the fast path doesn't cover.
**Rules:**
- **It judges the action, not the actor's story.** Its input is the structured
action (tool, command, target, a summary of the change) and the **agreed scope**
(the work item's task, the approved plan or milestone). Never the actor's
(the work item's task and what was agreed, or the approved step sequence). Never
the actor's
transcript or reasoning, and never untrusted content such as issue text or web
pages. Otherwise injected text, or the actor's own rationalisation, can argue past
it.
@@ -342,7 +365,18 @@ Existing Matrix clients are the UI.
**What the operator can say.** Plain language, plus a small command vocabulary
that works in every state: `status`, `stop`, `abandon`, `approve [rev-N]`, and
work-item selection (e.g. `use <project>`).
work-item selection (e.g. `use <project>`, "do #14 next").
- **Free-flowing conversation can open a work item.** A conversation about work
("I'm having trouble with X…") is kept as a work item from its first message; it
may end with a project, an issue, or just an answer.
- **A thread per work item.** The agent opens each work item's conversation as a
Matrix thread. A reply in a thread goes to that item, even while it is parked
(answering makes it ready); top-level messages go to the agent in general or to
the active item.
- **Documents as input.** The operator can paste a document, attach a text or
Markdown file, or point to a note in the knowledge vault (L10) ("my sketch is in
the vault as `recipes idea`"). The message is the instruction; the document is
material for the conversation.
- **Commands are matched without regard to case, surrounding whitespace, trailing
punctuation, or a leading mention of the agent.** Messages that begin by
addressing someone else are not for this agent.
@@ -382,7 +416,7 @@ What each model sees, what persists, and how memory is written and used.
for one role, fitted to that model's window. It is never stored; only the log is.
Fitting, in order: shorten old tool results to their first and last lines; drop
the oldest events (with a note saying so); never drop the system prompt, the
approved plan or the most recent turns. A call that still doesn't fit is not sent;
current plan or the most recent turns. A call that still doesn't fit is not sent;
the caller treats it as a failure (L9).
**2. The work-item transcript** — the canonical log (L7).
@@ -396,7 +430,7 @@ the caller treats it as a failure (L9).
happened from these, not by inferring it.
- **Each role gets its own view:**
- the driver sees the conversation, the facts, and helpers' reports;
- a delegated helper sees its brief, the approved plan, the facts, its own work,
- a delegated helper sees its brief, the current plan, the facts, its own work,
and the driver's work in the same milestone as compact narration (each step
and how it went);
- `ask_expert` is stateless: the expert sees only the question;
@@ -737,44 +771,83 @@ Designs and builds software projects, producing reviewed changes in Gitea.
### D1. How it works
A dev work item is a task on one repository: a feature, a fix, a new project.
Through conversation, the agent and the operator agree on an architecture (for a
new project, or a significant change) and a build plan; the agent then works
through the plan's milestones, each producing a PR, and keeps the project's
documents up to date as it goes.
A dev work item is a task on one repository: a bug, a feature, a new project.
Work arrives through Gitea issues or conversation, and leaves as reviewed PRs.
`use <project>` opens or resumes a work item on a repository. Messages sent before
it are kept for it (L5).
**Intake: an issue assigned to `dev-agent`.** Assignment says whose job it is.
- **The operator's issue becomes the work item's record;** the agent doesn't open a
second one. Only issues filed or assigned by allowlisted operators are acted on
(L11).
- **Unassigned issues are backlog,** never work. The agent can discuss them when
asked.
- **An issue with open Gitea dependencies is blocked** (L1) until they close.
- **On assignment the agent acknowledges** ("queued myproject#14, 1 ahead"), and the
item waits its turn (L1).
**Starting an item.** The agent reads the project's documents (D2), then
**restates its understanding** in the item's Matrix thread (L5): what it takes the
request to mean, how it fits the architecture, and its first step. Then:
- **An issue labelled `bug`** proceeds after a short grace period (as in L1), unless
something below says to talk first.
- **Anything else** (a feature, a change, a question) waits for the operator's
go-ahead after the restatement; the conversation is where the design is agreed.
- **Talk first when:** the issue is labelled `discuss`; the agent finds the report
unclear (it asks its questions instead of restating); or it can't reproduce a
bug. It never fixes a bug it couldn't see.
- **The agent never classifies an issue itself.** It may suggest ("this reads like
a bug; say *go* and I'll start"); labels and the operator decide. A missing label
costs at most a wait, never unwanted work.
**The flows.**
- **A bug:** reproduce (ideally as a failing test), fix, verify, and open a PR that
closes the issue.
- **A feature:** agree the design in conversation, write a build plan if the work
is longer than about one PR (D2), then work through it as milestones (D4).
- **A new project:** begins as a conversation, however vague. If it leads to a
project, the agent says what it needs provisioned: a repository, its protection
and the agent's access (D4), a build VM, and the project's configuration. The
operator provisions these (later `nisse-ops`, X4). The agent then opens a tracking
issue in the new repository ("implement the build plan"), and the architecture
lands in one or more PRs, followed by the build plan's milestones.
- **Progress** on a tracking issue is posted as it happens: facts by the harness
(milestone started, PR opened or merged), prose by the agent when there's
something worth saying.
**Finishing.** The final PR closes the issue on merge; the agent closes the work
item, after checking what is left open (D5).
### D2. Documents are the workflow
The project's own documents carry the agreement between the operator and the
agent, and live in its repository:
The project's own documents live in its repository and carry its design forward:
- **`ARCHITECTURE.md`** — what the project is, how it fits together, and why. The
durable "what and why". Created with a new project; changed whenever the design
changes.
- **Build plans** — how a piece of work gets done, as a sequence of milestones.
Short-lived: one per body of work, kept in the repo for the record.
durable "what and why". Created with a new project, and kept current by the agent:
a change to the design updates it.
- **Build plans** — how a piece of work gets done, as a sequence of milestones,
under `docs/plans/` or similar. The agent's working documents: how it stays
organised across context windows and sessions, and its own sanity check.
**Both are always discussed.** The agent drafts, the operator and agent talk it
through, and the document lands as a PR the operator approves. An approved
architecture and build plan are the scope (L3) for everything that follows.
**Neither is a gate.** They are reviewed the way all work is, in PRs.
- **Architecture changes** go in the PR with the code when small; the agent may
raise a larger one first. An issue labelled `design` asks for the design to be
agreed in conversation before any code. Without the label it is the agent's
judgement, and a wrong call costs a review, not safety.
- **Build plans are written for work longer than about one PR,** committed with the
work, so every PR shows the current plan and how it changed. The first PR of a
multi-milestone piece of work includes the whole plan, so a wrong direction shows
at the first review. The operator doesn't approve plans; the agent asks for
feedback when it wants it, and the operator can ask to discuss one at any time.
**A build plan specifies; it doesn't implement.** Each milestone states its goal,
the interfaces and behaviour it adds or changes, edge cases, and **tests as
acceptance criteria**. It includes code only where the code *is* the clearest
specification (a tricky algorithm, an exact format). It never manages branches
(D4). How much detail it needs is informed by the driver's capability profile
(L9).
(L9). It may note where the agent means to check in with the operator, and which
milestones aren't expected to change code.
**Each milestone is marked** pre-approved (proceed when reached) or needs discussion
(stop and talk first), and says whether it is expected to change code (a review or
investigation milestone may not).
The Gitea issue for the work item carries the conversation's record: plans and
approvals are posted there as comments, so the agreement survives loss of the
agent's state (L8).
**Progress is recorded in the plan itself:** the agent marks milestones done as
part of each PR. The framework never parses a plan (D4).
### D3. The workspace
@@ -788,7 +861,7 @@ commands run.
below). Work pushed to Gitea survives; a commit not yet pushed may be lost when a
VM is wiped, and that is accepted.
**A fresh, clean workspace at `use <project>`.**
**A fresh, clean workspace when a work item starts.**
- Fetch from the repository, and check out a clean tree at the latest base branch.
- Anything left in a previous workspace (uncommitted changes, local branches,
unpushed commits) is **set aside**, never silently discarded or carried over,
@@ -818,9 +891,25 @@ a corrupt `.pyc`.
### D4. Git, branches and PRs
- **The harness owns branches.** Each milestone works on a branch the harness
creates from the work item's current state; plans and models never create or
switch branches.
- **The harness owns branches.** Plans and models never create or switch
branches.
- **The workspace is never on the default branch:** when a work item starts, the
harness puts it on a branch of its own.
- **Milestones are started and finished by control tools** (L13):
`start_milestone(name)` makes the harness cut the milestone's branch;
`finish_milestone(summary)` makes it commit, push, open the PR and record the
fact. The model decides when, from its plan; the name is the model's, and the
harness doesn't check it against the plan.
- **Mistakes are made harmless,** not trusted away: finishing without a start
cuts the branch then; starting while another milestone is in progress is
refused; work done outside a milestone is still on a harness branch.
- **Wait for the merge between milestones.** The next milestone starts only after
the previous milestone's PR has merged, read from Gitea; until then the work item
waits on the operator and the agent works on something else (L1). Stacked PRs
are a possible later addition (Q).
- **Parking commits and pushes the item's branch,** so another work item on the
same project starts from a fresh checkout (D3) and the parked one resumes from
its branch.
- **Only the milestone's own changes are published:** what the agent changed,
never build artifacts (standard excludes in the clone, and committing only what
the work produced). Anything left out is reported.
@@ -835,10 +924,11 @@ a corrupt `.pyc`.
- **Publishing is idempotent** (L8): an open PR for the branch is adopted and
updated, never duplicated.
- **A milestone that completes without changes is done, not failed,** when it
wasn't expected to change code. The agent reports its result and moves on as the
build plan says. An unexpected "no changes" is raised with the operator.
- **Moving past a milestone checks its PR's state** in Gitea. If it is still open
and unmerged, the agent says so and asks, rather than leaving it behind.
wasn't expected to change code. The agent reports its result and moves on. An
unexpected "no changes" is raised with the operator.
- **The agent never owns or administers a repository.** Whoever creates a repo can
turn its protection off, so repositories are created and protected by the operator
(later `nisse-ops`), and `dev-agent` is a collaborator with write access.
- **The project's default branch is protected,** and this is verified, not
assumed: the agent's own token must be refused when pushing to it, and when
merging without whatever approval the trust dial requires (L3). A test asserts
@@ -853,8 +943,8 @@ a corrupt `.pyc`.
- **Intake is exactly as in L8:** cursors, at-most-once taking, at-least-once
answering, redelivery after a crash.
- **Merging follows the trust dial** (L3). Today the operator merges. As confidence
grows, the dial may let the agent merge a pre-approved milestone once checks pass
and the required approvals are recorded; the branch protection must then require
grows, the dial may let the agent merge its own PRs once checks pass and the
required approvals are recorded; the branch protection must then require
an operator approval the agent's account can't satisfy, so the merge rule is
enforced by Gitea, not by the agent.
- **Finishing a work item** closes its Gitea issue with a note (completed or
@@ -868,8 +958,8 @@ a corrupt `.pyc`.
| 2 (in scope) | Editing the workspace, committing to milestone branches, pushing them, opening and updating PRs, commenting on its issues and PRs, cleaning caches, installing project dependencies, packages and toolchains on the project's build VM |
| 3 | Merging to the default branch (unless the trust dial allows), deleting remote branches or others' work, changing repository settings, anything outside the work item's repo or its project's build VM |
Its scope sources are the approved architecture, the build plan and each
milestone's marking.
Its scope source is the assigned issue and what was agreed in its conversation
(L3); `ARCHITECTURE.md` and the build plan inform it but don't widen it.
### D7. Deployment and isolation
@@ -919,7 +1009,7 @@ they take effect.
| Axis | `nisse-dev` | `nisse-ops` |
|---|---|---|
| Undo | discard a branch, revert a snapshot | often none (`zfs destroy`, a firewall lockout) |
| Scope source | architecture and build plan | approved step sequences (O2) |
| Scope source | the assigned issue and its conversation | approved step sequences (O2) |
| Default for class 3 | discuss | discuss, and exact-step approval |
| Work shape | milestones → PRs | short reactive tasks and scheduled checks → live changes |
| Driver of work | the operator in Matrix, Gitea | Vikunja tickets, alerts, duties, the operator |
@@ -1083,10 +1173,11 @@ identity, so every upstream action traces to one agent.
### X5. Supporting services
All self-hosted: Matrix (Synapse), Gitea, LiteLLM, the local inference host
(llama-swap on an RTX 4090 host), Vikunja, Vaultwarden, LLDAP with Authelia, and
Syncthing for the operator's copy of the knowledge vault (L10). Each agent gets its own account, scoped token and room in each service
it uses; nothing is shared between agents.
All self-hosted: Matrix (Synapse), Gitea, LiteLLM, the local inference host (llama-
swap on an RTX 4090 host), Vikunja, Vaultwarden, LLDAP with Authelia, and Syncthing
for the operator's copy of the knowledge vault (L10). Each agent gets its own
account, scoped token and room in each service it uses; nothing is shared between
agents.
---
@@ -1120,6 +1211,9 @@ it uses; nothing is shared between agents.
- **How `nisse-ops`' MCP servers reach the second site** from the agent host
(Tailscale on the host, with other agents' networks denied the tailnet, or a
subnet route), since Tailscale on the host serves every container on it.
- **Stacked PRs.** Starting the next milestone before the previous PR merges,
with the harness rebasing and retargeting. Considered after experience with
waiting for merges (D4).
- **How long idle before a build VM is collected,** and whether collection needs the
operator's word.
@@ -1135,15 +1229,17 @@ history):
rigid state machine gating every phase.
- **The agent loop (L2)** replaces the per-agent state machines (old A4, B1). What
stays fixed is only what authority and durability need.
- **Approvals as scoped permissions (L3),** with pre-approved and needs-discussion
milestones and an operator-set trust dial. Revision binding is kept and extended
(task-wide revisions, stamping on arrival).
- **Approvals as scoped permissions (L3),** with scope from the assigned issue and
its conversation, labels to steer, and an operator-set trust dial. Revision
binding is kept and extended (task-wide revisions, stamping on arrival).
- **The local model drives everything (P7, L9);** frontier models are helpers. The
old "planning always escalates" phase policy is dropped.
- **Context and memory (L6)** is new: transcript scoping, attribution, orchestrator
facts, scope memory, agent memory, and how memory is written and used.
- **Documents as the dev workflow (D2),** a fresh workspace per task and toolchain
preflight (D3), and git hygiene learned from the live runs (D4).
- **The dev workflow (D1, D2):** issues assigned to the agent as intake, labels to
steer, documents kept current through PRs, and build plans as the agent's working
documents rather than gates; a fresh workspace per task and toolchain preflight
(D3), and git hygiene learned from the live runs (D4).
- **Conversation (L5)** is a first-class requirement, not a command interpreter.
- **The knowledge vault (L10)** replaces Outline: Markdown on the agent host, shared
between agents by mounts, synced to the operator by Syncthing. Revised in PR #90.