Development Plan M1.1, as discussed.
- Schema v4: a work_items table (id, scope, title, status, outcome,
position, data) and an agent row whose active_item points at one
(NULL: idle). Canonical events and intents carry their work item, and
events their actor. Older stores are refused with a clear message:
there was no production data, so no migration (the mechanism stays).
- The log is reached through a Transcript bound to one work item and
one actor: store.transcript(item, actor) reads only that item and
writes with both. The planner, executor, delegation and expert desk
get one per call from the orchestrator instead of holding the store,
so a component can neither read another task's events (the planner
read every earlier task's, #78) nor write without attribution.
store.all_events() is kept for readers that span items (evaluation
capture, dumps). Intents are begun through the transcript or with
work_item=, so each belongs to its item.
- The orchestrator opens a work item on 'use', closes it with its
outcome (completed or abandoned), and carries what the operator said
while idle into the new item as copies with their origin, so a task
sent before 'use' is still planned from (D1). control.py keeps its
state machine through get_state()/transition(), now over the active
work item, until M2.6 retires it.
- nisse_dev.dump shows work items and each event's item and actor.
Tests: test_work_items.py (scoping, attribution, intent ownership,
restart, outcomes, carried messages, a second task never seeing the
first); the suites updated for the per-call transcript.
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
Build-order step 4, sequenced before the LLM client (step 3) because
the client interface consumes the canonical log.
- canonical.py: typed events for the full A10 table (user_message
through compaction); canonical tool-call IDs minted locally,
provider IDs never a join key; strict payload validation on load
- StateStore: canonical_events table via schema v2 — the first real
migration, exercised in a test against a hand-built v1 database —
with append_event/read_events on the same fsync-per-commit path
- render.py: request bodies rendered from the log at call time;
first adapter targets the OpenAI-compatible shape of LiteLLM's
normalized route; compaction events replace their id range at
render time while staying in the durable log
- Crash integration test now also verifies transcript appends
survive process death
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Retrofit for the conventions merged in #4 (issue #3):
- OpLog: JSON-lines emitter with the fixed envelope (ts, agent,
event, task_id, cause); task_id set by the orchestrator at
unit-of-work start
- StateStore owns emission: transition, intent begin/commit/abort,
and schema migrations each emit their event right after the
durable write commits; callers pass the cause
- reconcile_on_boot emits boot.reconcile with action and verdicts,
and resolves intents with reconciliation-verdict causes
- Failed resolutions emit nothing (a line exists iff the write
committed)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Build-order step 1: the critical path everything else sits on.
- StateStore: SQLite in WAL mode with synchronous=FULL, so every
state transition and intent-log write is committed and fsynced
before returning; a power cut cannot tear state
- Durable intent log: begin_intent records a unit of work before it
starts; commit/abort resolve it; pending intents survive restart
- schema_version stamped from the first commit, with a sequential
migration hook; a newer state file than the code supports is
refused rather than misread
- reconcile_on_boot (A3 first cut): pending intents are checked
against external truth by registered reconcilers (observable
checks, never model self-assessment); verified effects are
committed, verified no-ops aborted, anything unverifiable gates
to AWAIT_INPUT — never blind-resume
- Lives in nisse_dev per spec 0.1: the library is extracted after
the dev agent works, not designed up front
- Integration test kills a subprocess mid-work with os._exit and
verifies state integrity and intent visibility on reopen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>