Merge pull request 'Define the work model: duties, work items, scopes' (#38) from work-model into main
CI / checks (push) Successful in 1m1s
CI / checks (push) Successful in 1m1s
Reviewed-on: dongar/nisse#38 Reviewed-by: Don Garrett <dgarrett@acm.org>
This commit was merged in pull request #38.
This commit is contained in:
commit
182216296d
1 file changed
+131
-8
@@ -64,6 +64,14 @@ Both are thin applications over a shared **`nisse-core`** library. The library i
|
||||
consumers that don't exist yet. Build the Dev Agent as if the library were internal,
|
||||
then lift the seams that proved real.
|
||||
|
||||
There is one deliberate, bounded exception: the **work model's identity scheme**
|
||||
(0.2) is settled for both agents ahead of extraction. Identifiers end up in the state
|
||||
schema, branch names, the operational log (A9), the transcript (A10), and external
|
||||
records. Changing those after extraction would mean migrating two agents and their
|
||||
stored state. Only identity is designed ahead. Behaviour is still built by each
|
||||
agent as it needs it, so nothing in 0.2 requires the Dev Agent to implement duties,
|
||||
deduplication, or concurrency.
|
||||
|
||||
The three artifacts live in **one repository** (decided — Part H), so the
|
||||
extraction step in the build order is a package split within one tree, not a repo
|
||||
split.
|
||||
@@ -86,6 +94,80 @@ split.
|
||||
|
||||
---
|
||||
|
||||
## 0.2 The Work Model: Duties, Work Items, and Scopes
|
||||
|
||||
Every agent's work is organised into three kinds of unit. They define what state
|
||||
belongs where, how work is identified, and what the operator is resuming when they
|
||||
come back to something. Components throughout this document refer to them.
|
||||
|
||||
| Concept | What it is | Lifetime | Dev Agent | Sysadmin Agent |
|
||||
|---|---|---|---|---|
|
||||
| **Duty** | A standing responsibility that produces work items | Indefinite, recurring | None yet; the operator is the only source | Scheduled checks, log monitoring, alert intake |
|
||||
| **Work item** | One effort with a lifecycle: opened, worked (often through gates), closed | Opened → closed | Building a feature through arch plan → milestones → PRs | Diagnosing one failure; answering one question |
|
||||
| **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 |
|
||||
|
||||
### Duty
|
||||
|
||||
- **A duty produces work items. It is not one itself.** A run of a duty (one log
|
||||
analysis, one scheduled check) is read-only and yields findings. A finding that
|
||||
needs attention becomes a work item.
|
||||
- **The operator is always an implicit source** of work items, for every agent.
|
||||
- **Deduplication is a duty's responsibility.** When a later run finds the same
|
||||
failure, it attaches to the existing open work item instead of opening a second
|
||||
one. That needs a stable fingerprint per finding, checked against open work items.
|
||||
This is the same idempotency concern A2/A3 address for side effects.
|
||||
|
||||
### Work item
|
||||
|
||||
- **The unit of state.** A work item owns its state-machine position, gates and plan
|
||||
revisions, pending questions, pinned role mapping (A11), and its transcript (A10).
|
||||
- **Identity is minted locally.** Every work item gets a local ID when it opens.
|
||||
This is the `task_id` of A9: a work item's ID and its correlation ID are one value.
|
||||
- **An external record is optional, and linked.** A work item *may* be tied to a
|
||||
durable external record: a Gitea issue for the Dev Agent, a Vikunja ticket for the
|
||||
Sysadmin Agent. Many will not be; a general question to either agent is a work
|
||||
item with no record. A work item can also gain a record partway through, for
|
||||
example a question that turns into a change to make.
|
||||
- **Where the spec requires a record, it has one.** Dev plan gates are externalized
|
||||
to Gitea (Part H), and ticket-driven ops work has its ticket (C4). When a record is
|
||||
linked, external truth wins on divergence (A2), and gate state can be re-derived
|
||||
from it after state loss. Whether *every* gated work item must have a record is
|
||||
open (Part H).
|
||||
- **Work items are not nested.** A milestone is a phase *within* a dev work item, and
|
||||
a delegation (A13) is an episode within one. Neither is a work item of its own.
|
||||
|
||||
### Scope
|
||||
|
||||
- **A scope holds context shared by the work items that operate on it:** for a repo,
|
||||
the clone and the project's conventions; for infrastructure, the A17 world model
|
||||
of that site or service.
|
||||
- **Many work items may share one scope.** A feature waiting on review and a bug fix
|
||||
in the same repo are two work items on one scope. So are two failures found in the
|
||||
same log run.
|
||||
- **A work item has zero or more scopes.** A dev work item has exactly one repo for
|
||||
now. A general question may have none.
|
||||
- **Scope identifiers follow existing naming.** Repos use `owner/repo`. Sites use the
|
||||
site identifier (Part 0), combined with the service.
|
||||
|
||||
### Park-and-switch: one active work item per agent
|
||||
|
||||
- **Each agent has at most one active work item.** Others are *parked*, with their
|
||||
full position held in the state store (A2).
|
||||
- **Switching happens only at safe points,** where the loop is waiting on a human
|
||||
(an open gate, a pending question, a PR in review) or is idle. It never happens
|
||||
mid-turn. Because turns run to completion, every point at which a command can
|
||||
arrive is a safe point, with one exception: switching is refused while intents
|
||||
are unresolved (A2). That keeps every pending intent owned by the active work
|
||||
item, so boot reconciliation (A3) stays single-item.
|
||||
- **Nothing is lost while parked.** Intake for a parked work item, such as comments
|
||||
on its Gitea issue or PR, is read from a durable per-thread cursor when it becomes
|
||||
active again. It is not dropped, and it is not replayed from the beginning.
|
||||
- **The state schema must not assume one item.** Work items live in their own table,
|
||||
and "active" is a pointer to one of them. Allowing more than one active item later
|
||||
(Part H) is then a control-loop change, not a migration of every stored item.
|
||||
|
||||
---
|
||||
|
||||
## PART A — THE AGENT LIBRARY (`nisse-core`)
|
||||
|
||||
The shared spine. Every component here is consumed by both agents. This is the part
|
||||
@@ -121,8 +203,9 @@ canonical log (A10) is the sole source of truth.
|
||||
process restart. This is what makes the whole system safe to redeploy and reboot; it
|
||||
blocks everything else.
|
||||
|
||||
Must persist: active project/task, per-project state-machine position, approvals
|
||||
granted, pending questions, session continuity, (dev) git clone locations.
|
||||
Must persist: the active work item and each work item's state-machine position
|
||||
(0.2), approvals granted, pending questions, session continuity, and scope state such
|
||||
as (dev) git clone locations.
|
||||
|
||||
Disposable (never on the state volume): agent code, build artifacts/test containers,
|
||||
shipped-out logs.
|
||||
@@ -203,6 +286,11 @@ Requirements:
|
||||
for copy-paste. Any other reply/comment is `reject <feedback>`. Reactions/emoji are
|
||||
never an approval path. Approvals are consumed at first sight; later edits to an
|
||||
approval comment are ignored and logged.
|
||||
- **Approvals bind to a work item, too (0.2).** A bare `approve` binds to the
|
||||
*active* work item's latest revision, under the unambiguity rule above. An
|
||||
approval arriving on a *parked* item's external record (a Gitea or Vikunja
|
||||
comment) is delivered when that item next becomes active. It is never applied to
|
||||
the active item.
|
||||
- **Autonomy inside an approved unit of work:** all tool/command use auto-approved
|
||||
(Claude Agent SDK `bypassPermissions` or an allow-all `canUseTool`). Escalation to
|
||||
the human happens *only* via `ask_human`.
|
||||
@@ -311,9 +399,11 @@ by replaying the operational trail:
|
||||
- `ts` — UTC ISO-8601 timestamp
|
||||
- `agent` — `nisse-dev` | `nisse-ops`
|
||||
- `event` — dotted type from the taxonomy below
|
||||
- `task_id` — correlation ID for the enclosing unit of work, minted when the
|
||||
unit starts (`use <project>`, milestone start, ticket pickup) and threaded
|
||||
through every event it causes; `null` only outside any unit (e.g. boot).
|
||||
- `task_id` — the enclosing work item's ID (0.2), minted when the work item
|
||||
opens (`use <project>`, ticket pickup, an operator question, a duty's finding)
|
||||
and threaded through every event it causes; `null` only outside any work item
|
||||
(e.g. boot, or a duty run before it yields a finding). A milestone is a phase
|
||||
within a work item, so starting one does not mint a new ID.
|
||||
- `cause` — what triggered the mutation: operator command, approval
|
||||
(`rev-N` + channel), escalation trigger, reconciliation verdict, or crash
|
||||
recovery.
|
||||
@@ -351,6 +441,9 @@ The durable record is a **canonical event log** in SQLite, not a provider-shaped
|
||||
| `knowledge_retrieved` | document text as read, document ID, revision, retrieved-at (A17) |
|
||||
|
||||
Rules:
|
||||
- **The log is scoped to a work item (0.2).** Every event carries its work item's ID,
|
||||
and a model call for one work item renders only that item's events. Evaluation
|
||||
capture (A16) may read across items.
|
||||
- **Canonical tool-call IDs are minted locally.** Provider IDs are mapped at the
|
||||
boundary and never used as a join key. Providers differ in both ID format and in
|
||||
where tool calls sit structurally (content blocks vs. a separate array on the
|
||||
@@ -691,6 +784,10 @@ intended.
|
||||
|
||||
A thin app over `nisse-core`. Plans and implements software projects; produces PRs.
|
||||
|
||||
In 0.2's terms, a **project** below is a dev work item on a repo scope. `use
|
||||
<project>` opens or resumes a work item on a repo, and `project complete` closes the
|
||||
work item. The repo, its clone, and any other work items on it continue.
|
||||
|
||||
### B1. Workflow (state machine)
|
||||
|
||||
```
|
||||
@@ -713,7 +810,8 @@ PR_OPEN
|
||||
|
||||
### B2. Requirements
|
||||
|
||||
- **One active project at a time.** Operator selects it.
|
||||
- **One active project at a time** (park-and-switch, 0.2). Operator selects it;
|
||||
other projects stay parked at their last position and resume where they left off.
|
||||
- **Operator commands** (over transport): `use <project>`, `approve`,
|
||||
`reject <feedback>`, `start milestone <n>`, `status`, plus free text during
|
||||
planning (forwarded to the agent as answers).
|
||||
@@ -845,8 +943,12 @@ Special cases fold into the tiers:
|
||||
### C4. Ticket-Driven Control (Vikunja)
|
||||
|
||||
The sysadmin analog of "dev agent driven by Gitea."
|
||||
- **Vikunja ticket = task of record** (durable, has status/queue/history). **Matrix =
|
||||
live conversation about it** (questions, confirm-first prompts, progress).
|
||||
- **Vikunja ticket = task of record** (durable, has status/queue/history): the
|
||||
external record of a work item (0.2). **Matrix = live conversation about it**
|
||||
(questions, confirm-first prompts, progress).
|
||||
- **Scheduled checks and alert intake are duties (0.2).** Tickets they generate are
|
||||
duty findings, deduplicated against open tickets, and they route through
|
||||
confirm-first (C2) like any auto-generated ticket.
|
||||
- **Flow:** ticket filed → agent picks up → discuss/confirm over Matrix → update
|
||||
status as it progresses → close with result.
|
||||
- **Mechanics:** Vikunja REST API + webhooks (same two-direction, JSON-only pattern
|
||||
@@ -1113,6 +1215,27 @@ The rest resolve when reached, not now.
|
||||
attribution property that makes instance-per-agent-VM (F3) clean.
|
||||
Instance-per-agent-VM is the starting position. Migrate one server at a time, when
|
||||
a specific server justifies it.
|
||||
- **Parked: more than one active work item (0.2).** Park-and-switch is the starting
|
||||
position. The pressure to relax it will come first from the Sysadmin Agent: an
|
||||
alert arriving mid-diagnosis, or a duty's scheduled run coming due while a work
|
||||
item is active, has to wait for a safe point. Allowing concurrent items would
|
||||
cost:
|
||||
- *Serialization in the state store and control loop.* Transitions for different
|
||||
items interleave, and the single orchestrator lock becomes per-item.
|
||||
- *A scheduler.* Something has to decide which item's turn runs, especially with
|
||||
one local inference host serving all of them.
|
||||
- *Approval binding.* Two confirm-first prompts open at once make a bare `approve`
|
||||
ambiguous everywhere, so per-item rooms or strict reply threading become
|
||||
mandatory rather than convenient.
|
||||
- *Shared-scope conflicts.* Two active work items on one repo need separate
|
||||
working trees. Two on one infrastructure scope could make changes that
|
||||
interfere with each other.
|
||||
- *Intent ownership.* Boot reconciliation has to sort pending intents by item, and
|
||||
refusing to switch no longer keeps them single-item.
|
||||
- **Open: when a work item must have an external record (0.2).** Plan-gated dev work
|
||||
and ticket-driven ops work already require one. Whether every gated work item
|
||||
needs one (for instance, an ops question that turns into a confirm-first change)
|
||||
is best settled alongside the C2 taxonomy.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user