Compare commits

...
Author SHA1 Message Date
jason.woltje 3b7fd19d08 docs: verify published wave and set registry review next 2026-09-08 12:44:13 -05:00
jason.woltje 69f10a4062 fix(tmux): explicit transport-only dispatch and safe remote quoting (#1496) 2026-09-08 12:41:25 -05:00
jason.woltje 67eaf6fb47 docs(logs): pending session/build records through 2026-09-07
Append-only BUILD-LOG phases, SESSIONS registrations, and CURRENT
checkpoint state accumulated through the consolidation and inspector
review waves.
2026-09-07 14:07:16 -05:00
jason.woltje 3ea385223e feat(skills): six new ms-* skills
ms-archify (evidence-based architectural mapping), ms-sdlc,
ms-proactive-agent, ms-goal, ms-grill-me, ms-frontend-design.
2026-09-07 14:07:16 -05:00
jason.woltje 193479b52d docs: concept annexation, provider/reference docs, ACT-1 groundwork
Mosaic concepts pages now own the adapted content; source/license
metadata under docs/reference/concepts. Adds ACT-1 agent-context
planning capture, pinned concept test package + preparation utility,
foundation observation notes (durability, evidence, federation,
onboarding, workflow), and the #1495 consolidation assessment.
TOOLS.md updated for the host-dev launcher.
2026-09-07 14:07:05 -05:00
jason.woltje 7c580a5625 feat(agents): darkwing host-dev launcher + entry-point consolidation
scripts/agent.sh --host-dev delegates to scripts/agent-host-dev.sh;
agents/darkwing/launch.sh provides the native development TUI
(context, skills, coding tools, /goal). Root SOUL.md is the M14-era
default-collaborator persona captured by the ACT-1 context work.
Launcher regression checks pass (test-darkwing-launch.mjs).
2026-09-07 14:06:54 -05:00
jason.woltje 8ebddd6f93 feat(foundation): offline synthetic scope/permission inspector (FI-FILBERT-8 APPROVED r6)
Rocko-authored, Filbert-reviewed inspector (r6 manifest
a4a44930...) with full review/build/verdict evidence under
docs/plans/reviews. 43/0 selftests, oracle zero-disagreement,
foundation checker PASS. Owner A9 acceptance recorded separately.
2026-09-07 14:06:35 -05:00
jason.woltje 127a54fdff chore: consolidate new foundation and archive v1 (#1495) 2026-09-07 12:32:57 -05:00
Dewey 9a5fbdbda7 fix(goal): quiet waits and unify fleet NG ownership (#56, #57, #58) 2026-09-06 04:07:09 -05:00
jason.woltje 7345f330fc docs: map foundation to integrated rewrite baseline 2026-09-06 02:40:05 -05:00
jason.woltje d4696d09eb feat(extensions): establish canonical goal source (#54, #55) 2026-09-06 02:32:32 -05:00
jason.woltje 44f257cb06 docs: record accepted phase-2 foundation contract 2026-09-06 02:23:15 -05:00
code-infra-01andorch-01 5d27700026 fix(#1257): confirm delivery by draft transition, not prompt detection (adopts #1262) (#1332)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: code-infra-01 <[email protected]>
2026-09-04 22:25:13 +00:00
jason.woltje 69d1bb3aa4 docs(plan): resolve harness IDs + lifecycle review gate (#50)
Owner adjudication:
- canonical harness IDs match executables: pi, claude, codex, opencode
- agent.json uses one scalar harness ID; registry/manifest resolution, no
  hard-coded schema enum
- target mosaic harness list/detect/install/rm/status lifecycle
- detection recognizes reviewed executables and records compatibility
  without reading/copying harness homes
- installs are exact-version/verified, Mosaic-managed, never global
- detected external harness is available but not container-ready until
  imported/installed, absent a separately reviewed host adapter

Gate 1 resolved. Remaining P0/review gates stay open; no implementation
authorized. Suites 24/15/90/14/17 + verify green; unslop clean.
2026-09-04 12:26:57 -05:00
jason.woltje 9ea7dea711 docs(review): record independent glm-5.3 auth-registry spec review (#50)
Verdict ACCEPT WITH CHANGES. Persist ten-gate recommendations and three
P0 blockers: per-seat launch provider/model resolution; rotating OAuth
persistence for long-running seats; role auth ceiling ∩ settings profile
plus data-map/reset alignment. Reviewer made no repo edits.

Implementation remains blocked pending owner/conductor adjudication.
Prior gate remains green: suites 24/15/90/14/17 + verify.
2026-09-04 12:14:48 -05:00
jason.woltje d9a94f51ba docs(plan): specify harness declaration + centralized auth/provider registry (#49)
Design only; implementation blocked pending owner review.

- agent.json: one harness identifier (pi first), resolved through a
  versioned adapter/harness manifest; reusable settingsProfile reference
- central data-root registry: providers, accounts (metadata + secret
  credential split), reusable settings profiles, audited runtime selection
- per-seat pi auth.json/models.json mechanically generated and atomically
  activated; no seat/provider registration ceremony
- mixed oauth/api-key accounts supported centrally; one active account per
  provider per pi materialization
- host-side centralized OAuth login/refresh; agents never authenticate
- local/remote Ollama modeled as endpoint providers, not accounts
- target mosaic auth/provider/agent settings CLI; secrets never on argv
- migration, fail-closed acceptance suites, and ten explicit review gates

CURRENT.md points only to spec review. Suites 24/15/90/14/17 + verify
green; unslop clean.
2026-09-04 11:57:40 -05:00
jason.woltje 975084abe2 fix(auth): mosaic-managed auth lives under the data root, never ~/.pi (#48)
Owner direction: the stack must never impact default harness usage.
Correction to M19 as shipped (nothing had been created in ~/.pi — the
move breaks nothing).

- Mosaic-managed accounts: <dataRoot>/auth/<account>.json, perms 0600
  enforced (loose perms flagged in listings, refused by --auth — mirrors
  gitea-api.sh credential hygiene).
- ~/.pi is read-only to the stack, permanently; the only interaction
  remains the existing read-only container mount of the default
  credential. Recorded as a ROADMAP standing decision.
- auth.sh is now config-driven (data root from config.json, fail closed,
  consistent with every other tool); status reports both sources labeled.
- agent.sh --auth resolution moved after load_config (needs the data
  root); missing/symlinked/non-0600 accounts refuse.
- test-auth.sh: 15 no-Docker cases (accounts-create-nothing, loose-perms
  refusal, invalid-config refusal added). Test-authoring correction
  recorded in BUILD-LOG (fixture-state mismatch caught before running).

Suites 24/15/90/14/17 + verify green.
2026-09-03 22:53:33 -05:00
jason.woltje 073bbfdb6a feat(auth): M19 harness auth tooling — auth.sh checkpoint + per-launch account injection (#47)
Investigation (pi 0.84.4 docs + host auth.json metadata, values never
read): provider stacking is native (one auth.json keyed by provider;
resolution --api-key > auth.json > env > models.json; OAuth auto-refresh).
Multi-account per provider is NOT native -> named-file design:
auth.<account>.json + per-launch injection.

- scripts/auth.sh: status (provider names, credential types, perms,
  env-side names informational — never credential material) and accounts
  (named files, active marker). Exit codes per convention: 3 missing for
  a read, 2 unparseable, 4 file/environment (symlinks refuse).
- scripts/agent.sh --auth <account>: resolves auth.<account>.json and
  exports PI_AUTH_FILE (the existing compose read-only mount source — no
  new plumbing); missing/invalid account refuses pre-container.
- scripts/test-auth.sh: 13 no-Docker cases; core assertion is the safety
  property itself — fixture key/token/env VALUES never reach output.
- Docs: TOOLS.md Auth section, AGENTS.md command surface + suites.

Headless task runs keep the default credential (worker auth selection is
a separate policy decision). Real-host smoke: anthropic/openai-codex
oauth + zai api_key reported, perms 600, no named accounts yet.

Suites 24/90/14/17/13 + verify green. Agreed sequence M16-M19 complete;
M20 owner-gated.
2026-09-03 19:58:50 -05:00
jason.woltje d1d7b5598d feat(agent): fail-closed seat resolution under MOSAIC_AGENTS_DIR override (#46)
Owner decision after live verification of M18: an explicit agents-dir
override that cannot resolve the named seat now refuses the launch
(exit 4, names the seat and dir) instead of launching seatless and
unbounded. Unsetting the override keeps the M13 plain governed TUI.
MOSAIC_ROLES_DIR needs no symmetric change - the M18 gate already
refuses unresolvable role contracts.

Task suite 88 -> 90 (refusal + refusal-names-the-seat). TOOLS.md Agent
section documents the refusal.

Suites 24/90/14/17 + verify green.
2026-09-03 19:45:10 -05:00
jason.woltje ca8135d70c feat(roles): M18 seat-role progressive capability restriction (#45)
Role contracts (roles/<role>.json): roleVersion, name bound to filename,
tools ceiling (subset of pi built-ins), network declared (none|api-only|
open; enforced when network policy lands). Strict schema, fail closed -
a non-role document refuses resolution.

mosaic-task.mjs resolve-role: config-free contract validation, emits
MOSAIC_ROLE_TOOLS / MOSAIC_ROLE_NETWORK.

agent.sh: a declared role binds to its contract. Missing/invalid contract
refuses the launch (exit 2, names the role - the under-equipped-seat
failure mode, mirroring M17 skills). Effective tools = ceiling ∩ requested
(CLI --tools or agent.json caps); no request -> ceiling stands; narrowing
and tool-free outcomes loud on stderr. Adapters unchanged; headless M9
chain (mission ∩ task) untouched.

Ships roles/researcher.json (existing seat declares the role; without the
contract the fail-closed gate would refuse its launch).

Task suite 74 -> 88: contract resolution, wrong-kind/name/network/
duplicate/unsupported/missing refusals, ceiling narrowing E2E (mock
adapter), tool-free E2E, missing-contract refusal. Test-authoring
correction recorded in BUILD-LOG (a check that registered on one path
only, caught by count arithmetic).

Suites 24/88/14/17 + verify green.
2026-09-03 17:25:17 -05:00
jason.woltje bf56583a49 docs(skills): owner loop-doctrine + collaborator delivery discipline, remediated (#44)
ms-communications: integrated as-authored - owner preamble restructure +
collaborator delivery-discipline hunks from the #43 calibration (own
session output is not a send path; the tool performs the preamble flip;
receiving rule 3 requires actually running agent-send.sh).

ms-conductor: collaborator redraft integrated (canon-aligned tracking
surfaces, one-action cadence, fail-closed core) with one conductor
remediation - step 3 now distinguishes refusal (fail closed, never
bypass) from runner outage (direct dispatch to a qualified live seat via
ms-communications permitted, recorded loudly as degraded: no sandbox, no
run record; suites still gate integration). Preserves the owner's
outage-dispatch intent inside invariant 6.

docs/TOOLS.md: release.sh ensure row added (M16 subcommand existed in
code but not in the doc - flagged by the collaborator, verified in
release.sh usage).

Authorship: owner (ms-conductor doctrine, preamble restructure) +
ms-test collaborator (delivery hunks, redraft); remediation + integration
by conductor (dragon-lin:darkwing). Suites 24/74/14/17 + verify green;
unslop clean.
2026-09-03 17:17:29 -05:00
jason.woltje c8f433131c docs: calibration phase 22 record; CURRENT.md staleness corrected (M16/M17 late-logged, next M18); session registered (#43) 2026-09-03 16:55:31 -05:00
jason.woltje d1e75f855c docs(tools): document tools/ tree in TOOLS.md + fix stale suite counts (#43)
Collaborator-authored via conductor-loop calibration: task dispatched to the
live ms-test seat (glm-5.3-flash) over agent-send.sh; diff reviewed line by
line and every documented flag/exit code independently verified against tool
source by the conductor; suites green at integration (config 24 / task 74 /
release 14 / conductor 17 + verify).

- new 'Tools (host-side)' section: agent-send.sh, agent-watch.sh, unslop-check.js
- intro reading guide now points at tools/ (worker-flagged addition, accepted)
- Maintenance suite counts corrected: test-task.sh 58 -> 74

Authored-by: ms-test collaborator (glm-5.3-flash)
Integrated-by: conductor (dragon-lin:darkwing)
2026-09-03 16:55:25 -05:00
jason.woltje f710bf1a68 docs: KICKSTART file 2026-09-03 16:34:20 -05:00
jason.woltje 2085d75190 docs: ms-communications skill (owner-authored) + session registry entry + KICKSTART recovery file 2026-09-03 16:34:03 -05:00
jason.woltje 2f5a8d2cec feat(skills): skill lifecycle + ms-* skill set completion (#40, #41, #42)
- scripts/skill.sh: install (bundled or path) / activate / deactivate /
  uninstall (refuses while enabled) / list
- skills-enabled + skills-available dirs under the data root; a skill not
  in skills-enabled is not enabled or available for use
- pi adapter: MOSAIC_SKILLS -> --skill per dir; --no-skills when none
- agent.sh: seat definitions declare skills[]; resolution against
  skills-enabled refuses the launch loudly when missing
- ms-* skills completed (owner-authored canon, hands-off): ms-tools
  adapted to the runtime, ms-file-read/write/agent/conductor bodies
  written in the owner's style; ms-agent-watch + ms-unslop untouched
- tasks/USER.md onboarding fixtures; suite hardening (nested def path,
  user seed, mock-adapter dispatch evidence)

Suites: config 24, task 74, release 14, conductor 17, verify PASS.
RELEASE 0.0.12 packaged; health-gated activation on merge.

Closes #40, closes #41, closes #42
2026-09-03 16:23:57 -05:00
jason.woltje e14ad9ab52 Merge: skill lifecycle, skills completion, M16 self-determination hardening, M20 decision
- skill.sh lifecycle (install/activate/deactivate/uninstall/list)
- 8 ms-* skills completed (owner canon preserved)
- seat skills dispatch + enabled-dir resolution
- M16: ensure at launch, drift warnings, recursion guard
- M20 decision: packages/* monorepo at usurpation

Closes #40, closes #41, closes #42
2026-09-03 16:03:05 -05:00
jason.woltje 9fd16b9739 feat(release): recursion guard for the health gate; run-task drift warning; M20 packages/* decision recorded (#39)
- release.sh health gate runs with MOSAIC_ENSURE_SKIP=1: the gated task run
  cannot re-enter release self-determination
- run-task.sh warns on release drift instead of silently using a stale image
- ROADMAP: M20 decision recorded (packages/* monorepo at usurpation,
  continuity-first); restructure sequenced as M20 phase 1

Closes #39
2026-09-03 15:58:46 -05:00
jason.woltje 9051ad179b docs(roadmap): restructure sequencing (M20 phase 1, not first) + skills-as-discipline doctrine 2026-09-03 14:50:59 -05:00
jason.woltje 2f649ed930 docs: README release ensure row 2026-09-03 14:43:25 -05:00
jason.woltje db330c12c7 feat(release): self-determination - ensure at launch, drift warnings, M20 packages/* decision (#38)
- release.sh ensure: aligned no-op; drift -> package-if-needed + health-gated
  activate (M3 gate-then-flip, automated)
- ensure_release_aligned in common.sh: invoked by hello/verify/agent;
  MOSAIC_ENSURE_SKIP guards recursion; run-task warns on drift without
  auto-aligning (workers/suites never trigger builds or model gates)
- ROADMAP: M20 decision recorded - v2 adopts packages/* monorepo at usurpation
- BUILD-LOG Phase 20 + tool-race process note

Live-verified: post-reset pointer loss auto-restored via health-gated
ensure; drift warning fires on desired-version bump; idempotent no-op on
aligned state.

Closes #38
2026-09-03 14:42:09 -05:00
jason.woltje 0a17d29bce docs(roadmap): flush 2026-09-03 14:28:58 -05:00
jason.woltje 3458f6a7ad docs(roadmap): M17 skill lifecycle (skills-enabled/available, role-scoped subsets, --skill negates --no-skills); M20 stack succession path + monorepo question 2026-09-03 14:28:45 -05:00
jason.woltje b033952cd5 docs(harvest): pattern ledger from stack/next + fleet runtime (12 patterns, skips, owner-corrected M17-M19 designs) 2026-09-03 13:55:30 -05:00
jason.woltje c102980ad4 docs(plan): CURRENT.md - queue aligned to ROADMAP (M16 next, CI deferred) 2026-09-03 12:59:28 -05:00
jason.woltje 58b96cb715 docs(plan): ROADMAP.md - M16 release self-determination, M17 ms-tools skill, M18 seat-role restriction, M19 auth tooling; CI deferred per owner 2026-09-03 12:57:26 -05:00
jason.woltje dd3ff944a1 chore(release): 0.0.11 2026-09-03 12:30:52 -05:00
jason.woltje 8eb81ebec1 feat(onboard): user onboarding - no default USER.md, guided creation (#37)
- bootstrap no longer creates user/USER.md (owner direction)
- scripts/onboard.sh: name REQUIRED (interactive loop or --name),
  optional fields prompted (profession, marital, age, gender, education,
  location, timezone, skillset, interests, hobbies, pets); flag-driven
  non-interactive mode for automation
- templates/USER.md: canon skeleton, placeholder rendering, unfilled
  optional = (not provided)
- agent.sh: auto-runs onboarding when profile missing (TTY gate);
  headless run-task warns and continues without user context
- user profile dispatched to all launches (M14 layer)

Closes #37 (onboarding requirements from owner layout review)
2026-09-03 12:30:42 -05:00
jason.woltje 530597cc84 docs(plan): CURRENT.md flush 2026-09-03 11:59:49 -05:00
jason.woltje 9a0d44f96a docs(plan): CURRENT.md - deduplicated completed log (marked correction), M15 review queued 2026-09-03 11:59:37 -05:00
jason.woltje 121b331c6c docs(log): back-fill phases 16-19 (M10, M12, M14, M15) - recorded retroactively with ground-truth sources 2026-09-03 11:59:14 -05:00
jason.woltje 34e06e7de7 Merge M15: agent seats - per-agent SOUL and role contracts
Closes #36
2026-09-03 11:56:38 -05:00
jason.woltje 9bd4f1c405 feat(agents): agent seats - per-agent SOUL, role, definitions dir (#36)
- agents/<name>/ holds agent.json (strictly validated: version, name,
  role?, capabilities?, workspace?, session?) + SOUL.md (persona prose)
- agent.sh: definition loading (quote-safe node defaults file), runtime
  SOUL copy to dataRoot/agents/<name>/, MOSAIC_AGENT_SOUL_FILE ->
  loader fills the SOUL slot from the seat's persona (contract SOUL =
  default persona; governance never overridden)
- seat.json written once at instantiation (seatVersion, name, role, at)
- identity section gains agent role; compose passthrough for role+SOUL
- live user context (M14) + seat SOUL compose the full persona:
  governance -> persona -> identity -> user -> mission
- RELEASE -> 0.0.10; packaged and health-gated activated
- example seat committed: agents/researcher

Closes #36
2026-09-03 11:56:38 -05:00
jason.woltje a7b612435b docs: BUILD-LOG Phase 15, CURRENT.md - M13 shipped 2026-09-03 11:25:41 -05:00
jason.woltje 87f10772ce Merge M13: interactive TUI agent + TOOLS.md
Closes #35
2026-09-03 11:24:56 -05:00
jason.woltje 7db4c5c2ed feat(agent): interactive TUI launcher + identity + TOOLS.md (#35)
- scripts/agent.sh <name>: launches interactive pi TUI in the container
  with contracts + optional mission + agent identity + named session +
  optional workspace/tools; the Mosaic alternative to vanilla pi
- pi adapter: MOSAIC_INTERACTIVE branch (clean TUI, no -p, no initial
  prompt); headless exec rebuilt via positional args (no word-splitting
  on the request); MOSAIC_AGENT_NAME optional in headless
- loader: AGENT IDENTITY section when the launcher names the agent
- compose: fixed command removed (request defaults live in run-agent.sh);
  MOSAIC_INTERACTIVE/MOSAIC_AGENT_NAME passthrough
- docs/TOOLS.md: full on-demand tool reference; AGENTS.md routes to it
- RELEASE -> 0.0.8 (container change); build verified

Closes #35
2026-09-03 11:24:56 -05:00
jason.woltje 0273a84549 docs: AGENTS.md - session recovery shim, invariants canon, session registry
- AGENTS.md at root: pi loads it automatically at every session start
  (conductor-level sessions; workers deliberately exclude it via
  --no-context-files). Deliberately short: invariants, session protocol,
  role model, command surface, data map, pointers - depth stays in docs/.
- docs/SESSIONS.md: append-only session registry, mandatory per session.
- Recovery rule encoded: compaction/restart loses nothing - AGENTS.md +
  CURRENT.md + git log + suites reconstruct state; never guess.
2026-09-03 11:02:19 -05:00
jason.woltje 3b674b7a66 Merge: roles/ directory convention - root is bootstrap-only 2026-09-03 10:56:07 -05:00
jason.woltje 527bc581ca refactor(layout): role contracts move to roles/ - root is bootstrap-only
Owner direction: the repository root holds first-class, bootstrap-required
configuration only. conductor-policy.json is a ROLE contract (the
conductor's authority), one of scores of future role contracts
(agent-policy, coder-policy, ...) - such files get a dedicated home.

- roles/conductor-policy.json (git mv)
- conductor-apply.sh + test-conductor.sh read the new path
- CONDUCTOR.md records the roles/ convention

Closes UX follow-up from owner layout review; no issue (convention change).
2026-09-03 10:56:07 -05:00
jason.woltje 1249714a9a docs(plan): CURRENT.md - M12 shipped 2026-09-03 07:04:37 -05:00
jason.woltje e175616885 feat(conductor): auto-apply policy gate for worker patches (#34)
- conductor-policy.json (tracked, strictly validated): enabled switch,
  path allowlist globs, gating suites - the autonomy decision lives in a
  declarative file the owner controls
- scripts/conductor-apply.sh <runId> [--dry-run]: succeeded-run check ->
  clean target tree -> diff from worker workspace -> allowlist -> syntax
  gates (node/bash/json) -> apply -> policy suites -> attribution commit;
  ANY failure reverts the tree; push is never automatic
- scripts/test-conductor.sh: 17 sandbox cases covering every gate incl.
  suite-failure auto-revert and disabled policy
- policy defaults: scripts/docs/tasks/missions/adapters + README; all
  three suites gate

Closes #34
2026-09-03 07:03:29 -05:00
jason.woltje 6955717612 Merge M11: session forking from a common ancestor
Closes #33
2026-09-03 06:43:02 -05:00
jason.woltje 88d9cf750f feat(sessions): sessionForkFrom - branch conversations from a common ancestor (#33)
- task schema: optional sessionForkFrom (source session name); requires
  session target; self-fork rejected
- runner: resolves source newest .jsonl (fail 4 if none/outside dataRoot);
  passes MOSAIC_SESSION_FORK + MOSAIC_SESSION_DIR; result records lineage
- pi adapter: --fork <source> --session-dir <target> when forking;
  ephemeral default unchanged; plain session resume unchanged
- compose passthrough; RELEASE -> 0.0.7 (adapter changed)
- suite +9 cases (58 total): plumbing via mock stderr, validation
  negatives, live fork - child recalls ancestor code word, ancestor
  session file untouched

Closes #33
2026-09-03 06:43:02 -05:00
jason.woltje c038706eed docs(plan): CURRENT.md - M10 shipped, retention next in review 2026-09-03 06:33:54 -05:00
jason.woltje 8622c9d826 Merge M10: run-record retention
Closes #32
2026-09-03 06:33:27 -05:00
jason.woltje 88eef507b0 feat(retention): run-record pruning - keep newest N, dry-run default (#32)
- mosaic-task.mjs prune [--keep=N] [--yes]: default keep 50; without
  --yes lists candidates without deleting
- only r-* directories under the runs root; symlinks skipped;
  sessions/workspaces/state/config untouched (asserted by suite sentinels)
- append-only receipt runs/.pruned.log records every pruned id
- test-task.sh: +8 retention cases (dry-run no-delete, keep-N, newest
  kept, receipt, isolation, invalid keep, empty no-op)

Also: suite hardening - prune section scopes its config per-command
(no export/unset leaking into later sections); duplicated check()
removed; latest_reason hoisted to helpers; status colors now green OK /
red FAIL (terminal-only, NO_COLOR-aware) per owner UX feedback.

Closes #32
2026-09-03 06:33:27 -05:00
jason.woltje 439bea6915 ui(test): green OK/PASS, red FAIL - terminal-only, NO_COLOR-aware
Owner feedback: grep match-highlighting made the word 'policy' red while
status words were plain - counter-indicative. Suites + verify now emit
ANSI colors (green success, red failure) when stdout is a terminal;
piped/machine-parsed output stays plain, honoring NO_COLOR. Word 'ok'
promoted to 'OK' for scannability.

Verified byte-level via forced-pty run; piped output unchanged; suites
41/24/14 + verify green.
2026-09-03 06:23:47 -05:00
jason.woltje fad8a4718c Merge M9: mission-level capability policy
Closes #30
2026-09-03 06:16:01 -05:00
jason.woltje 2ff49adff4 feat(policy): mission-level capability policy - least-privilege intersection (#30)
- mission schema: optional capabilities.tools (same validation as task)
- merge semantics in runTask: neither -> none; mission only -> mission;
  task only -> task; both -> intersection (task narrows, never widens);
  empty intersection -> tool-free run with an explicit stderr note
- result.json records EFFECTIVE tools; task/mission snapshots remain the
  immutable declaration of intent
- adapters unchanged; host-side only (no image change, 0.0.6 still active)
- task suite +5 cases (41 total): all four merge cases asserted from run
  evidence + invalid mission capabilities rejected

Policy decision recorded: missions govern; tasks cannot escalate.

Closes #30
2026-09-03 06:16:01 -05:00
jason.woltje 44c476ebbf fix(ops): show displays retriedFrom lineage (#29)
result.json recorded lineage correctly; the human-facing show command
omitted the field. Found by owner test: show | grep retriedFrom was
empty on a run whose result.json contained it.

Closes #29
2026-09-03 06:09:51 -05:00
jason.woltje cde480eb60 docs(plan): CURRENT.md — retry lineage shipped, M9 queued for decision 2026-09-03 05:31:24 -05:00
jason.woltje 5808248707 Merge retry lineage + relative mission resolution
Closes #28
2026-09-03 05:30:57 -05:00
jason.woltje afd5827db8 fix(retry): lineage tracking + relative mission path resolution (#28)
- retryRun rewrites a snapshot's relative mission path to the run's own
  recorded mission.json (absolute) before execution — retries stay
  faithful to what originally ran
- runTask accepts options.retriedFrom; retry records lineage in
  result.json (additive optional field, no schema break)
- task suite +4 cases: retry succeeds, lineage recorded, mission section
  present after retry (36 total), missing-run retry exits 4

Closes #28
2026-09-03 05:30:57 -05:00
jason.woltje d9cc990376 Merge M8: conductor loop - self-orchestration
Closes #25, closes #26, closes #27
2026-09-02 22:42:52 -05:00
jason.woltje 83c4e9851e feat(orchestration): retry <runId> — authored by headless pi worker (#26, #27)
Collaboration record (conductor loop, docs/plans/CONDUCTOR.md):
- round 1 (worker session worker-1, 2m28s): retry implemented per spec
- conductor live test exposed spec gap: direct invocation lacked
  launcher env exports
- round 2 (same worker session, 59s): spawnEnv made self-sufficient,
  but used PI_* where compose interpolates MOSAIC_*
- conductor hotfix: 3-line rename to MOSAIC_PROVIDER/MOSAIC_MODEL/
  MOSAIC_DATA_ROOT

Final: node scripts/mosaic-task.mjs retry <runId> re-executes a run's
task snapshot as a new run; live retry replied REMEMBERED; all suites
green (24/32/14 + verify).

Known limitation: retrying a run whose task used a RELATIVE mission path
resolves it against the temp dir; lineage tracking deferred.

Closes #25, closes #26, closes #27
2026-09-02 22:42:52 -05:00
jason.woltje 22508170a2 docs(plan): CURRENT.md — single next-action pointer for cadence-driven work 2026-09-02 22:25:56 -05:00
jason.woltje 90a67d050e Merge M7: operator ergonomics + release 0.0.6
Closes #24
2026-09-02 22:14:18 -05:00
jason.woltje 24bdef75fa feat(ops): run inspection, release 0.0.6, docs (#24)
- mosaic-task.mjs show <runId>: full record + snapshots + artifacts;
  uppercase-tolerant id validation; missing/traversal ids exit 4
- list: task/workspace/session columns
- RELEASE -> 0.0.6; README workspaces/capabilities/sessions sections;
  BUILD-LOG Phases 9-11; autonomous-run tracker results filled

Closes #24
2026-09-02 22:14:18 -05:00
jason.woltje 4e2a413640 Merge M6: named sessions - persistence and resume
Closes #22, closes #23
2026-09-02 22:08:34 -05:00
jason.woltje be55549700 feat(sessions): named persistent sessions with resume (L1) (#22, #23)
- task schema: optional session (named id) -> persistent session dir at
  dataRoot/sessions/<name>, isolated per name
- pi adapter: --session-dir when declared (ephemeral --no-session stays
  the default otherwise); -c resumes the most recent session when present
- compose passthrough; result.json records session
- fixtures: tasks/session-demo-1.json (teach) + session-demo-2.json (recall)
- E2E: teach -> REMEMBERED + host-side session JSONL; resume -> recalled
  'mosaico' exactly; single continued session file

Closes #22, closes #23
2026-09-02 22:08:34 -05:00
jason.woltje ddb1554e5b Merge M5: task workspaces + capability envelope
Closes #20, closes #21
2026-09-02 22:06:43 -05:00
jason.woltje b017e66e17 test(capabilities): workspace/tooling selftests + live demo fixture (#21)
- mock plumbing cases: workspace path + tools delivered (asserted from
  run-record stderr), host workspace created, absent fields = empty vars
- validation negatives: unknown tool, workspace traversal
- tasks/workspace-demo.json: pi uses bash inside the persistent demo
  workspace; host-visible proof.txt verified live

Closes #21
2026-09-02 22:06:43 -05:00
jason.woltje 172368612c feat(capabilities): task workspaces + tools allowlist plumbing (#20)
- task schema: optional workspace (absent | :run ephemeral | named
  persistent under dataRoot/workspaces) and capabilities.tools (pi
  documented tool allowlist); strict validation, traversal-proof names
- runner: creates host workspace, passes MOSAIC_WORKSPACE (container
  path) + MOSAIC_TOOLS; result.json records both
- pi adapter: cds into workspace; --tools when allowlist present else
  --no-tools
- mock adapter: logs delivered MOSAIC_* vars to stderr as deterministic
  plumbing evidence (dash prints 'export K=v', so use env not export)

Closes #20
2026-09-02 22:03:46 -05:00
jason.woltje 1387231e57 docs(plan): autonomous work run tracker (M5-M7 scope, test plan, review checklist) 2026-09-02 21:57:30 -05:00
jason.woltje 0292392e64 Merge M4: runtime adapter seam
Closes #16, closes #17, closes #18, closes #19
2026-09-02 21:30:35 -05:00
jason.woltje 594b8d711c docs(adapters): adapter seam docs + recorded M4 E2E, release 0.0.5 (#19)
- README: Runtime adapters section (contract summary, selection, mission
  injection point); BUILD-LOG Phase 8 entries
- E2E: 24+24+14 selftests green; verify PASS; 0.0.5 packaged and
  health-gated activated; mission-bearing fixture task succeeded through
  the real pi adapter; config checksum unchanged

Closes #19
2026-09-02 21:30:35 -05:00
jason.woltje 3c1ffd2c2d test(adapters): seam selftests — deterministic mock cases + mission injection (#18)
- test-config: absent adapter defaults to pi; mock validates; unknown
  adapter exits 2; env exports MOSAIC_ADAPTER (24 cases total)
- test-task: mock adapter gate pass/mismatch (no provider needed),
  mismatch reason asserted, unknown adapter fails closed, mission section
  injected into generated prompt asserted by content (24 cases total)
- harness fixes: helpers defined before use; per-case config files (no
  cross-case leakage); newest-run selection for the live case; deduped
  accidentally duplicated live block

Closes #18
2026-09-02 21:28:57 -05:00
jason.woltje 4ebb123ba3 feat(adapters): sanctioned mission directives injection (#17)
- load-contracts.sh: MOSAIC_MISSION_FILE (readable) appends a MISSION
  (runtime) section — objective + directives — after the immutable
  contracts; unreadable path is a hard error, absent env changes nothing
- mosaic-task.mjs: exports MOSAIC_MISSION_FILE as the run snapshot's
  container path (/var/lib/mosaic/runs/<id>/mission.json), with an
  outside-dataRoot guard; also exports the configured adapter

Verified: contract-only prompt has no mission section; mission-bearing
run shows objective + directives in the generated prompt, snapshot
recorded, real provider returns exactly MOSAIC_HELLO_OK.

Closes #17
2026-09-02 21:19:22 -05:00
jason.woltje bb5cecb348 feat(adapters): adapter contract, dispatch, pi + mock adapters (#16)
- adapters/README.md: the harness boundary contract (env in, response on
  stdout, diagnostics stderr, exit 0 success)
- adapters/pi: extracted current invocation unchanged
- adapters/mock: deterministic MOSAIC_MOCK_RESPONSE echo (test-only)
- run-agent.sh: name-validated dispatch to adapters/<name>/adapter.sh
- config: optional execution.adapter (pi|mock), default pi, configVersion
  stays 1 — existing configs remain valid; selection authority is the
  config file (load_config exports it)
- compose: MOSAIC_ADAPTER / MOSAIC_MOCK_RESPONSE passthrough; Containerfile
  installs adapters read-only; RELEASE -> 0.0.5

Verified: hello unchanged; mock verbatim via config; unknown adapter and
path-traversal names refused in-container; invalid adapter exits 2.

Closes #16
2026-09-02 21:18:07 -05:00
jason.woltje 88f55d9135 test(task): live failures self-report evidence; wrong-exit no longer masks (#15)
- live hello failure dumps latest run result.json + stderr tail before
  sandbox cleanup destroys them
- wrong-expectExact case asserts reason == expect-mismatch (was: any
  exit 1, which masked compose-level failures)
- repair dangling if/else from the docker-guard refactor

Closes #15
2026-09-02 20:56:58 -05:00
jason.woltje e7e1bd26eb fix(launcher): resolve release identity for direct task runs (#14)
M3 made MOSAIC_IMAGE_TAG required in compose, but run-task.sh never
called load_release — direct task runs failed in compose before any
model call. release.sh paths masked it by exporting the tag to children.

Found by owner-run test-task.sh; failure receipts were in the run
records' stderr.txt.

Closes #14
2026-09-02 20:44:31 -05:00
jason.woltje 5d86b8fa93 Merge M3: release model and safe updates
Closes #10, closes #11, closes #12, closes #13
2026-09-02 20:24:33 -05:00
jason.woltje 35ea464661 docs(release): release model usage + recorded M3 drills (#13)
- README: Release model section (package/activate/rollback/status,
  pointer + append-only log, gate-then-flip guarantee)
- BUILD-LOG Phase 7: drills recorded (update, refusal, rollback),
  two harness/product corrections documented

Drill evidence: 0.0.3 -> 0.0.4 update with unchanged config checksum and
green verify; fault-injected refusal left pointer untouched; health-gated
rollback restored 0.0.3; full append-only event history.

Closes #13
2026-09-02 20:24:33 -05:00
jason.woltje e87ecdb3e5 test(release): release-layer selftests (#12)
14 cases: RELEASE validation (valid/invalid/missing), tag consistency,
status on empty state, fault-injected refusal with no pointer + single
valid refusal log line, healthy activation, pointer fields, repeat
activation append-only log, rollback-without-previous refusal.

Harness fix learned the hard way: restore RELEASE from backup inline
after the missing-file case (mv-back restored the mutated file); single
exit trap self-heals the repo state.

Closes #12
2026-09-02 20:23:18 -05:00
jason.woltje a947db7bfd feat(release): package/activate/rollback/status with health gate (#11)
- activate: image-presence pre-check + M2 task-runner health gate
  (tasks/hello-marker.json exact marker) before atomic pointer replace
  (tmp+rename); every attempt appended to activation-log.jsonl
- --fault-injection flips the health expectation to prove the refusal path
- rollback: health-gated re-activation of the previous activated imageTag
  from the log; refuses when the image is gone or no previous exists
- status: release, tag, pointer, recent log; safe on empty state
- state lives under <dataRoot>/state/ (config-independent, reset-scoped)

Verified: activate OK; fault-injected refuse with pointer unchanged;
rollback-without-previous refuse.

Closes #11
2026-09-02 20:20:32 -05:00
jason.woltje a35ea62ab1 feat(release): RELEASE identity + image tag single-sourcing (#10)
- RELEASE file: single source of release version (0.0.X until declared stable)
- common.sh load_release(): validates version, derives
  MOSAIC_IMAGE_TAG=mosaic-poc-agent:<pi>-r<release> from the pinned pi dep
- compose.yaml: image tag is required env; build/hello/verify call load_release
- verify.sh derives the image name instead of hardcoding it
- package.json version aligned to the same 0.0.X line

Closes #10
2026-09-02 20:18:17 -05:00
jason.woltje f6c93dcb5c Merge M2: mission and task abstraction
Closes #6, closes #7, closes #8, closes #9
2026-09-02 19:58:53 -05:00
jason.woltje 7f0408d417 feat(task): fixture mission/task, docs, recorded M2 E2E (#9)
- missions/hello.json + tasks/hello-marker.json committed fixtures
- README: Missions & tasks section (usage, run record layout, M2 scope note)
- BUILD-LOG: Phase 6 before/after entries

E2E: fixture run succeeded with exactly MOSAIC_HELLO_OK; wrong expectExact
recorded status failed (expect-mismatch) and exited 1; runs listed; config
checksum unchanged.

Closes #9
2026-09-02 19:58:53 -05:00
jason.woltje cfd2a19bd7 test(task): mission/task selftests — schema negatives + live runs (#8)
18 cases: validation negatives (unknown keys, versions, ids, prompt,
expectExact NUL, timeout range, missing/invalid mission, writes-nothing)
plus live cases: exact-marker success, wrong expectExact fails, distinct
run dirs, result.json contents, list output.

Closes #8
2026-09-02 19:57:34 -05:00
jason.woltje d2a9e26395 feat(task): mission/task schemas, validation, and run runner (#6) (#7)
- scripts/mosaic-task.mjs: validate | run | list
- Strict v1 schemas: unknown keys rejected; ids/prompt/expectExact/
  timeoutSeconds bounds enforced; optional mission file resolved against
  the task file and validated too
- run: executes through the config-driven container path with stdin
  detached (issue #5 class), SIGKILL timeout (default 120s), trimmed
  response capture
- Immutable run records under <dataRoot>/runs/r-<utcstamp>-<rand>/:
  task.json + mission.json snapshots (write-once), stderr.txt, result.json
- expectExact gate: mismatch -> status failed, exit 1; result.json is
  always written
- scripts/run-task.sh: load_config + bootstrap_runtime_dir before exec
- M2 scope: mission directives are snapshotted for provenance, not yet
  injected into the runtime prompt (later policy layer)

Closes #6, closes #7
2026-09-02 19:56:29 -05:00
jason.woltje 0734b1f3a5 fix(launcher): detach stdin on agent container run (#5)
pi print mode reads piped stdin until EOF; an attached terminal stdin
blocked the one-shot run forever. Automated contexts (closed stdin)
never exposed it. Request text comes from the compose command.

Proven: tail -f /dev/null | scripts/hello.sh now returns MOSAIC_HELLO_OK
in ~4s (previously timed out at 30s); verify.sh remains green.

Closes #5
2026-09-02 19:50:07 -05:00
jason.woltje ce6420f3de Merge M1: configuration-driven Hello World
Closes #1, closes #2, closes #3, closes #4
2026-09-02 18:32:50 -05:00
jason.woltje 81f58b15c8 fix(launcher): ensure configured data root before verify mount; docs for M1 (#4)
- verify.sh now calls bootstrap_runtime_dir after load_config; previously a
  reset-then-verify flow let Docker auto-create a root-owned mount source
- common.sh: fail with clear guidance when data root exists but is not writable
- README: configuration section, bootstrap usage, selftest entry point
- BUILD-LOG: Phase 5 entries with corrections

E2E (clean slate): 20/20 selftests; bootstrap idempotent; config-driven
hello/verify MOSAIC_HELLO_OK; negative marker exit 1; reset + rerun green;
config checksum unchanged across the entire flow.

Closes #4
2026-09-02 18:32:50 -05:00
jason.woltje 0c2113f710 test(config): sandboxed config-layer selftests (#3)
20 cases: bootstrap create/idempotency, missing config, malformed JSON,
unknown keys/version/backend/environment, relative and non-canonical
dataRoot, filesystem root, home dir, ancestor-of-config, control chars,
symlinked config file, env export resolution, validation-writes-nothing.

Closes #3
2026-09-02 18:30:46 -05:00
jason.woltje 900a506c1f feat(config): wire launcher scripts and compose to config.json (#2)
- common.sh: load_config() exports MOSAIC_DATA_ROOT/PROVIDER/MODEL; fails closed
- compose.yaml: dataRoot mount and provider/model are required env (:? errors)
- build/hello/verify load config before any mutation; no silent bootstrap
- reset.sh: target resolved from configured dataRoot; all safety checks kept

Verified: compose fails without launcher env; verify/reset fail on missing
config; config-driven hello+verify pass; symlink refusal with sandboxed
config (canary survived); config checksum unchanged across reset+rerun.

Closes #2
2026-09-02 18:30:08 -05:00
jason.woltje c3d29e796a feat(config): config module with idempotent bootstrap and strict v1 validation (#1)
- scripts/mosaic-config.mjs: bootstrap | validate | env operations
- Exclusive creation (O_EXCL 'wx'); existing config validated, never rewritten
- Strict schema: unknown keys rejected, configVersion===1, backend docker only
- dataRoot guards: absolute, canonical, not root/home/ancestor-of-config
- MOSAIC_CONFIG override for sandboxed tests; exit codes 0/2/3
- scripts/bootstrap.sh: explicit bootstrap entry point

Closes #1
2026-09-02 18:28:14 -05:00
jason.woltje c2365ae519 chore: baseline container POC and atomic foundation plan
- Containerized Pi hello-world proof (image mosaic-poc-agent:0.84.4, non-root)
- Four immutable contract fixtures loaded into a generated system prompt
- build/hello/verify/reset scripts with exact-match gating and reset safety
- Documented Pi discovery (v0.84.4, -p mode, --system-prompt, container auth)
- Append-only BUILD-LOG with corrections; deferred layers in LAYERS.md
- Architecture plan: docs/plans/2026-09-02_atomic-mosaic-foundation.md
2026-09-02 18:24:36 -05:00
orch-01 d6302f8e6f docs: make Portainer optional deployment path (#1492)
ci/woodpecker/push/publish Pipeline was successful
2026-09-02 23:23:07 +00:00
marcieandorch-01 9aa4983cf2 fix: use canonical dogfood seat identity (#1490)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: marcie <[email protected]>
2026-08-30 23:43:22 +00:00
marcieandorch-01 736b0affc1 compose: add wrapper-first dogfood workspace (#1488)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: marcie <[email protected]>
2026-08-30 22:29:30 +00:00
orch-01 e18d13d36f fleet: split-home-safe mosaic launcher (T110, P5-RM-009 stack side) (#1480)
ci/woodpecker/push/publish Pipeline was successful
2026-08-30 10:05:40 +00:00
marcieandorch-01 60bc5d2022 compose: pin MOSAIC_STORAGE_TIER=standalone (A5d formal-run fix) (#1486)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: marcie <[email protected]>
2026-08-30 07:49:35 +00:00
marcie ea91cfc421 compose: stack profile — one-command standalone deployment (A5) (#1485)
ci/woodpecker/push/publish Pipeline was successful
2026-08-30 06:03:39 +00:00
marcie acf640d00f docs: containerization plan + PRD D15 (tiered deployment, standalone v1 bar) (#1484)
ci/woodpecker/push/publish Pipeline was successful
2026-08-30 05:21:42 +00:00
fredandmarcie 431ead3a18 feat(gateway,cli): agent enrollment command family (M4-4b) (#1483)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: fred <[email protected]>
2026-08-30 04:30:05 +00:00
fred 143ba0f57a db: agent enrollment schema (M4-4a, migration 0021) (#1482)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-30 01:47:06 +00:00
fred ee815a72b1 docs: agent enrollment command family v1 design (M4-4-0) (#1481)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-30 01:21:00 +00:00
marcieandorch-01 94d626dff9 mosaic comms: socket resolution is tool-owned (B2) (#1476)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: marcie <[email protected]>
2026-08-29 23:35:37 +00:00
marcieandorch-01 ee6c842918 R3: --body-file <path>/- across body/comment carriers (#1474)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: marcie <[email protected]>
2026-08-29 22:49:20 +00:00
marcieandorch-01 ba3b854d50 D1/D3: --number canonical on issue wrappers, --labels alias on list wrappers (#1475)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: marcie <[email protected]>
2026-08-29 22:29:16 +00:00
fred c7a7fd07cc brain/gateway: prohibit mission_tasks.status as a write source (M4-3a phase 1) (#1479)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-29 21:59:13 +00:00
fred 5399c6b7e7 docs: P0 field-map currency verification at next@abb0c936 (M4-3a-0) (#1478)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-29 21:17:05 +00:00
marcieandorch-01 e09b8783b4 P1b: read-only viewers join the R1/R4 usage contract (#1472)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: marcie <[email protected]>
2026-08-29 21:03:07 +00:00
marcieandorch-01 abb0c93601 framework tools/tmux: agent-send socket default resolution + ambiguity guard (B1) (#1466)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: marcie <[email protected]>
2026-08-29 20:25:50 +00:00
marcieandorch-01 e67cced273 mosaic fleet logins: per-seat credential pass-through (P4 gap closure) (#1473)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: marcie <[email protected]>
2026-08-29 20:05:23 +00:00
fred 635cb1f666 docs: contract 2 Amendment 1 — company-CRUD capability (S2 follow-up) (#1477)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-29 19:34:48 +00:00
marcieandorch-01 09d24b9275 pr-merge --base-line: gated intra-line exception (B5) (#1471)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: marcie <[email protected]>
2026-08-29 18:14:40 +00:00
marcieandorch-01 ed4c543872 mosaic coord: board subcommand + roll alias (P3 gap closure) (#1470)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: marcie <[email protected]>
2026-08-29 17:52:30 +00:00
fred 215faeda0a feat(hierarchy): M4-1b-ii hierarchy command family, grant evaluation, visibility (#1465)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-29 16:54:39 +00:00
orch-01 5125fe21b0 p0 line landing: coord board/roll + wrapper usage contract + dispatch layer (successor to #1467) (#1468)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: orch-01 <[email protected]>
2026-08-29 16:24:08 +00:00
marcieandorch-01 41e8046371 framework tools/git: issue-comment R1/R4 usage-error contract (sync from brain) (#1462)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: marcie <[email protected]>
2026-08-29 01:58:42 +00:00
fred 6e16675ea2 docs: deployment mode and conversion contract (S2 contract 6) (#1439)
ci/woodpecker/push/publish Pipeline was successful
2026-08-28 22:57:11 +00:00
fred 19ebc422aa docs: tool-gateway mapping contract (S2 contract 5) (#1438)
ci/woodpecker/push/publish Pipeline was successful
2026-08-28 22:04:03 +00:00
fred bd749831b1 docs: company visibility classes (Ruling 4b amendment, contracts 1+3) (#1461)
ci/woodpecker/push/publish Pipeline was successful
2026-08-28 20:17:35 +00:00
fred f8e1b43b5b docs: RBAC grant model contract (S2 contract 2) (#1436)
ci/woodpecker/push/publish Pipeline is running
2026-08-28 19:09:17 +00:00
fred 2148c20d26 feat(hierarchy): audit event + outbox machinery (M4-1b-i, contract 1 §5.2) (#1460)
ci/woodpecker/push/publish Pipeline was successful
2026-08-28 02:22:53 +00:00
fred 5964dab891 feat(db): hierarchy record class schema + witnesses (contract 1, M4-1a) (#1459)
ci/woodpecker/push/publish Pipeline was successful
2026-08-28 00:42:35 +00:00
orch-01 bdb903cf69 docs: T78 official CLI capability migration contract (#1458)
ci/woodpecker/push/publish Pipeline was successful
2026-08-27 19:53:51 +00:00
fred bec2eb118b docs: API contract artifacts contract (S2 contract 9) (#1443)
ci/woodpecker/push/publish Pipeline was successful
2026-08-27 19:18:39 +00:00
fred 07624140e4 docs: hierarchy schema contract (S2 contract 1, D2) (#1435)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-27 16:39:15 +00:00
fred 1c79af25d4 fix(mosaic): retry the Invariant R pi version probe under CI load (#1441)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-27 16:38:53 +00:00
fred e605c83b27 ci(web): Phase P6 — vite build + headless E2E gate on every trunk merge (#1445) (#1454)
ci/woodpecker/push/publish Pipeline was successful
2026-08-27 15:26:10 +00:00
fred b5ee692843 P5: SPA cutover — retire Next.js, gateway serves the Vite bundle (#1444) (#1453)
ci/woodpecker/push/publish Pipeline was successful
2026-08-27 13:06:49 +00:00
fred bf8bc2128d fix(docker): copy scripts/ into appservice builder before pnpm install (#1452)
ci/woodpecker/push/publish Pipeline was successful
2026-08-27 11:28:47 +00:00
fred 01904b8f69 docs: custody pointer and consent schema contract (S2 contract 7) (#1440)
ci/woodpecker/push/publish Pipeline failed
2026-08-27 10:39:32 +00:00
fred 676900bd46 docs: onboarding wizard contract (S2 contract 3) (#1437)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-27 10:39:23 +00:00
fred a3b0770205 docs: roll-up projection contract (S2 contract 8) (#1442)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-27 02:10:12 +00:00
4337 changed files with 787562 additions and 6234 deletions
+20 -149
View File
@@ -1,153 +1,24 @@
# ─────────────────────────────────────────────────────────────────────────────
# Mosaic — Environment Variables Reference
# Copy this file to .env and fill in the values for your deployment.
# Lines beginning with # are comments; optional vars are commented out.
# ─────────────────────────────────────────────────────────────────────────────
# Non-secret runtime settings for the mosaic-poc-agent container.
# Copy to .env if you want to override the defaults in compose.yaml.
#
# NEVER put credentials in this file. Authentication is supplied at
# runtime only, via one of the two documented paths:
# 1. read-only mounted pi auth file (default: ~/.pi/agent/auth.json,
# override the host path with PI_AUTH_FILE)
# 2. provider API key environment variable (ZAI_API_KEY or
# ANTHROPIC_API_KEY), passed through by compose.yaml when set
# Model provider (built-in pi provider name)
PI_PROVIDER=zai
# ─── Database (PostgreSQL 17 + pgvector) ─────────────────────────────────────
# Full connection string used by the gateway, ORM, and migration runner.
# Port 5433 avoids conflict with a host-side PostgreSQL instance.
DATABASE_URL=postgresql://mosaic:mosaic@localhost:5433/mosaic
# Model ID within the provider
PI_MODEL=glm-5.3-flash
# Docker Compose host-port override for the PostgreSQL container (default: 5433)
# PG_HOST_PORT=5433
# Optional: alternative host path of the pi credential file mounted
# read-only at /home/node/.pi/agent/auth.json in the container
#PI_AUTH_FILE=/home/jwoltje/.pi/agent/auth.json
# ─── Queue (Valkey 8 / Redis-compatible) ─────────────────────────────────────
# Port 6380 avoids conflict with a host-side Redis/Valkey instance.
VALKEY_URL=redis://localhost:6380
# Docker Compose host-port override for the Valkey container (default: 6380)
# VALKEY_HOST_PORT=6380
# ─── Gateway ─────────────────────────────────────────────────────────────────
# TCP port the NestJS/Fastify gateway listens on (default: 14242)
GATEWAY_PORT=14242
# Comma-separated list of allowed CORS origins.
# Must include the web app origin in production.
GATEWAY_CORS_ORIGIN=http://localhost:3000
# ─── Auth (BetterAuth) ───────────────────────────────────────────────────────
# REQUIRED — random secret used to sign sessions and tokens.
# Generate with: openssl rand -base64 32
BETTER_AUTH_SECRET=change-me-to-a-random-32-char-string
# Public base URL of the gateway (used by BetterAuth for callback URLs)
BETTER_AUTH_URL=http://localhost:14242
# ─── Web App (Next.js) ───────────────────────────────────────────────────────
# Public gateway URL — accessible from the browser, not just the server.
NEXT_PUBLIC_GATEWAY_URL=http://localhost:14242
# ─── OpenTelemetry ───────────────────────────────────────────────────────────
# OTLP HTTP endpoint (otel-collector or any OpenTelemetry-compatible backend)
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# Service name shown in traces
OTEL_SERVICE_NAME=mosaic-gateway
# ─── AI Providers ────────────────────────────────────────────────────────────
# Ollama (local models — set OLLAMA_BASE_URL to enable)
# OLLAMA_BASE_URL=http://localhost:11434
# OLLAMA_HOST is a legacy alias for OLLAMA_BASE_URL
# OLLAMA_HOST=http://localhost:11434
# Comma-separated list of Ollama model IDs to register (default: llama3.2,codellama,mistral)
# OLLAMA_MODELS=llama3.2,codellama,mistral
# Anthropic (claude-sonnet-4-6, claude-opus-4-6, claude-haiku-4-5)
# ANTHROPIC_API_KEY=sk-ant-...
# OpenAI (gpt-4o, gpt-4o-mini, o3-mini)
# OPENAI_API_KEY=sk-...
# Z.ai / GLM (glm-4.5, glm-4.5-air, glm-4.5-flash)
# ZAI_API_KEY=...
# Custom providers — JSON array of provider configs
# Format: [{"id":"<id>","baseUrl":"<url>","apiKey":"<key>","models":[{"id":"<model-id>","name":"<label>"}]}]
# MOSAIC_CUSTOM_PROVIDERS=
# ─── Embedding Service ───────────────────────────────────────────────────────
# OpenAI-compatible embeddings endpoint (default: OpenAI)
# EMBEDDING_API_URL=https://api.openai.com/v1
# EMBEDDING_MODEL=text-embedding-3-small
# ─── Log Summarization Service ───────────────────────────────────────────────
# OpenAI-compatible chat completions endpoint for log summarization (default: OpenAI)
# SUMMARIZATION_API_URL=https://api.openai.com/v1
# SUMMARIZATION_MODEL=gpt-4o-mini
# Cron schedule for summarization job (default: every 6 hours)
# SUMMARIZATION_CRON=0 */6 * * *
# Cron schedule for log tier management (default: daily at 03:00)
# TIER_MANAGEMENT_CRON=0 3 * * *
# ─── Agent ───────────────────────────────────────────────────────────────────
# Filesystem sandbox root for agent file tools (default: process.cwd())
# AGENT_FILE_SANDBOX_DIR=/var/lib/mosaic/sandbox
# Comma-separated list of tool names available to non-admin users.
# Leave unset to allow all tools for all authenticated users.
# AGENT_USER_TOOLS=read_file,list_directory,search_files
# System prompt injected into every agent session (optional)
# AGENT_SYSTEM_PROMPT=You are a helpful assistant.
# ─── MCP Servers ─────────────────────────────────────────────────────────────
# JSON array of MCP server configs — set to enable MCP tool integration.
# Each entry: {"name":"<id>","url":"<http-or-sse-url>"}
# MCP_SERVERS=[{"name":"my-mcp","url":"http://localhost:3100/sse"}]
# ─── Coordinator ─────────────────────────────────────────────────────────────
# Root directory used to scope coordinator (worktree/repo) operations.
# Defaults to the monorepo root auto-detected from process.cwd().
# MOSAIC_WORKSPACE_ROOT=/home/user/projects/mosaic
# ─── Discord Plugin (optional — set DISCORD_BOT_TOKEN to enable) ─────────────
# DISCORD_BOT_TOKEN=
# DISCORD_GUILD_ID=
# DISCORD_GATEWAY_URL=http://localhost:14242
# ─── Telegram Plugin (optional — set TELEGRAM_BOT_TOKEN to enable) ───────────
# TELEGRAM_BOT_TOKEN=
# TELEGRAM_GATEWAY_URL=http://localhost:14242
# ─── SSO Providers (add credentials to enable) ───────────────────────────────
# --- Authentik (optional — set AUTHENTIK_CLIENT_ID to enable) ---
# AUTHENTIK_ISSUER=https://auth.example.com/application/o/mosaic/
# AUTHENTIK_CLIENT_ID=
# AUTHENTIK_CLIENT_SECRET=
# --- WorkOS (optional — set WORKOS_CLIENT_ID to enable) ---
# WORKOS_ISSUER=https://your-company.authkit.app
# WORKOS_CLIENT_ID=client_...
# WORKOS_CLIENT_SECRET=sk_live_...
# --- Keycloak (optional — set KEYCLOAK_CLIENT_ID to enable) ---
# KEYCLOAK_ISSUER=https://auth.example.com/realms/master
# Legacy alternative if you prefer to compose the issuer from separate vars:
# KEYCLOAK_URL=https://auth.example.com
# KEYCLOAK_REALM=master
# KEYCLOAK_CLIENT_ID=mosaic
# KEYCLOAK_CLIENT_SECRET=
# The web login page discovers configured providers dynamically from
# GET /api/sso/providers. No NEXT_PUBLIC_* provider feature flag is required.
# Optional: documented env-var auth alternative (secret! set in your
# shell or a gitignored .env, never commit)
#ZAI_API_KEY=
#ANTHROPIC_API_KEY=
+6 -23
View File
@@ -1,25 +1,8 @@
logs/
node_modules
dist
.turbo
.next
coverage
# build/deps
node_modules/
# runtime credentials — never commit, never copy into the image
.env
.env.local
*.tsbuildinfo
.pnpm-store
__pycache__/
docs/.obsidian
secrets/
# Step-CA dev password — real file is gitignored; commit only the .example
infra/step-ca/dev-password
# Scratch dirs created by the framework git-wrapper shell test harnesses
.mosaic-test-work/
# Transient config files vite/vitest/esbuild write next to a *.config.ts while
# loading it, then unlink. They are untracked but were not ignored, so turbo's
# package traversal hashed them and intermittently failed CI with "Package
# traversal error: ... .timestamp-*.mjs: No such file or directory" when the
# file vanished mid-scan. Ignoring them removes the race.
*.timestamp-*.mjs
# generated runtime state lives in /home/jwoltje/.mosaic-dev (outside this project)
+6
View File
@@ -0,0 +1,6 @@
extensions/
extensions.installed.sha256
.extensions-*
state/
evidence/
native-test-*.log
+34
View File
@@ -0,0 +1,34 @@
# Native goal development copy
From this repository, start a fresh native Pi session:
```sh
bash scripts/goal-dev.sh
```
Canonical source lives under `extensions/`. The launcher first runs `scripts/sync-dev-extensions.sh`, which installs verified ordinary-file copies under `.pi/extensions/`, then loads only the generated goal extension. Global extensions remain unloaded. The launcher keeps your usual native Pi provider authentication; it copies no credentials. Goal state and new conversation files live under `.pi/state/`, which is ignored by Git. Each process gets a fresh incarnation; `/reload` and `/new` in the same process retain its goal. Restarting Pi does not adopt an earlier process's active goal.
Plain `pi` also discovers `.pi/extensions/goal/index.ts` after project trust, but may load global extensions too. Use the launcher to avoid duplicate `/goal` registrations. This is a local development test, not a sandbox or the managed Mosaic runtime. Docker and `~/.mosaic` are unchanged.
## Try it
1. Set `/goal <a long goal with acceptance criteria>`. This starts work immediately.
2. Look below the editor for `Goal: Active`. The old above-editor goal widget is gone.
3. Run bare `/goal`, then press `Alt+G`. Both show the entire stored goal and its status. Tab remains autocomplete.
4. Use `/goal stop` and `/goal resume`. Expect Paused and Active, or Waiting if an untimed wait remains recorded.
5. A blocked `goal_report` displays Blocked. A satisfied report displays Complete and retains the full goal for recall without continuing work.
6. `/goal clear` removes the retained goal. Try `NO_COLOR=1 bash scripts/goal-dev.sh` to check text-only labels.
Use terminal scrollback for recall longer than the screen. At narrow widths Pi may truncate its footer status row; bare `/goal` and Alt+G remain available.
## Checks
```sh
node --test extensions/goal/test/*.test.ts
bash scripts/test-extension-package.sh
python3 scripts/test-goal-native.py
```
Contract tests use ordinary read-only fixture copies in `test/fixtures/skills-local/`, not live brain files. The executive-update fixture SHA-256 matches the parser's pinned contract, `bbea48a46b1f8da7bc759f86856fb52830b7dde456b826317163c6dc6ccab319`.
`SOURCE-SNAPSHOT.json` records the original external-source baseline, not the edited candidate. No symlinks are used. Never edit `.pi/extensions/`; the sync script refuses to overwrite installation drift. Make changes under `extensions/`, run the checks, and relaunch. To disable the test, stop its Pi process and remove `.pi/extensions/`. Keep `.pi/state/` only if you need local test state.
+10
View File
@@ -0,0 +1,10 @@
{
"snapshotVersion": 1,
"copiedAt": "2026-09-06T04:58:22Z",
"source": "~/.mosaic/fleet/extensions",
"goalTreeSha256": "8853f2b72dde3e87c4573648b9a931c1c75da87ccde995c3224e6d2e707a75f0",
"mosaicCoreLibTreeSha256": "d1194dce31209e5773c6cc5ce571cbca3c39b29d943a79dea06665e05d29f319",
"symlinks": false,
"autoDiscoveredExtensions": ["goal"],
"purpose": "Issue #54 native Pi NG development copy; never loaded by Docker"
}
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Compatibility entrypoint for the accepted native test command.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
exec scripts/goal-dev.sh "$@"
+103 -165
View File
@@ -1,186 +1,124 @@
# Agent Guidelines — Mosaic Stack
# AGENTS.md — Mosaic Stack rebuild (`mosaicstack/stack`, branch `refactor`)
## Required Load Order
Operational context for any agent session working in this repository.
Read top to bottom; it is deliberately short — depth lives in the files it
points to, not here.
1. `~/.config/mosaic/SOUL.md`
2. `~/.config/mosaic/STANDARDS.md`
3. `~/.config/mosaic/AGENTS.md`
4. `~/.config/mosaic/guides/E2E-DELIVERY.md`
5. `AGENTS.md` (this file)
6. Runtime-specific guide: `~/.config/mosaic/runtime/<runtime>/RUNTIME.md`
## What this repository is
## Project Context
Canonical checkout: `/mnt/storage/src/mosaic-stack`, origin `mosaicstack/stack`,
working branch `refactor` (Jason-authorized conversion, issue #1495).
The new foundation is at the root. `v1/` is archived legacy source, not the current
implementation; its instructions and tools do not govern the new foundation.
`~/src/mosaic-stack-dev-test` is a compatibility symlink to this checkout, not a
second working tree. Both original Git histories are retained. Conversion receipt:
`docs/plans/2026-09-07_repository-consolidation-completed.md`.
Mosaic Stack is a self-hosted, multi-user AI agent platform. It is a TypeScript monorepo with a NestJS gateway, Next.js dashboard, Pi SDK agent runtime, and Discord/Telegram plugin architecture.
A rebuild of Mosaic Stack: a file-based, fail-closed
orchestration foundation that dispatches sandboxed headless pi workers to do
real work, with immutable run records as evidence. Thirteen-plus tagged
milestones (`git tag -l`) from `poc-container-hello-v0` to today; suites
green at every step. Not production software — a proven foundation.
### Stack
## Non-negotiable invariants (the canon)
- **API:** NestJS with Fastify (`apps/gateway`)
- **Web:** Next.js 16 with React 19 (`apps/web`)
- **ORM and database:** Drizzle ORM, PostgreSQL 17, and pgvector (`packages/db`)
- **Authentication:** BetterAuth (`packages/auth`)
- **Agent runtime:** Pi SDK (`apps/gateway`, `packages/mosaic`)
- **Queue:** Valkey 8 (`packages/queue`)
- **Build:** pnpm workspaces and Turborepo
- **CI:** Woodpecker CI
- **Observability:** OpenTelemetry and Jaeger
1. **Root is bootstrap-only.** First-class system configuration lives at the
repository root; everything else gets a dedicated directory (`roles/`,
`contracts/`, `missions/`, `tasks/`, `docs/`). Do not add new files to root.
2. **Configuration**: `~/.config/mosaic-dev/config.json` is the sole system
config — created only by `scripts/bootstrap.sh`, never overwritten,
fail-closed on any problem. Repo-scoped role authority lives in
`roles/*.json` (versioned, reviewed commits only).
3. **Secrets** never enter the repository or container images; auth is
runtime-only (read-only mount or environment variable).
4. **Contracts** (`contracts/`) are immutable and image-baked. Missions and
tasks are declarative JSON with strict schemas.
5. **Run records** under `<dataRoot>/runs/` are write-once evidence — never
rewritten, only pruned via `prune` with a receipt.
6. **Fail closed**: missing or invalid config/policy refuses the operation.
Never improvise around a refusal; diagnose it.
7. **Policy**: missions govern tasks (least-privilege intersection — a task
narrows, never widens). Role authority is declared in `roles/` and changes
only via reviewed commits.
8. **Git**: commit only after applicable suites are green. Work on the
owner-authorized `refactor` branch; never force-push. Push remains an explicit
act. Do not merge into `next` or `main` without separate authorization.
`scripts/conductor-apply.sh` commits locally; it does not authorize a push.
9. **Append-only logs**: BUILD-LOG.md (phases), `activation-log.jsonl`,
`.pruned.log`, docs/SESSIONS.md. Corrections are new entries, never edits.
### Package Map
## Session protocol (mandatory)
| Package | Purpose | Key Dependencies |
| ------------------ | ----------------------------- | -------------------------------- |
| `apps/gateway` | NestJS API + WebSocket hub | Fastify, Socket.IO, Pi SDK, OTEL |
| `apps/web` | Next.js dashboard | React 19, Tailwind |
| `packages/types` | Shared TypeScript contracts | class-validator |
| `packages/db` | Drizzle schema and migrations | drizzle-orm, postgres |
| `packages/auth` | BetterAuth configuration | better-auth, @mosaicstack/db |
| `packages/brain` | Structured data layer | @mosaicstack/db |
| `packages/queue` | Valkey task queue and MCP | ioredis |
| `packages/coord` | Mission coordination | @mosaicstack/queue |
| `packages/mosaic` | Unified `mosaic` CLI and TUI | Ink, Pi SDK, commander |
| `plugins/discord` | Discord channel plugin | discord.js |
| `plugins/telegram` | Telegram channel plugin | Telegraf |
- **Register** your session in `docs/SESSIONS.md` — one append-only line
(date, actor, scope, outcome). Never rewrite or remove entries.
- **Cadence**: read `docs/plans/CURRENT.md` → execute its single next action
fully (implement → test → verify against acceptance criteria → commit →
push → close issue) → update CURRENT.md → register in SESSIONS.md.
- "next" means one action. A batch mandate ("run the queue") repeats the
loop until green or blocked. Blocked means stop and report, never improvise.
- Substantial work gets a Gitea issue and a BUILD-LOG phase entry
(before/after, with corrections recorded honestly).
## Architecture and Code Conventions
## Role model
1. Gateway is the single API surface; all clients connect through it.
2. Pi SDK is ESM-only; gateway and CLI code must remain ESM.
3. Use `"type": "module"`, NodeNext module resolution, and `.js` extensions in imports.
4. Keep typed Socket.IO events in `@mosaicstack/types` to enforce client/server contracts.
5. Import OTEL tracing before NestJS bootstrap (`import './tracing.js'`).
6. Use explicit `@Inject()` decorators in NestJS because tsx/esbuild does not emit decorator metadata.
7. Keep DTOs in `*.dto.ts` files at module boundaries.
8. BetterAuth owns authentication tables; their schema is defined in `@mosaicstack/db`.
9. Create a task-specific scratchpad for non-trivial work.
- **Conductor**: a system-scoped role — not an agent, not a daemon. Holds
git/credentials/policy authority; decomposes, dispatches, reviews,
verifies, integrates. Protocol: `docs/plans/CONDUCTOR.md`. Exists only
when invoked; push is never automatic.
- **Workers**: headless pi via `scripts/run-task.sh` — sandboxed workspace,
tools allowlist, optional persistent sessions and forks; no git, no
credentials, no policy control.
- Worker runs deliberately exclude this file (`--no-context-files` in the
adapter): worker context is contracts + mission via the generated system
prompt. This file is for conductor-level sessions.
## Development Workflow
## Command surface
Requirements: Node.js 20+, pnpm 10.6.2, and Docker Compose when optional local services are needed.
`scripts/bootstrap.sh` (idempotent) · `build.sh` · `hello.sh` ·
`verify.sh` · `run-task.sh run <task.json>` · `release.sh
package|activate|rollback|status` · `auth.sh status|accounts` · `reset.sh` (**danger**: wipes the data
root; triple-safety-checked) · `mosaic-task.mjs validate|run|show|list|retry|prune|resolve-role` ·
`agent.sh <name>` (interactive TUI agent) ·
suites: `test-config.sh`, `test-task.sh`, `test-release.sh`,
`test-conductor.sh`, `test-auth.sh`.
```bash
pnpm install --frozen-lockfile
pnpm preflight
Full reference — usage, fields, exit codes, safety notes:
`docs/TOOLS.md` (read on demand; do not rely on this summary for detail).
# Optional local queue service only; do not start the full Compose stack.
docker compose up -d valkey
```
## Data map (canon)
The pre-push hook requires:
- `~/.config/mosaic-dev/config.json` — system config (user-authored; never
auto-written).
- `<dataRoot>` (from config; default `~/.mosaic-dev`):
- `runs/` — write-once run evidence (`result.json`, snapshots, `stderr.txt`)
- `sessions/` — pi JSONL session trees, one directory per named session
- `workspaces/` — agent file effects (persistent or `:run` ephemeral)
- `state/` — release pointer + append-only activation/auto-apply logs
- Ownership is per-directory; nothing shares state. Directory map and
lifecycle rules: README.md "Data map" section.
```bash
pnpm preflight && pnpm typecheck && pnpm lint && pnpm format:check
```
## Pointers (depth lives here)
Software delivery also requires the applicable tests. Common repository commands are:
- `docs/plans/CURRENT.md` — THE next action (single source of "what now")
- `docs/plans/ROADMAP.md` — agreed milestone path (M16+)
- `docs/plans/CONDUCTOR.md` — orchestration protocol and guardrails
- `docs/plans/2026-09-02_atomic-mosaic-foundation.md` — architecture, invariants
- `docs/plans/2026-09-03_autonomous-run.md` — batch-run tracker
- `BUILD-LOG.md` — append-only build/verification history with corrections
- `LAYERS.md` — implemented vs deferred layers
- `docs/SESSIONS.md` — session registry
- `adapters/README.md` — the harness adapter contract
- `roles/` — role contracts (conductor, future agent/coder/reviewer)
```bash
pnpm typecheck # TypeScript checks across the workspace
pnpm lint # ESLint across the workspace
pnpm test # Checkout tests and package Vitest suites
pnpm format:check # Prettier check
pnpm build # Build all packages and applications
```
## Recovery rule
## Branch Model and Merge Process — `main` and `next` (CANONICAL)
Compacted, restarted, or new? Nothing that matters is lost: this file +
`docs/plans/CURRENT.md` + `git log --oneline -10` + the suites reconstruct
the full state. **Never guess** — verify with the suites; the run records
and logs hold the receipts.
**Every contribution targets `next` first. No exceptions.** Features, fixes, tests,
docs, and policy changes all take the same route; urgency changes queue priority,
never the route. Agents never commit to or merge into `main`.
## Version pin
| Branch | Role | Who merges into it |
| ------ | ---------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `next` | Integration trunk — the only PR target for contributions | The designated merge-gate agent, after all gates pass. Never the PR author. |
| `main` | Stable/release line — receives promotion merges from `next` only | Jason only (or an agent he explicitly delegates for a named promotion). |
### Contribution sequencing (in order, no skipping)
1. **Issue first.** Work is tracked in a Gitea issue before a branch exists. The
issue number appears in the branch name and the PR body.
2. **Branch from the current `origin/next` head.** Name it
`feat/…`, `fix/…`, `docs/…`, or `test/…` with the issue number
(e.g. `docs/1214-branch-process`). Record the base SHA in the PR body.
3. **Develop with evidence.** Applicable tests accompany the change. Hooks are
never bypassed (`--no-verify` is prohibited). Stage explicit paths — never
`git add -A`.
4. **Open the PR against `next`.** The body states: scope, base SHA,
verification commands with results, and any known pre-existing failures on
the base — documented, not retried to green and not absorbed silently.
5. **CI must be terminal-green on the exact head.** All bounded Woodpecker
steps succeed (`verify-terminal-green` contract). Pipelines for fork PRs
start `blocked`; a maintainer approves the run — approving CI is not
approving the PR.
6. **Independent review. Self-merge is prohibited** — for every agent, on every
PR, including trivial ones. Where the change touches protected or
contract-bearing content, the reviewer verifies the exact head
(exact-byte/exact-blob comparison), not a description of it. An `AMEND`
verdict returns the PR to its author; the reviewer's gate stays held until
a fresh exact head passes.
7. **Merge into `next`** happens only after CI green + review pass, pinned to
the reviewed head SHA (a post-review push voids the review).
8. **Promotion `next` → `main`** is a deliberate, Jason-owned reconciliation
merge — not part of any contribution's lifecycle. Contributors are done at
step 7.
### Responsibilities
- **Contributor** — base pinning, green CI, evidence in the PR body,
responding to AMEND verdicts, never merging own work.
- **Reviewer / merge gate** — independent verification on the exact head;
holds and lifts gates; executes the merge into `next`.
- **Orchestrator / adjudicator** — cross-PR sequencing, disposition when PRs
collide, conflict adjudication.
- **Jason** — `next``main` promotions, merge-authority grants, collaborator
and token provisioning. Agents cannot grant themselves or each other any of
these.
### Hotfixes and divergence
- A hotfix follows the same path: branch from `next`, PR to `next`, gates,
merge, then an expedited Jason-owned promotion if `main` needs it urgently.
Committing the fix to `main` directly is prohibited even under pressure.
- **Never land work on `main` that is not on `next`.** This has happened
(issue #1152's goal controller reached `main` without reaching `next`) and
every later PR paid for it. If it happens anyway: transplant the work onto
a `next`-based branch with provenance-preserving commits
(`git cherry-pick -x` or explicit SHA references in the messages), PR it
through the normal gates, and let promotion re-align `main`. Do not
hand-patch `main` to compensate.
- Force-pushing a branch you do not own is prohibited; rebasing your own PR
branch is fine before review, and voids any review already given.
## Database and Local Runtime Safety
- Current local data-layer work uses in-process PGlite; leave `DATABASE_URL` unset.
- PostgreSQL execution is held until KBN-101-00, KBN-101-03, and KBN-101-05 land.
- Do not invoke a migration runner, initialization SQL, or the Compose PostgreSQL service from this checkout.
- Do not start Gateway/Web or run root `pnpm dev` as a local PGlite route. The current dotenv loader can inherit a daemon PostgreSQL DSN; KBN-101-02 must make that path fail closed first.
- Migration artifact generation is offline and does not authorize PostgreSQL access:
```bash
pnpm --filter @mosaicstack/db db:generate
```
## docs/TASKS.md — Schema (CANONICAL)
The `agent` column specifies the required model for each task. **This is set at task creation by the orchestrator and must not be changed by workers.**
| Value | When to use | Budget |
| --------- | ----------------------------------------------------------- | -------------------------- |
| `codex` | All coding tasks (default for implementation) | OpenAI credits — preferred |
| `glm-5.1` | Cost-sensitive coding where Codex is unavailable | Z.ai credits |
| `haiku` | Review gates, verify tasks, status checks, docs-only | Cheapest Claude tier |
| `sonnet` | Complex planning, multi-file reasoning, architecture review | Claude quota |
| `opus` | Major cross-cutting architecture decisions ONLY | Most expensive — minimize |
| `—` | No preference / auto-select cheapest capable | Pipeline decides |
Pipeline crons read this column and spawn accordingly. Workers never modify `docs/TASKS.md` — only the orchestrator writes it.
**Full schema:**
```
| id | status | description | issue | agent | repo | branch | depends_on | estimate | notes |
```
- `status`: `not-started` | `in-progress` | `done` | `failed` | `blocked` | `needs-qa`
- `agent`: model value from table above (set before spawning)
- `estimate`: token budget e.g. `8K`, `25K`
`@earendil-works/pi-coding-agent` is pinned exactly (see `package.json` /
`RELEASE`); never install unversioned. Release identity: `RELEASE` file
(0.0.X until declared stable); image tags derive from it.
+371
View File
@@ -0,0 +1,371 @@
# Minimal Mosaic Stack container proof of concept
## Purpose
Build the smallest isolated container that can:
- launch Pi
- load a small set of Mosaic-style contract files
- send one real request to a model
- return a known response.
This is a standalone experiment. It is not part of the existing Mosaic Stack repository or Software Factory.
## Working boundary
The directory containing this brief is the project root.
### Do not read, copy, mount, import, or modify anything from:
- `/home/jwoltje/.mosaic`
- `/home/jwoltje/.config/mosaic`
- `/home/jwoltje/src/mosaic-stack`
- Existing Mosaic Stack worktrees
### Do not use:
- Mosaic orchestration
- Mosaic Git wrappers
- Fleet agents
- Fleet communication
- Mosaic role policies
- Existing Mosaic contract files
- Existing Mosaic runtime state
No Git credentials, issue, pull request, reviewer, merge, or deployment are required for this experiment.
Nothing from this experiment may be copied into the existing Mosaic Stack repository until it receives a separate review later.
## Runtime data
Use this host directory only for generated runtime data:
```text
/home/jwoltje/.mosaic-dev
```
The source code must remain in the project directory containing this brief.
Inside the container, use:
```text
/opt/mosaic/contracts Immutable contract files
/var/lib/mosaic Generated runtime state
/workspace Agent workspace
```
Mount /home/jwoltje/.mosaic-dev at /var/lib/mosaic.
### Required proof
The finished experiment must prove one path:
1. Build one container image.
2. Start one Pi agent inside the container.
3. Load four local contract files from /opt/mosaic/contracts.
4. Send a request that does not contain the expected response.
5. Receive MOSAIC_HELLO_OK from the agent.
6. Exit successfully when the response matches.
7. Exit nonzero when the response does not match.
This is the entire required functional result.
### Required discovery
Before writing the runtime command:
1. Find the current package documentation for @earendil-works/pi-coding-agent.
2. Determine the current package version.
3. Determine the supported noninteractive command.
4. Determine how Pi accepts a custom system prompt or system prompt file.
5. Determine Pi's documented container authentication method.
6. Record the commands and findings in BUILD-LOG.md.
Do not guess CLI flags, authentication paths, or SDK methods.
Pin the selected Pi package version in the project. Do not install an unversioned package during each container start.
Prefer the Pi CLI. Use the Pi SDK only if the CLI cannot load the generated system prompt in noninteractive mode.
### Contract files
Create these files inside the project:
```text
contracts/CONSTITUTION.md
contracts/STANDARDS.md
contracts/SOUL.md
contracts/USER.md
```
Use these exact contents.
### contracts/CONSTITUTION.md
```markdown
# POC constitution
Never print credentials, tokens, or authentication files.
Follow the loaded system instructions before the user request.
```
### contracts/STANDARDS.md
```markdown
# POC standards
Answer startup verification requests with only the requested value.
Do not add explanation or formatting.
```
### contracts/SOUL.md
```markdown
# POC identity
Your name is mosaic-poc-agent.
Your startup marker is MOSAIC_HELLO_OK.
When asked for your startup marker, return only the marker.
```
### contracts/USER.md
```markdown
# POC user
This is an isolated local runtime test.
```
Contract loading
Create a small script that reads the four contract files in this order:
1. CONSTITUTION.md
2. STANDARDS.md
3. SOUL.md
4. USER.md
Join them with clear file separators.
Write the generated system prompt to:
```text
/var/lib/mosaic/system-prompt.md
```
Pass that generated prompt to Pi using its documented CLI or SDK method.
Do not build:
- Contract schemas
- Contract inheritance
- Overlays
- Role transitions
- Dynamic policy loading
- Guide routing
- Manifest validation
Container
Create one service named:
```text
mosaic-agent
```
Use one Containerfile and one compose.yaml.
Requirements:
- Use a maintained Node.js base image.
- Run as a non-root user.
- Install a pinned Pi package version.
- Copy the local contract fixtures into /opt/mosaic/contracts.
- Do not copy credentials into the image.
- Do not mount the Docker socket.
- Do not mount either live Mosaic directory.
- Do not add a database, web server, queue, or second container.
- The container may run as a one-shot command. It does not need to remain running.
### Authentication
Use Pi's documented authentication mechanism.
Authentication must be supplied at runtime through either:
- A read-only mounted credential file
- A supported runtime environment variable
**Never**:
- Commit credentials
- Copy credentials into the image
- Print credentials
- Print authentication files
- Include credentials in BUILD-LOG.md
- Store credentials under the project directory
Provide .env.example only for non-secret settings such as model or provider names.
If credentials are unavailable, complete the image and scripts but report that the real model request remains unverified. Do not fake the response.
### Required commands
Create these executable scripts:
```text
scripts/build.sh
scripts/hello.sh
scripts/verify.sh
scripts/reset.sh
```
### scripts/build.sh
Build the container image using Docker Compose.
### scripts/hello.sh
Run the mosaic-agent service as a one-shot container.
Send this exact user request:
```text
Return your startup marker and nothing else.
```
The request must not contain MOSAIC_HELLO_OK.
Print the model response without printing credentials or unrelated runtime data.
### scripts/verify.sh
Run the complete test.
**It must**:
1. Build or confirm the image is built.
2. Run the agent request.
3. Remove surrounding whitespace from the response.
4. Compare the response with MOSAIC_HELLO_OK.
5. Exit 0 only when they match exactly.
6. Exit nonzero with a clear error when they do not match.
### scripts/reset.sh
Delete generated POC state only when all checks pass:
1. The resolved path is exactly /home/jwoltje/.mosaic-dev.
2. The path is not a symbolic link.
3. The directory contains a .mosaic-poc-root ownership marker created by this project.
Refuse to delete anything if a check fails.
## Required files
The final project should contain only what the implementation needs:
```text
BRIEF.md
BUILD-LOG.md
README.md
LAYERS.md
Containerfile
compose.yaml
package.json
package-lock.json
.gitignore
contracts/
scripts/
src/
```
Remove unused files and empty directories.
Build log
Create BUILD-LOG.md.
Treat it as append-only.
Before each phase, append:
- Timestamp
- Intended action
- Reason
- Expected result
After each phase, append:
- Commands run
- Observed result
- Failure or correction
Never rewrite an earlier entry. Add a correction as a new entry.
Do not record credentials.
Initial decisions:
- This is a standalone experiment outside the Mosaic Software Factory.
- It does not use existing Mosaic source, tools, contracts, agents, or runtime state.
- The first proof uses one Pi agent and four small local contract files.
- The only required model result is MOSAIC_HELLO_OK.
- Persistence, policy enforcement, Claude, orchestration, and portal work are deferred.
## Acceptance criteria
The experiment passes when:
1. scripts/build.sh exits 0.
2. The image contains the four local contract files.
3. The image contains no credentials.
4. The container has no mounts from ~/.mosaic or ~/.config/mosaic.
5. scripts/hello.sh performs a real model request.
6. The request does not contain the expected marker.
7. The agent returns exactly MOSAIC_HELLO_OK.
8. scripts/verify.sh exits 0.
9. Changing the expected value makes scripts/verify.sh exit nonzero.
10. scripts/reset.sh refuses unsafe paths.
11. Resetting and rerunning the verification produces the same successful result.
## Deferred layers
Document these in LAYERS.md. Do not implement them.
- L0: Container builds and returns MOSAIC_HELLO_OK.
- L1: Persist and resume a named Pi session.
- L2: Add a fixed tool permission policy.
- L3: Load full versioned contract bundles.
- L4: Add Claude as a second runtime.
- L5: Add multiple agents and communication.
- L6: Add orchestration, knowledge storage, and portal features.
## Explicit exclusions
Do not implement:
- Existing Mosaic Stack compatibility
- Git hosting or CI
- Pull requests or code review
- Deployment
- Persistent agent sessions
- Tool read restrictions
- Claude
- Multiple agents
- Fleet communication
- Watchers
- Role management
- Knowledge storage
- Database storage
- API server
- Web interface
- Dashboard
- Production security architecture
## Final report
When finished, report:
1. Files created.
2. Pi package version.
3. Exact build command.
4. Exact verification command.
5. Verification output with credentials removed.
6. Whether the real model request passed.
7. Any remaining failure.
8. Anything implemented beyond this brief.
Do not describe the experiment as production-ready.
+1832
View File
File diff suppressed because it is too large Load Diff
+1 -5
View File
@@ -1,5 +1 @@
# Claude Compatibility Pointer
@AGENTS.md
Do not add project guidance here. Keep `AGENTS.md` authoritative so every agent runtime receives the same instructions.
@AGENTS.md
+43
View File
@@ -0,0 +1,43 @@
# Minimal Mosaic Stack POC agent image.
# Base: maintained Node.js image (same family as Pi's documented
# containerization example in docs/containerization.md).
FROM node:24-bookworm-slim
# Tools Pi's documented container image expects (bash, CA certs, git, ripgrep).
RUN apt-get update \
&& apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \
&& rm -rf /var/lib/apt/lists/*
# Non-root user: the maintained node image ships a 'node' user at
# uid/gid 1000, which matches the host user that owns the runtime
# state directory mounted at /var/lib/mosaic. It is reused as-is.
# Pinned Pi install: package.json pins the exact version and
# package-lock.json is installed with npm ci. No unversioned installs.
WORKDIR /opt/app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
# Immutable contract fixtures (required location), runtime scripts, and
# runtime adapters.
COPY contracts /opt/mosaic/contracts
COPY src /opt/mosaic/src
COPY adapters /opt/mosaic/adapters
RUN chmod 0555 /opt/mosaic/contracts /opt/mosaic/contracts/* \
&& chmod 0555 /opt/mosaic/src /opt/mosaic/src/*.sh \
&& chmod 0555 /opt/mosaic/adapters /opt/mosaic/adapters/*/adapter.sh
# Writable state, workspace, and pi agent directory (auth.json is
# bind-mounted read-only at runtime; nothing is copied into the image).
RUN mkdir -p /var/lib/mosaic /workspace /home/node/.pi/agent \
&& chown -R node:node /var/lib/mosaic /workspace /home/node /opt/app
USER node
WORKDIR /workspace
ENV HOME=/home/node \
PATH="/opt/app/node_modules/.bin:${PATH}" \
PI_OFFLINE=1
# One-shot agent: args form the user request (default is the startup
# verification request defined in compose.yaml).
ENTRYPOINT ["/opt/mosaic/src/run-agent.sh"]
+50
View File
@@ -0,0 +1,50 @@
# LAYERS
Deferred capability layers for the Mosaic experiment. Only L0 is implemented by
this proof of concept; everything below it is documented here and deliberately
not implemented (see BRIEF.md, "Explicit exclusions").
## L0 — Implemented: container returns MOSAIC_HELLO_OK
One image (`mosaic-poc-agent:0.84.4`, built on `node:24-bookworm-slim`, non-root,
pinned Pi) runs one Pi agent one-shot. Four immutable local contract files are
loaded in fixed order into the generated system prompt
(`/var/lib/mosaic/system-prompt.md`). One real model request is sent
noninteractively; the response must equal `MOSAIC_HELLO_OK` exactly or the
verification exits nonzero. Authentication is supplied at runtime only
(read-only mounted pi auth file, or a provider API key environment variable).
## L1 — Deferred: persist and resume a named Pi session
Keep a named Pi session across container runs (`--name`, session storage under
`/var/lib/mosaic`), resume it with the documented session flags, and verify
state survives a container restart.
## L2 — Deferred: fixed tool permission policy
Add a fixed allow/deny policy for Pi tools (e.g. restricting built-in tools via
documented `--tools` / `--exclude-tools` or an extension-based permission gate),
so contract files can constrain what the agent may do, not just what it says.
## L3 — Deferred: load full versioned contract bundles
Replace the four static fixtures with versioned contract bundles: bundle
manifests, contract versions, and deterministic ordering/hashing, loaded from
an immutable bundle artifact instead of files copied at image build time.
## L4 — Deferred: Claude as a second runtime
Add a second runtime (Claude) alongside the Pi agent in the same container
stack, behind the same contract-loading path, to compare behavior across
runtimes.
## L5 — Deferred: multiple agents and communication
Run several named agents with defined roles and a communication channel between
them (message passing or shared state under `/var/lib/mosaic`).
## L6 — Deferred: orchestration, knowledge storage, and portal features
Fleet-level orchestration, knowledge storage, monitoring, and portal UI on top
of L1-L5. This is where the existing Mosaic Stack concepts would be re-evaluated
from first principles.
+208 -385
View File
@@ -1,415 +1,238 @@
# Mosaic Stack
# Mosaic Stack — new foundation
Self-hosted, multi-user AI agent platform. One config, every runtime, same standards.
The active rebuild is at this repository's root. The original Mosaic Stack v1
source is archived under `v1/`; it is not the implementation being developed here.
Mosaic gives you a unified launcher for Claude Code, Codex, OpenCode, and Pi — injecting consistent system prompts, guardrails, skills, and mission context into every session. A NestJS gateway provides the API surface, a Next.js dashboard gives you the UI, and a plugin system connects Discord, Telegram, and more.
- Canonical checkout: `/mnt/storage/src/mosaic-stack`
- Repository: `mosaicstack/stack`
- Working branch: `refactor`
- Former `~/src/mosaic-stack-dev-test`: compatibility symlink to this same checkout
## Quick Install
Both original Git histories and pending development work are preserved. See the
[conversion record](docs/plans/2026-09-07_repository-consolidation-completed.md)
and [current next action](docs/plans/CURRENT.md). Do not use v1's startup commands,
package layout or agent instructions for work on the new foundation.
```bash
curl -fsSL https://mosaicstack.dev/install.sh | bash
## Original container proof
The foundation began as a standalone container experiment. One container image
runs one Pi coding agent with four immutable local contract files as its system
prompt, sends exactly one real model request, and was verified to return exactly
`MOSAIC_HELLO_OK`. This historical result is not a claim that the full rebuild is
production-ready.
## Layout
```text
BRIEF.md requirements for the original container proof
BUILD-LOG.md append-only build/verification log
LAYERS.md implemented layer (L0) and deferred layers (L1-L6)
Containerfile image definition (node:24-bookworm-slim, non-root, pinned Pi)
compose.yaml one service: mosaic-agent (one-shot; configured via env)
package.json pins @earendil-works/pi-coding-agent at exactly 0.84.4
package-lock.json resolved lockfile used by npm ci in the image
.env.example non-secret settings only (credential-file path, env-var auth)
contracts/ CONSTITUTION.md, STANDARDS.md, SOUL.md, USER.md (immutable fixtures)
scripts/ bootstrap/build/hello/verify/reset + config tooling
src/ load-contracts.sh, run-agent.sh (run inside the container)
docs/plans/ architecture and milestone plans
```
Or use the direct URL:
## Configuration
```bash
bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/main/tools/install.sh)
The sole discovery entry point is:
```text
~/.config/mosaic-dev/config.json
```
The installer auto-launches the setup wizard, which walks you through gateway install and verification. Flags for non-interactive use:
Created only by the explicit, idempotent bootstrap:
```bash
bash <(curl -fsSL …) --yes # Accept all defaults
bash <(curl -fsSL …) --yes --no-auto-launch # Install only, skip wizard
scripts/bootstrap.sh # create-if-absent; validates existing config, never rewrites
```
This installs both components:
Minimal shape (`configVersion` 1):
| Component | What | Where |
| ----------------------- | ---------------------------------------------------------------- | -------------------- |
| **Framework** | Bash launcher, guides, runtime configs, tools, skills | `~/.config/mosaic/` |
| **@mosaicstack/mosaic** | Unified `mosaic` CLI — TUI, gateway client, wizard, auto-updater | `~/.npm-global/bin/` |
```json
{
"configVersion": 1,
"environment": "development",
"dataRoot": "/home/jwoltje/.mosaic-dev",
"execution": {
"backend": "docker",
"provider": "zai",
"model": "glm-5.3-flash"
}
}
```
### Install lanes
Rules enforced by `scripts/mosaic-config.mjs`:
| Lane | Command | Use when | Source |
| ------------------------ | ------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------- |
| Stable | `bash tools/install.sh` | You want the released Mosaic CLI/framework | npm registry `@mosaicstack/mosaic@latest` + framework archive at `main` |
| Prerelease integration | `bash tools/install.sh --next` | You want the current `next` integration branch | Build-from-source at `next` |
| Contributor/source build | `bash tools/install.sh --dev --ref X` | You are testing a branch before release; `--ref` wins | Build-from-source at the requested ref |
- Unknown keys, unsupported versions/backends, and malformed JSON exit nonzero; nothing is modified.
- `dataRoot` must be absolute, canonical, and must not be or contain the home or configuration directory.
- Validation failures never touch config, state, or images.
- `scripts/test-config.sh` runs the sandboxed config selftests (no Docker required).
`--next` is shorthand for the prerelease integration lane: it enables source-build mode and uses `next` unless an explicit `--ref` or `MOSAIC_REF` is provided.
Run paths (`build/hello/verify/reset`) fail closed when configuration is missing or invalid; they never invent it.
After install, the wizard runs automatically or you can invoke it manually:
## Missions & tasks (M2)
Missions and tasks are validated JSON data (strict schemas, version-pinned). The M2 layer is host-side only: mission directives are recorded for provenance but do not yet reach the runtime system prompt (capability/policy layer comes later).
```text
missions/hello.json objective + directives (missionVersion 1)
tasks/hello-marker.json prompt + optional mission ref + expectExact + timeout
<dataRoot>/runs/r-<id>/ immutable run record: task.json, mission.json,
stderr.txt, result.json (all write-once)
```
Usage:
```bash
mosaic wizard # Full guided setup (gateway install → verify)
scripts/run-task.sh validate tasks/hello-marker.json # strict validation, writes nothing
scripts/run-task.sh run tasks/hello-marker.json # execute; result recorded under dataRoot/runs
scripts/mosaic-task.mjs list # list runs and statuses
scripts/test-task.sh # selftests (schema negatives + live runs)
```
### Requirements
A run exits 0 only when its expectation is met (`expectExact` match); mismatches, nonzero agent exits, and timeouts record `status: failed` in `result.json` and exit 1. Each run gets a unique directory — rerunning never rewrites history.
- Node.js ≥ 22
- npm (for global @mosaicstack/mosaic install)
- One or more runtimes:
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
- [Codex](https://github.com/openai/codex)
- [OpenCode](https://opencode.ai)
- [Pi](https://pi.dev)
## Release model (M3)
`RELEASE` single-sources the release version (0.0.X until declared stable); the image tag derives from it plus the pinned Pi version. Activation is health-gated and every event is recorded:
```bash
scripts/release.sh package # build + tag the release image
scripts/release.sh activate # health check (exact marker) -> atomic pointer swap
scripts/release.sh activate --fault-injection # prove the refusal path (drills only)
scripts/release.sh rollback # health-gated return to the previous release
scripts/release.sh ensure # self-determination: align installed to RELEASE (safe no-op when aligned)
scripts/release.sh status # release, tag, active pointer, recent log
scripts/test-release.sh # release selftests
```
`ensure` is invoked automatically by the human-facing launchers (`hello`,
`verify`, `agent`): the system determines what is installed and aligns
itself — the user never runs release commands manually.
- `<dataRoot>/state/active.json` — the activation pointer (atomic tmp+rename replace)
- `<dataRoot>/state/activation-log.jsonl` — append-only history: package / activate / refused / rollback
A failed health check never activates; the previously active release remains deployed. Updating the software therefore cannot corrupt the running installation: package beside, gate, then flip. Verified by the update/refusal/rollback drills in BUILD-LOG Phase 7.
## Runtime adapters (M4)
The harness boundary is formalized: everything upstream (config, contracts, missions, tasks, run records) is harness-agnostic; everything inside an adapter belongs to one runtime.
```text
adapters/<name>/adapter.sh env in: MOSAIC_SYSTEM_PROMPT_FILE, MOSAIC_REQUEST,
MOSAIC_PROVIDER, MOSAIC_MODEL
stdout: response only; stderr: diagnostics
```
- Selection: `execution.adapter` in config.json (optional; `pi` default; allowlist `pi`, `mock`)
- `pi` — pinned Pi CLI, noninteractive print mode, ambient discovery off
- `mock` — deterministic test adapter; never for real verification
- Mission directives have a sanctioned injection point: when a task references a mission, the task runner mounts the run snapshot and the generated prompt gains a `MISSION (runtime)` section (objective + directives) after the four immutable contracts
- Adding a harness (Claude, Codex, OpenCode) later means adding one directory — no orchestrator changes
See `adapters/README.md` for the full contract.
## Workspaces, capabilities, sessions (M5/M6)
Optional task fields extend what an agent can do — all defaulting to the previous behavior:
```json
{
"workspace": "demo", // ":run" ephemeral, or persistent dataRoot/workspaces/<name>
"capabilities": { "tools": ["bash", "read"] }, // pi tool allowlist; absent = no tools
"session": "demo" // persistent session at dataRoot/sessions/<name>
}
```
- The adapter runs inside the workspace; files it writes are host-visible (`dataRoot/workspaces/<name>`).
- Sessions persist via pi's documented `--session-dir`; a follow-up run in the same session resumes the conversation (`-c`) and can recall prior context. Distinct names never share state. Ephemeral (`--no-session`) remains the default when no session is declared.
- Selection authority: config for adapter/provider/model; the task file for workspace/capabilities/session.
Inspect anything:
```bash
node scripts/mosaic-task.mjs list # runs with task/workspace/session columns
node scripts/mosaic-task.mjs show <runId> # full record + snapshots + artifacts
```
Demo fixtures: `tasks/workspace-demo.json`, `tasks/session-demo-1.json` + `tasks/session-demo-2.json`.
See `docs/plans/2026-09-02_atomic-mosaic-foundation.md` for the full plan.
Inside the container:
```text
/opt/mosaic/contracts immutable contract files
/var/lib/mosaic generated runtime state (mounted from configured dataRoot)
/workspace agent workspace
```
## How it works
1. `scripts/build.sh` builds the release image (`mosaic-poc-agent:<pi>-r<release>`,
tag derived from `RELEASE` + the pinned Pi version) with Docker Compose.
2. On each run, `/opt/mosaic/src/load-contracts.sh` reads the four contract files
in fixed order (CONSTITUTION, STANDARDS, SOUL, USER), joins them with clear
separators, and writes `/var/lib/mosaic/system-prompt.md`.
3. `/opt/mosaic/src/run-agent.sh` starts Pi noninteractively
(`pi -p "Return your startup marker and nothing else."`) with
`--system-prompt "$(cat /var/lib/mosaic/system-prompt.md)"` and all ambient
discovery disabled (`--no-context-files --no-skills --no-extensions
--no-prompt-templates --no-themes`), ephemeral (`--no-session`), tool-free
(`--no-tools`), and offline for startup network operations (`--offline`).
4. `scripts/verify.sh` trims surrounding whitespace from the response and exits 0
only when it equals `MOSAIC_HELLO_OK` exactly.
## Usage
### Launching Agent Sessions
```bash
scripts/bootstrap.sh # create config.json if absent (idempotent)
scripts/build.sh # build the image
scripts/hello.sh # one-shot request; prints the model response
scripts/verify.sh # full gated test; exit 0 only on exact MOSAIC_HELLO_OK
scripts/run-task.sh # run a mission/task file (see Missions & tasks)
scripts/release.sh # package / activate / rollback / status (see Release model)
scripts/test-config.sh # fast config-layer selftests (no Docker)
scripts/test-task.sh # mission/task selftests (schema + adapter seam + live runs)
scripts/test-release.sh # release selftests
scripts/reset.sh # delete the configured data root (safety-checked)
```
Prove the failure path (acceptance criterion 9):
```bash
mosaic pi # Launch Pi with Mosaic injection
mosaic claude # Launch Claude Code with Mosaic injection
mosaic codex # Launch Codex with Mosaic injection
mosaic opencode # Launch OpenCode with Mosaic injection
mosaic yolo claude # Claude with dangerous-permissions mode
mosaic yolo pi # Pi in yolo mode
EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh # must exit nonzero
```
The launcher verifies your config, checks for `SOUL.md`, injects your `AGENTS.md` standards into the runtime, and forwards all arguments.
Pi launches default to a token-lean skill posture: `mosaic pi` passes `--no-skills` so Pi does not preload every global skill description into the system prompt. Use `MOSAIC_PI_SKILL_MODE=all mosaic pi` for the legacy all-skills catalog, or `MOSAIC_PI_SKILL_MODE=discover mosaic pi` to let Pi use its native settings/project skill discovery.
Mosaic also loads its Pi extensions from `~/.config/mosaic/runtime/pi/`. Inside Pi,
`/goal set <statement>` starts a bounded persistent loop that checks every turn and successful
compaction, requires two evidence-bearing completion reports, and can be inspected or stopped with
`/goal status`, `/goal pause`, `/goal resume`, and `/goal cancel`. Controller-owned goal-state
entries redact common credential shapes, but Pi's model/tool-call history is separate, so goals and
evidence must never contain secrets or raw sensitive output. Mosaic does not install this extension
into `~/.pi/agent/extensions/`.
### TUI & Gateway
```bash
mosaic tui # Interactive TUI connected to the gateway
mosaic gateway login # Authenticate with a gateway instance
mosaic sessions list # List active agent sessions
```
### Gateway Management
```bash
mosaic gateway install # Install and configure the gateway service
mosaic gateway verify # Post-install health check
mosaic gateway login # Authenticate and store a session token
mosaic gateway config rotate-token # Rotate your API token
mosaic gateway config recover-token # Recover a token via BetterAuth cookie
```
If you already have a gateway account but no token, use `mosaic gateway config recover-token` to retrieve one without recreating your account.
### Configuration
Mosaic supports three storage tiers: `local` (PGlite, single-host), `standalone` (PostgreSQL, single-host), and `federated` (PostgreSQL + pgvector + Valkey, multi-host). See [Federated Tier Setup](docs/federation/SETUP.md) for multi-user and production deployments, or [Migrating to Federated](docs/guides/migrate-tier.md) to upgrade from existing tiers.
```bash
mosaic config show # Print full config as JSON
mosaic config get <key> # Read a specific key
mosaic config set <key> <val># Write a key
mosaic config edit # Open config in $EDITOR
mosaic config path # Print config file path
```
### Management
```bash
mosaic doctor # Health audit — detect drift and missing files
mosaic sync # Sync skills from canonical source
mosaic skill list # Audit Claude skill registrations and conflicts
mosaic skill register <name> # Register one canonical skill with Claude Code
mosaic skill unregister <name> # Remove one Mosaic-owned Claude link
mosaic update # Update CLI/framework and auto-register canonical skills
mosaic wizard # Full guided setup wizard
mosaic bootstrap <path> # Bootstrap a repo with Mosaic standards
mosaic coord init # Initialize a new orchestration mission
mosaic prdy init # Create a PRD via guided session
```
### Sub-package Commands
Each Mosaic sub-package exposes its API surface through the unified CLI:
```bash
# User management
mosaic auth users list
mosaic auth users create
mosaic auth sso
# Agent brain (projects, missions, tasks)
mosaic brain projects
mosaic brain missions
mosaic brain tasks
mosaic brain conversations
# Agent forge pipeline
mosaic forge run [--simulate] # fails closed (FORGE_NO_EXECUTOR) with no executor wired; --simulate for typed simulated runs
mosaic forge status
mosaic forge resume [--simulate] # same fail-closed rule as forge run
mosaic forge personas
# Structured logging
mosaic log tail
mosaic log search
mosaic log export
mosaic log level
# MACP protocol
mosaic macp tasks
mosaic macp submit
mosaic macp gate
mosaic macp events
# Agent memory
mosaic memory search
mosaic memory stats
mosaic memory insights
mosaic memory preferences
# Task queue (Valkey)
mosaic queue list
mosaic queue stats
mosaic queue pause
mosaic queue resume
mosaic queue jobs
mosaic queue drain
# Object storage
mosaic storage status
mosaic storage tier
mosaic storage export
mosaic storage import
# Schema migration is unavailable in this release. The current storage wrapper shells
# directly to `pnpm --filter @mosaicstack/db db:migrate`; it is legacy N-1,
# uncertified, and MUST NOT be invoked pending KBN-101-02/-03/-06/-08 activation.
# Future schema migration is non-operative: external bootstrap → TLS/roles → runner
# --run → runner --verify → readiness. Tier copy uses only the separately held secure
# migrate-tier route.
```
### Telemetry
```bash
# Local observability (OTEL / Jaeger)
mosaic telemetry local status
mosaic telemetry local tail
mosaic telemetry local jaeger
# Remote telemetry (dry-run by default)
mosaic telemetry status
mosaic telemetry opt-in
mosaic telemetry opt-out
mosaic telemetry test
mosaic telemetry upload # Dry-run unless opted in
```
Consent state is persisted in config. Remote upload is a no-op until you run `mosaic telemetry opt-in`.
## Development
### Prerequisites
- Node.js ≥ 22
- pnpm 10.6+
- Docker & Docker Compose
### Setup
```bash
git clone [email protected]:mosaicstack/stack.git
cd stack
# Install dependencies. The local tier uses in-process PGlite; leave DATABASE_URL unset.
# The pnpm store defaults to $HOME/.local/share/pnpm/store. Override it without
# editing the checkout with NPM_CONFIG_STORE_DIR=$HOME/another-store if needed.
pnpm install
# Verify dependencies and generated state before running source-quality gates.
# Missing dependencies exit 42; stale/foreign apps/web/.next state exits 43.
# The web build certifies its exact standalone symlink manifest; added, removed,
# retargeted, or manifest-only-tampered generated links also exit 43. This detects
# accidental, independent, stale, and foreign-residue mutation—the class exposed by
# a five-month-stale .next that produced 19 phantom TS2307 errors.
# It does NOT defend against a same-UID actor that can rewrite both manifest and
# marker consistently (CWE-345). RM-59 tracks the required executor/spine-side
# trust anchor outside worktree authority.
pnpm preflight
# Optional local queue service only. This does not start PostgreSQL.
docker compose up -d valkey
# The current Gateway/Web local process is held; see docs/guides/dev-guide.md.
# Do not start it until KBN-101-02 makes inherited dotenv/DSN state fail closed.
```
### Held future procedure
The checked-in Compose PostgreSQL service mounts legacy initialization SQL and is **not** a
current PostgreSQL, standalone, or federated developer route. Do not start it with Compose,
invoke initialization SQL, or treat the planned migrator as currently executable.
**Held future activation procedure — non-operative and no current command authority until KBN-101-00, KBN-101-03, and KBN-101-05
land:** external bootstrap → TLS/roles → `mosaic-db-migrator --run`
`mosaic-db-migrator --verify` → Gateway/Compose readiness. The future deployment artifacts—not
this README—will provide the reviewed commands and secret-consumer interface.
For local data-layer work, PGlite needs no PostgreSQL service. The optional Compose command above
starts only Valkey; OTEL Collector and Jaeger may likewise be started individually if needed,
without starting PostgreSQL. A Gateway/Web local process is not currently a safe PGlite route:
its unguarded dotenv loader may inherit a daemon PostgreSQL DSN. Do not use root `pnpm dev` or a
Gateway start command until KBN-101-02 makes that state fail closed.
### Quality Gates
```bash
pnpm preflight # Checkout/dependency/generated-state validation
pnpm typecheck # TypeScript type checking (all packages)
pnpm lint # ESLint (all packages)
pnpm test # Vitest (all packages)
pnpm format:check # Prettier check
pnpm format # Prettier auto-fix
```
### CI
Woodpecker CI runs on every push:
- `pnpm install --frozen-lockfile`
- **Legacy N-1 CI status only — active, uncertified, and non-authorizing as an operator route:** the checked-in job currently invokes `pnpm --filter @mosaicstack/db run db:migrate` with `DATABASE_URL` against an isolated disposable PostgreSQL CI database. It performs direct DDL in that CI database, is not approved ordinary behavior or an operator route, and remains a known exception pending KBN-101-06 removal/replacement by the certified runner-backed CI path.
- `pnpm test` (Turbo-orchestrated across all packages)
npm packages are published to the Gitea package registry on main merges.
## Architecture
```
stack/
├── apps/
│ ├── gateway/ NestJS API + WebSocket hub (Fastify, Socket.IO, OTEL)
│ └── web/ Next.js dashboard (React 19, Tailwind)
├── packages/
│ ├── mosaic/ Unified CLI — TUI, gateway client, wizard, sub-package commands
│ ├── types/ Shared TypeScript contracts (Socket.IO typed events)
│ ├── db/ Drizzle ORM schema + migrations (pgvector)
│ ├── auth/ BetterAuth configuration
│ ├── brain/ Data layer (PG-backed)
│ ├── queue/ Valkey task queue + MCP
│ ├── coord/ Mission coordination
│ ├── forge/ Multi-stage AI pipeline (intake → board → plan → code → review)
│ ├── macp/ MACP protocol — credential resolution, gate runner, events
│ ├── agent/ Agent session management
│ ├── memory/ Agent memory layer
│ ├── log/ Structured logging
│ ├── prdy/ PRD creation and validation
│ ├── quality-rails/ Quality templates (TypeScript, Next.js, monorepo)
│ └── design-tokens/ Shared design tokens
├── plugins/
│ ├── discord/ Discord channel plugin (discord.js)
│ ├── telegram/ Telegram channel plugin (Telegraf)
│ ├── macp/ OpenClaw MACP runtime plugin
│ └── mosaic-framework/ OpenClaw framework injection plugin
├── tools/
│ └── install.sh Unified installer (framework + npm CLI, --yes / --no-auto-launch)
├── scripts/agent/ Agent session lifecycle scripts
├── docker-compose.yml Dev infrastructure
└── .woodpecker/ CI pipeline configs
```
### Key Design Decisions
- **Gateway is the single API surface** — all clients (TUI, web, Discord, Telegram) connect through it
- **ESM everywhere** — `"type": "module"`, `.js` extensions in imports, NodeNext resolution
- **Socket.IO typed events** — defined in `@mosaicstack/types`, enforced at compile time
- **OTEL auto-instrumentation** — loads before NestJS bootstrap
- **Explicit `@Inject()` decorators** — required since tsx/esbuild doesn't emit decorator metadata
### Framework (`~/.config/mosaic/`)
The framework is the bash-based standards layer installed to every developer machine:
```
~/.config/mosaic/
├── AGENTS.md ← Central standards (loaded into every runtime)
├── SOUL.md ← Agent identity (name, style, guardrails)
├── USER.md ← User profile (name, timezone, preferences)
├── TOOLS.md ← Machine-level tool reference
├── bin/mosaic ← Unified launcher (claude, codex, opencode, pi, yolo)
├── guides/ ← E2E delivery, orchestrator protocol, PRD, etc.
├── runtime/ ← Per-runtime configs (claude/, codex/, opencode/, pi/)
├── skills/ ← Universal skills (shipped with the framework package)
├── tools/ ← Tool suites (orchestrator, git, quality, prdy, etc.)
└── memory/ ← Persistent agent memory (preserved across upgrades)
```
### Forge Pipeline
Forge is a multi-stage AI pipeline for autonomous feature delivery:
```
Intake → Discovery → Board Review → Planning (3 stages) → Coding → Review → Remediation → Test → Deploy
```
Each stage has a dispatch mode (`exec` for research/review, `yolo` for coding), quality gates, and timeouts. The board review uses multiple AI personas (CEO, CTO, CFO, COO + specialists) to evaluate briefs before committing resources.
## Upgrading
Run the installer again — it handles upgrades automatically:
```bash
curl -fsSL https://mosaicstack.dev/install.sh | bash
```
Or use the direct URL:
```bash
bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/main/tools/install.sh)
```
Or use the CLI:
```bash
mosaic update # Check + install CLI updates
mosaic update --check # Check only, don't install
```
The CLI also performs a background update check on every invocation (cached for 1 hour).
### Installer Flags
```bash
bash tools/install.sh --check # Version check only
bash tools/install.sh --framework # Framework only (skip npm CLI)
bash tools/install.sh --cli # npm CLI only (skip framework)
bash tools/install.sh --next # Prerelease lane: source build from next
bash tools/install.sh --dev # Contributor lane: source build at --ref/main
bash tools/install.sh --ref v1.0 # Install from a specific git ref (--ref wins over --next)
bash tools/install.sh --yes # Non-interactive, accept all defaults
bash tools/install.sh --no-auto-launch # Skip auto-launch of wizard
```
The installer rejects unrecognized flags or positional arguments before making changes and prints the supported-option usage.
## Contributing
```bash
# Create a feature branch
git checkout -b feat/my-feature
# Make changes, then verify
pnpm typecheck && pnpm lint && pnpm test && pnpm format:check
# Commit (husky runs lint-staged automatically)
git commit -m "feat: description of change"
# Push and create PR
git push -u origin feat/my-feature
```
DTOs go in `*.dto.ts` files at module boundaries. Scratchpads (`docs/scratchpads/`) are mandatory for non-trivial tasks. See `AGENTS.md` for the full standards reference.
## License
Proprietary — all rights reserved.
## Authentication
Pi's documented container authentication (see the package's
`docs/containerization.md`) is used, in this order:
1. **Read-only mounted credential file** (default): the host pi auth file
`~/.pi/agent/auth.json` is bind-mounted read-only to
`/home/node/.pi/agent/auth.json`. The host file holds a static API-key
entry for the built-in `zai` provider, so no token refresh writes are needed.
2. **Runtime environment variable** (documented alternative): set `ZAI_API_KEY`
or `ANTHROPIC_API_KEY` in the environment or in a gitignored `.env`; compose
passes them through. Pi's documented precedence applies.
Credentials are never committed, never copied into the image, and never printed.
Mosaic-managed named accounts (`agent.sh --auth`) live under the data root
(`auth/<account>.json`, 0600) — the stack never writes into `~/.pi`.
`.env.example` contains non-secret settings only.
## Boundaries honored
- No mounts of `~/.mosaic` or `~/.config/mosaic`; no Docker socket mount.
- Source stays in this project directory; generated state only in
`/home/jwoltje/.mosaic-dev` (host) and `/var/lib/mosaic` (container).
- No database, web server, queue, second container, orchestration, Git
integration, persistent sessions, or policy machinery.
+1
View File
@@ -0,0 +1 @@
0.0.12
+30
View File
@@ -0,0 +1,30 @@
# Mosaic Stack
You are the default collaborator for Mosaic Stack: a practical engineering
partner helping people build, inspect, and operate a trustworthy foundation
for delegated work.
Mosaic Stack is deliberately small, file-based, and evidence-oriented. Its
purpose is not to perform confidence; it is to make useful work attributable,
bounded, reproducible, and reviewable. Treat the system's contracts, policies,
and run records as part of the product, not paperwork around it.
Work with calm precision. Start from what the user is trying to accomplish,
make the next useful step clear, and explain results in plain language. Be
decisive when the evidence supports a decision; be explicit about uncertainty
when it does not. Never claim a test, command, integration, or outcome that
you have not actually verified.
Respect boundaries. Ask before expanding scope, changing authority, touching
credentials, or taking an irreversible external action. Prefer the least
privileged path, preserve user work, and stop on a policy or validation
refusal rather than working around it. A clean refusal with a useful diagnosis
is better than a superficially successful but untrustworthy result.
Leave a legible trail. Make changes intentional, keep records honest, and
report what changed, how it was checked, and what remains unresolved. When
coordinating other workers, give each one a bounded objective and review their
evidence instead of treating their confidence as proof.
The aim is dependable progress: small enough to understand, safe enough to
trust, and concrete enough for a person to verify.
+54
View File
@@ -0,0 +1,54 @@
# Mosaic runtime adapters
An adapter is the entire harness-specific surface of the system. Everything
upstream of an adapter — configuration, contracts, missions, tasks, run
records — is harness-agnostic; everything inside an adapter may assume one
specific agent runtime.
## Contract
An adapter lives at:
```text
/opt/mosaic/adapters/<name>/adapter.sh
```
and must be executable. The dispatcher (`/opt/mosaic/src/run-agent.sh`)
selects it via `MOSAIC_ADAPTER` (default: `pi`) and execs it after the
system prompt has been generated.
**Inputs (environment):**
| Variable | Meaning |
|---|---|
| `MOSAIC_SYSTEM_PROMPT_FILE` | Absolute path to the generated system prompt (contracts + optional mission section). Read it; do not modify it. |
| `MOSAIC_REQUEST` | The exact user request text (may contain newlines). |
| `MOSAIC_PROVIDER` | Configured provider name. |
| `MOSAIC_MODEL` | Configured model id. |
Optional, adapter-specific (documented per adapter):
| Variable | Meaning |
|---|---|
| `MOSAIC_MOCK_RESPONSE` | mock only: the verbatim response to emit |
**Outputs:**
- `stdout`: the model response text — the only channel the orchestrator captures
- `stderr`: diagnostics (never credentials)
- exit `0`: success; nonzero: failure
## Rules
1. Adapters print ONLY the response on stdout. Status lines go to stderr.
2. Adapters never read configuration files; the resolved settings arrive via environment.
3. Adapters never write outside `/var/lib/mosaic`.
4. Adding an adapter requires: a new directory, the contract implementation, and
adding the name to the allowlist in `scripts/mosaic-config.mjs`.
## Included adapters
- `pi` — the pinned `@earendil-works/pi-coding-agent` CLI in noninteractive
print mode (`-p`), ambient discovery disabled, stdin detached.
- `mock` — deterministic echo of `MOSAIC_MOCK_RESPONSE`. Test-only: never use
it where a real model response is required.
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# Mock adapter: deterministic response for seam tests. NEVER use where a
# real model response is required.
#
# Contract: see /opt/mosaic/adapters/README.md.
set -eu
[ -n "${MOSAIC_SYSTEM_PROMPT_FILE:-}" ] || { echo "mock adapter: MOSAIC_SYSTEM_PROMPT_FILE is required" >&2; exit 2; }
if [ "${MOSAIC_INTERACTIVE:-}" != "1" ]; then
[ -n "${MOSAIC_REQUEST:-}" ] || { echo "mock adapter: MOSAIC_REQUEST is required" >&2; exit 2; }
fi
[ -r "$MOSAIC_SYSTEM_PROMPT_FILE" ] || { echo "mock adapter: system prompt not readable: $MOSAIC_SYSTEM_PROMPT_FILE" >&2; exit 2; }
echo "mock adapter: responding verbatim from MOSAIC_MOCK_RESPONSE" >&2
# Deterministic plumbing evidence: which MOSAIC_* variables did the
# orchestrator actually deliver? (Auth secrets are not MOSAIC_-prefixed.)
(env | grep '^MOSAIC_' | sort) >&2 2>/dev/null || true
printf '%s\n' "${MOSAIC_MOCK_RESPONSE:-}"
+96
View File
@@ -0,0 +1,96 @@
#!/bin/sh
# Pi adapter: implements the Mosaic adapter contract for the pinned
# @earendil-works/pi-coding-agent CLI.
#
# Contract: see /opt/mosaic/adapters/README.md.
# Headless (default): stdout = response only; stderr = diagnostics; exit 0.
# Interactive (MOSAIC_INTERACTIVE=1): full pi TUI on the attached terminal.
set -eu
[ -n "${MOSAIC_SYSTEM_PROMPT_FILE:-}" ] || { echo "pi adapter: MOSAIC_SYSTEM_PROMPT_FILE is required" >&2; exit 2; }
[ -r "$MOSAIC_SYSTEM_PROMPT_FILE" ] || { echo "pi adapter: system prompt not readable: $MOSAIC_SYSTEM_PROMPT_FILE" >&2; exit 2; }
# MOSAIC_AGENT_NAME is optional in headless mode (identity section is then
# omitted); interactive launches always set it via scripts/agent.sh.
: "${PI_PROVIDER:?pi adapter: PI_PROVIDER is required}"
: "${PI_MODEL:?pi adapter: PI_MODEL is required}"
INTERACTIVE="${MOSAIC_INTERACTIVE:-}"
if [ "$INTERACTIVE" != "1" ]; then
[ -n "${MOSAIC_REQUEST:-}" ] || { echo "pi adapter: MOSAIC_REQUEST is required" >&2; exit 2; }
fi
# Workspace (M5): run inside the provided workspace when present.
if [ -n "${MOSAIC_WORKSPACE:-}" ]; then
mkdir -p "$MOSAIC_WORKSPACE"
cd "$MOSAIC_WORKSPACE"
fi
# Session (M6/M11): default ephemeral (--no-session). With a declared
# session dir: persist there and resume the most recent session. With a
# fork source: branch the source session file into the target dir
# (pi --fork) - the ancestor session is never modified.
SESSION_FLAGS="--no-session"
if [ -n "${MOSAIC_SESSION_FORK:-}" ]; then
[ -n "${MOSAIC_SESSION_DIR:-}" ] || { echo "pi adapter: session fork requires MOSAIC_SESSION_DIR" >&2; exit 2; }
mkdir -p "$MOSAIC_SESSION_DIR"
SESSION_FLAGS="--fork $MOSAIC_SESSION_FORK --session-dir $MOSAIC_SESSION_DIR"
elif [ -n "${MOSAIC_SESSION_DIR:-}" ]; then
mkdir -p "$MOSAIC_SESSION_DIR"
SESSION_FLAGS="--session-dir $MOSAIC_SESSION_DIR"
if [ -n "$(ls -A "$MOSAIC_SESSION_DIR" 2>/dev/null)" ]; then
SESSION_FLAGS="$SESSION_FLAGS -c"
fi
fi
# Capabilities (M5): explicit allowlist or no tools.
TOOLS_FLAG="--no-tools"
[ -n "${MOSAIC_TOOLS:-}" ] && TOOLS_FLAG="--tools $MOSAIC_TOOLS"
# Skills (M17): explicitly provided skill dirs replace discovery. When none
# are provided the agent runs with --no-skills (nothing ambient to find).
SKILLS_FLAG="--no-skills"
if [ -n "${MOSAIC_SKILLS:-}" ]; then
SKILLS_FLAG=""
OLDIFS=$IFS; IFS=','
for s in $MOSAIC_SKILLS; do
[ -d "$s" ] || { echo "pi adapter: skill dir missing: $s" >&2; exit 2; }
SKILLS_FLAG="$SKILLS_FLAG --skill $s"
done
IFS=$OLDIFS
fi
# Mode (M13): interactive TUI or one-shot print.
PRINT_MODE="-p"
REQUEST_ARG=""
if [ "$INTERACTIVE" = "1" ]; then
PRINT_MODE=""
else
REQUEST_ARG="$MOSAIC_REQUEST"
fi
# All flags documented in the pi package README (CLI Reference):
# -p/--print one-shot mode: print the response and exit (omitted in
# interactive TUI mode)
# --system-prompt replace the default prompt with the generated one
# --no-* no ambient context/skills/extensions/templates/themes
# SESSION_FLAGS ephemeral | persistent | forked (per env)
# TOOLS_FLAG per capabilities
# --offline no startup network operations (update checks/telemetry)
PROMPT_CONTENT="$(cat "$MOSAIC_SYSTEM_PROMPT_FILE")"
set -- \
--offline \
--no-extensions \
$SKILLS_FLAG \
--no-prompt-templates \
--no-themes \
--no-context-files \
$TOOLS_FLAG \
$SESSION_FLAGS \
--provider "$PI_PROVIDER" \
--model "$PI_MODEL" \
--system-prompt "$PROMPT_CONTENT"
# One-shot mode appends -p and the request (both safely quoted);
# interactive mode appends nothing - clean TUI.
[ "$INTERACTIVE" = "1" ] || set -- "$@" -p "$MOSAIC_REQUEST"
exec pi "$@"
+38
View File
@@ -0,0 +1,38 @@
===== DARKWING NATIVE DEVELOPMENT CONTEXT =====
Your identity is Darkwing. This launch runs Pi directly on the host, in the
Mosaic Stack development repository. The injected SOUL defines your persona;
CONSTITUTION and STANDARDS supply governance, USER supplies user context,
and AGENTS.md supplies repository instructions.
You have host read, bash, edit, write, grep, find, and ls tools. This is a
development TUI with the operator's OS access, not a sandbox or a registered
managed fleet seat. Use repository scripts for Mosaic operations and inspect
their effects before running them. Container paths in skills describe worker
deployments, not your current workspace. A tool's presence is not authority
to change unrelated files, other agents' work, or the live fleet.
For an assigned improvement, inspect the implementation, reproduce the issue,
make the smallest useful change, verify it, and continue through the authorized
outcome. Read docs/plans/CURRENT.md to reconcile ownership and existing gates;
a new user assignment does not silently resume unrelated queued work.
The local /goal extension is loaded and owns any operator-set goal lifecycle.
Use ms-proactive-agent for work selection and ms-goal for recovery guidance;
do not create a competing goal loop. Follow goal_report's actual schema and
reporting instructions. Its text format is Just Completed / Next Step /
Blocked, with '* none' for empty sections. No external reporting skill is
needed to discover that format. Native development packaging supersedes
older skill statements that this extension is unavailable.
For relocation recovery, read agents/darkwing/work/RESTART.md after the root
AGENTS.md and docs/plans/CURRENT.md. It records verified checkpoints and limits,
not a new assignment. The canonical checkout is /mnt/storage/src/mosaic-stack;
v1/ is archived legacy source. Reconcile newer owner direction before acting.
Conversation history persists across launcher restarts. Goals belong to a
single process incarnation; recover the assignment from verified records and
the operator's direction after a restart. No goal is started by this launcher.
Context is captured anew at launch; source edits do not update this process's
injected snapshot. Relaunch to load approved context changes.
+64
View File
@@ -0,0 +1,64 @@
# Darkwing development TUI
From any terminal, run:
```sh
/home/jwoltje/src/mosaic-stack-dev-test/agents/darkwing/launch.sh
```
The agent launcher is a thin shim to `scripts/agent.sh --host-dev darkwing`,
forwarding all arguments unchanged. `scripts/agent.sh` is the common entry
point; `scripts/agent-host-dev.sh` implements its native development mode.
The host launcher opens the repository as Darkwing's workspace.
It uses the repository-pinned Pi, the configured Mosaic provider/model, and
native Pi authentication (normal `~/.pi/agent`, or `PI_CODING_AGENT_DIR` if
explicitly set). It never copies credentials. Install dependencies with
`npm ci --ignore-scripts --no-audit --no-fund` if needed.
`--check` validates configuration and required inputs without opening Pi or
calling a model. `--fresh` starts a new conversation without deleting earlier
ones. Normal launches continue the latest conversation under
`.pi/state/darkwing/sessions/`; the first launch creates one. A launcher lock
rejects simultaneous launches through this script. It does not exclude Pi
processes started another way. Damaged JSONL history refuses automatic resume;
`--fresh` is an explicit escape hatch that preserves the damaged evidence.
The current files are combined into a private launch snapshot under
`.pi/state/darkwing/launches/`:
- `contracts/CONSTITUTION.md` and `contracts/STANDARDS.md`
- `agents/darkwing/SOUL.md`
- `<configured dataRoot>/user/USER.md`, the deployment's live user profile
- the repository's `AGENTS.md` and Darkwing's `CONTEXT.md`
Use `--soul FILE`, `--constitution FILE`, or `--user FILE` to select alternate
inputs, including a future `contracts/USER.md`. Relative paths resolve from
the repository root. Missing or empty inputs refuse launch. Snapshots can
contain personal context and remain local, with private file permissions.
Context edits take effect on relaunch, including when resuming a conversation.
The launcher enables coding/search tools, `goal_report`, ten explicit local
skills, and the canonical goal extension through `scripts/sync-dev-extensions.sh`.
Ambient context, skills, extensions, templates, and themes are disabled.
The normal Pi coding prompt is retained with the Mosaic context appended.
Enter `/goal <assignment and acceptance criteria>` to start continuing work;
`/goal stop`, `/goal resume`, and `/goal` pause, resume, and inspect it. A new
process does not automatically adopt a previous process's goal.
This TUI has the operator's host access, including repository edits and host
commands. Its tool list is not OS isolation. It creates no managed role or
fleet registration. Worker dispatch still uses the governed Mosaic task runner.
The user supplies the assignment; launch alone does not start self-modification.
## Deployment findings
The existing `scripts/agent.sh` launches a Docker container, defaults to the
`agent-<name>` session directory, and asks Pi to continue when that directory
is nonempty. Its default workspace is `<dataRoot>/workspaces/<name>`, not this
checkout. `src/load-contracts.sh` loads image-baked governance, an optional
seat SOUL override, live user Markdown, and mission context into a shared
prompt path. A seat override requires `agent.json`; a standalone SOUL is not
discovered. `adapters/pi/adapter.sh` disables extensions. The temporary host
launcher follows the existing native development path to provide repository
access and `/goal`, and keeps its conversations separate from container and
live fleet sessions. It does not invoke release alignment on startup.
+20
View File
@@ -0,0 +1,20 @@
# SOUL — Darkwing
You are Darkwing, Mosaic Stack's hands-on engineering collaborator. Your job
is to help Jason make the system dependable by using it, finding where it
falls short, and carrying authorized improvements through verification.
Be curious, direct, and resourceful. Have a technical opinion and explain
the evidence behind it. Investigate before guessing. Distinguish a design
claim, a passing test, and behavior you have observed in the running system.
Use Mosaic's own tools and workflows where they fit. Turn a failure into a
reproducible case, make a focused correction, and test the behavior again.
Let each verified improvement inform the next one within the assignment.
Keep the human informed when the result, scope, or next decision changes.
Own the outcome while respecting other agents' work. Preserve their changes
and records, give delegated work clear boundaries, and seek independent
review where required. Self-improvement never grants new authority: changing
your instructions, permissions, or a live deployment follows the same review
and authorization rules as any other system change.
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Darkwing's native development mode through the Mosaic agent entry point.
set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
exec "$REPO/scripts/agent.sh" --host-dev darkwing "$@"
+21
View File
@@ -0,0 +1,21 @@
// Refuse damaged history before Pi's --continue can silently skip it.
import { readFileSync, lstatSync } from 'node:fs';
try {
for (const file of process.argv.slice(2)) {
if (!lstatSync(file).isFile()) throw new Error(`not a regular session file: ${file}`);
const lines = readFileSync(file, 'utf8').trim().split('\n');
const entries = lines.map((line) => JSON.parse(line));
const header = entries[0];
if (header?.type !== 'session' || typeof header.id !== 'string' || !header.id ||
typeof header.version !== 'number' || typeof header.cwd !== 'string' ||
!Number.isFinite(Date.parse(header.timestamp)) ||
entries.slice(1).some((entry) => !entry || typeof entry.type !== 'string')) {
throw new Error(`invalid session structure: ${file}`);
}
if (header.cwd !== process.cwd()) throw new Error(`session belongs to another workspace: ${file}`);
}
} catch (error) {
console.error(`darkwing: cannot safely resume: ${error.message}; inspect history or explicitly use --fresh`);
process.exit(1);
}
+108
View File
@@ -0,0 +1,108 @@
# Darkwing — relocation handoff
Recorded 2026-09-07 17:43 UTC. Jason intends to relaunch with
`/mnt/storage/src/mosaic-stack/agents/darkwing/launch.sh`.
This is a recovery note, not a new assignment or automatic goal resumption.
## Read first
1. Root `AGENTS.md` and `docs/plans/CURRENT.md`.
2. This note, then `git status --short` and `git log --oneline -5`.
3. Reconcile current owner direction and any newer declared artifacts before acting.
## Repository conversion is completed locally
Jason explicitly ordered the conversion and confirmed no work was active.
- Canonical checkout: `/mnt/storage/src/mosaic-stack`.
- Origin: `https://git.mosaicstack.dev/mosaicstack/stack`.
- Branch: `refactor`.
- Conversion commit: `127a54fdff1fe6ae56c3197edddf957481465db4`.
- New foundation is at root. `v1/` is legacy archival source, NOT current code.
- Old `/home/jwoltje/src/mosaic-stack-dev-test` is a compatibility symlink to this
same checkout. Do not recreate a second working copy there.
- Both histories retained: merge parents v2 `9a5fbdbda74b16adf488fe28138b2ba69ea5e669`
and v1 `5d2770002612a09ae0cadc129b4ea30619133e8a`.
- Exact 3,507-file v1 tracked tree imported; v1 refs under `refs/archive/v1/`.
- Original v2 refs retained; `stack-v2-archive` remote has a disabled push URL.
- Only legacy tracked tree and four conversion docs committed. All earlier
uncommitted/untracked/ignored work preserved. Index was verified clean.
- Issue https://git.mosaicstack.dev/mosaicstack/stack/issues/1495 closed explicitly
for local conversion. No push, PR/trunk merge or live-service change occurred.
Record: `docs/plans/2026-09-07_repository-consolidation-completed.md`.
Receipts: `docs/plans/reviews/2026-09-07_repository-conversion-verification.json`
and `2026-09-07_repository-conversion-postcommit-verification.json`.
Verified rollback copies, NOT development roots:
- `/mnt/storage/src/.mosaic-stack-conversion-20260907T172430Z/`
- `/home/jwoltje/src/.mosaic-stack-dev-test.pre-conversion-20260907T172430Z`
Do not delete them, launch from them or restore over newer work.
## Current unfinished foundation gate
Jason's A9 acceptance of the first offline synthetic scope/permission inspector
is pending. Code is independently approved by Filbert; no blocking code finding
remains at the reviewed r6 candidate. Owner acceptance is not inferred from tests.
- Manifest: `docs/plans/reviews/2026-09-07_foundation-inspector-rocko-build-manifest-r6.json`
SHA-256 `a4a4493000aff5905337a643886ca36e7c5377d52deed77b8aeab7174ca73dcf`.
- Report: `docs/plans/reviews/2026-09-07_foundation-inspector-rocko-build-r6.md`
SHA-256 `ee0e83efd7c71eddecf5e26f939e9a34ba85b184cfcd1cffac9ff9e56ea13c37`.
- APPROVED verdict: `docs/plans/reviews/2026-09-07_foundation-inspector-code-verdict-r6.md`
SHA-256 `ab9dd5e5c3cad5c9263e873ff82cac444da2d36040e907e4798b208fa1c08b13`.
- Guide: `docs/plans/reviews/2026-09-07_foundation-inspector-demo.md`.
All 382 approved inspector files and pinned inputs survived conversion unchanged.
Actual offline checks: Node 80/0, selftests 43/0, oracle 1,568 records / zero
schema disagreements, foundation checker PASS, config/auth/conductor 24/15/17.
Postcommit conductor 17/0 and four CLI demos passed: allowed read, allowed change
PREVIEW (no mutation), missing-registration refusal, unresolved reassignment with
original selection retained. Demo inputs are separate synthetic scenarios.
`test-task.sh` and `test-release.sh` remain NOT RUN / DEFERRED under Jason's bounded
offline-demo ruling. No deployment/native/live/provider/security certification.
Reviewer qualifications: ordering equality means structural equality, not byte
identity; auxiliary native-parser warm-run anomalies remain separate unresolved
observations, not a passing universal parser-equivalence claim. Preserve all earlier
NOT APPROVED reviews and the historical correction that r3 ran unauthorized live
branches; later deferral did not retroactively authorize them.
## Ownership and limits
- Rocko authored inspector code; Filbert independently reviewed; Darkwing coordinates
and verifies. Keep the approved candidate frozen unless a new fix is authorized.
- No automatic permission to push, merge to next/main, deploy, change live config,
grant permissions, access credentials, investigate ~/.mosaic, or start new runtime
work. Local conversion authority is not authority for those activities.
- Preserve unrelated pending work. In particular `scripts/agent.sh`, `docs/TOOLS.md`,
host launcher/context files and other untracked concepts/skills belong to existing
work. Do not blanket-stage/reset/clean. Root logs and CURRENT remain uncommitted.
- Foundation #53 in the old stack-v2 project remains a separate open issue; do not
silently close or renumber it. Accepted historical SHA/path citations remain valid.
- Rocko's Archify C1 remains HELD for owner T2/T3 decisions. No lane reassignment.
- Future durability/workflow/evidence/federation/onboarding topics are notes, not
authorization to expand the inspector.
## Communications
Use only `tools/tmux/agent-send.sh`; sender `dragon-lin:darkwing`.
Rocko: `-L mosaic-fleet -s '=rocko'`; Filbert/Dewey:
`-L default -s '=filbert'` / `'=dewey'`.
Conversion notice delivered to Rocko. Filbert/Dewey sends were unconfirmed
(input boxes not locatable); no retries, no acknowledgement claimed. Check declared
artifact paths as well as direct messages; completed reviews have existed without
transported replies. Do not inspect private panes or blindly resend.
## Relaunch and goal recovery
The project launcher continues its own latest `.pi/state/darkwing/sessions/`
conversation by default. Do NOT assume this pre-launch conversation is already in
that store or that the next launch resumes this exact conversation. This handoff
is the durable bridge. No session-tree migration or launch was performed here.
The goal extension owns lifecycle. The earlier extension goal had been paused;
no restart automatically resumes it. Reconcile the actual new process state and
Jason's direction rather than reporting progress against a guessed old goal or
creating a second goal loop. Launch alone grants no new assignment.
This handoff and its CONTEXT pointer are documentation-only. Launcher scripts,
private sessions, credentials and runtime configuration were not modified.
+5
View File
@@ -0,0 +1,5 @@
# SOUL - researcher
You are the researcher seat of the Mosaic fleet. You are curious, methodical,
and precise. You cite what you know, admit what you do not, and never guess
when you can verify.
+6
View File
@@ -0,0 +1,6 @@
{
"agentVersion": 1,
"name": "researcher",
"role": "researcher",
"capabilities": { "tools": ["read", "bash"] }
}
@@ -1,58 +0,0 @@
import path from 'node:path';
import fs from 'node:fs';
/**
* Resolves a user-provided path and verifies it is inside the allowed sandbox directory.
* Throws SandboxEscapeError if the resolved path is outside the sandbox.
*
* Uses realpathSync to resolve symlinks in the sandbox root. The user-supplied path
* is checked for containment AFTER lexical resolution but BEFORE resolving any symlinks
* within the user path — so symlink escape attempts are caught too.
*
* @param userPath - The path provided by the agent (may be relative or absolute)
* @param sandboxDir - The allowed root directory (already validated on session creation)
* @returns The resolved absolute path, guaranteed to be within sandboxDir
*/
export function guardPath(userPath: string, sandboxDir: string): string {
const resolved = path.resolve(sandboxDir, userPath);
const sandboxResolved = fs.realpathSync.native(sandboxDir);
// Normalize both paths to resolve any symlinks in the sandbox root itself.
// For the user path, we check containment BEFORE resolving symlinks in the path
// (so we catch symlink escape attempts too — the resolved path must still be under sandbox)
if (!resolved.startsWith(sandboxResolved + path.sep) && resolved !== sandboxResolved) {
throw new SandboxEscapeError(userPath, sandboxDir, resolved);
}
return resolved;
}
/**
* Validates a path without resolving symlinks in the user-provided portion.
* Use for paths that may not exist yet (creates, writes).
*
* Performs a lexical containment check only using path.resolve.
*/
export function guardPathUnsafe(userPath: string, sandboxDir: string): string {
const resolved = path.resolve(sandboxDir, userPath);
const sandboxAbs = path.resolve(sandboxDir);
if (!resolved.startsWith(sandboxAbs + path.sep) && resolved !== sandboxAbs) {
throw new SandboxEscapeError(userPath, sandboxDir, resolved);
}
return resolved;
}
export class SandboxEscapeError extends Error {
constructor(
public readonly userPath: string,
public readonly sandboxDir: string,
public readonly resolvedPath: string,
) {
super(
`Path escape attempt blocked: "${userPath}" resolves to "${resolvedPath}" which is outside sandbox "${sandboxDir}"`,
);
this.name = 'SandboxEscapeError';
}
}
-50
View File
@@ -1,50 +0,0 @@
import { test, expect } from '@playwright/test';
import { loginAs, TEST_USER } from './helpers/auth.js';
test.describe('Chat page', () => {
test.beforeEach(async ({ page }) => {
await loginAs(page, TEST_USER.email, TEST_USER.password);
// If login failed (no seeded user in env) we may be on /login — skip
const url = page.url();
test.skip(!url.includes('/chat'), 'No seeded test user — skipping authenticated tests');
});
test('chat page loads and shows the welcome message or conversation list', async ({ page }) => {
await page.goto('/chat');
// Either there are conversations listed or the welcome empty-state is shown
const hasWelcome = await page
.getByRole('heading', { name: /welcome to mosaic chat/i })
.isVisible()
.catch(() => false);
const hasConversationPanel = await page
.locator('[data-testid="conversation-list"], nav, aside')
.first()
.isVisible()
.catch(() => false);
expect(hasWelcome || hasConversationPanel).toBe(true);
});
test('new conversation button is visible', async ({ page }) => {
await page.goto('/chat');
// "Start new conversation" button or a "+" button in the sidebar
const newConvButton = page.getByRole('button', { name: /new conversation|start new/i }).first();
await expect(newConvButton).toBeVisible({ timeout: 10_000 });
});
test('clicking new conversation shows a chat input area', async ({ page }) => {
await page.goto('/chat');
// Find any button that creates a new conversation
const newBtn = page.getByRole('button', { name: /new conversation|start new/i }).first();
await newBtn.click();
// After creating, a text input for sending messages should appear
const chatInput = page.getByRole('textbox').or(page.locator('textarea')).first();
await expect(chatInput).toBeVisible({ timeout: 10_000 });
});
test('sidebar navigation is present on chat page', async ({ page }) => {
await page.goto('/chat');
// The app-shell sidebar should be visible
await expect(page.getByRole('link', { name: /chat/i }).first()).toBeVisible();
});
});
-23
View File
@@ -1,23 +0,0 @@
import type { Page } from '@playwright/test';
export const TEST_USER = {
email: process.env['E2E_USER_EMAIL'] ?? '[email protected]',
password: process.env['E2E_USER_PASSWORD'] ?? 'password123',
name: 'E2E Test User',
};
export const ADMIN_USER = {
email: process.env['E2E_ADMIN_EMAIL'] ?? '[email protected]',
password: process.env['E2E_ADMIN_PASSWORD'] ?? 'adminpass123',
name: 'E2E Admin User',
};
/**
* Fill the login form and submit. Waits for navigation after success.
*/
export async function loginAs(page: Page, email: string, password: string): Promise<void> {
await page.goto('/login');
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: /sign in/i }).click();
}
-6
View File
@@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
-32
View File
@@ -1,32 +0,0 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
transpilePackages: ['@mosaicstack/design-tokens'],
// Enable gzip/brotli compression for all responses.
compress: true,
// Reduce bundle size: disable source maps in production builds.
productionBrowserSourceMaps: false,
// Image optimisation: allow the gateway origin as an external image source.
images: {
formats: ['image/avif', 'image/webp'],
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
],
},
// Experimental: enable React compiler for automatic memoisation (Next 15+).
// Falls back gracefully if the compiler plugin is not installed.
experimental: {
// Turbopack is the default in dev for Next 15; keep it opt-in for now.
// turbo: {},
},
};
export default nextConfig;
-32
View File
@@ -1,32 +0,0 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Playwright E2E configuration for Mosaic web app.
*
* Assumes:
* - Next.js web app running on http://localhost:3000
* - NestJS gateway running on http://localhost:14242
*
* Run with: pnpm --filter @mosaicstack/web test:e2e
*/
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env['CI'],
retries: process.env['CI'] ? 2 : 0,
workers: process.env['CI'] ? 1 : undefined,
reporter: 'html',
use: {
baseURL: process.env['PLAYWRIGHT_BASE_URL'] ?? 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
// Do NOT auto-start the dev server — tests assume it is already running.
// webServer is intentionally omitted so tests can run against a live env.
});
-14
View File
@@ -1,14 +0,0 @@
import type { ReactNode } from 'react';
import { GuestGuard } from '@/components/guest-guard';
export default function AuthLayout({ children }: { children: ReactNode }): React.ReactElement {
return (
<GuestGuard>
<div className="flex min-h-screen items-center justify-center bg-surface-bg">
<div className="w-full max-w-md rounded-xl border border-surface-border bg-surface-card p-8 shadow-lg">
{children}
</div>
</div>
</GuestGuard>
);
}
-139
View File
@@ -1,139 +0,0 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { api } from '@/lib/api';
import { authClient, signIn } from '@/lib/auth-client';
import type { SsoProviderDiscovery } from '@/lib/sso';
import { SsoProviderButtons } from '@/components/auth/sso-provider-buttons';
export default function LoginPage(): React.ReactElement {
const router = useRouter();
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [ssoProviders, setSsoProviders] = useState<SsoProviderDiscovery[]>([]);
const [ssoLoadingProviderId, setSsoLoadingProviderId] = useState<
SsoProviderDiscovery['id'] | null
>(null);
useEffect(() => {
api<SsoProviderDiscovery[]>('/api/sso/providers')
.catch(() => [] as SsoProviderDiscovery[])
.then((providers) => setSsoProviders(providers.filter((provider) => provider.configured)));
}, []);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>): Promise<void> {
e.preventDefault();
setError(null);
setLoading(true);
const form = new FormData(e.currentTarget);
const email = form.get('email') as string;
const password = form.get('password') as string;
const result = await signIn.email({ email, password });
if (result.error) {
setError(result.error.message ?? 'Sign in failed');
setLoading(false);
return;
}
router.push('/chat');
}
async function handleSsoSignIn(providerId: SsoProviderDiscovery['id']): Promise<void> {
setError(null);
setSsoLoadingProviderId(providerId);
try {
const result = await authClient.signIn.oauth2({
providerId,
callbackURL: '/chat',
newUserCallbackURL: '/chat',
});
if (result.error) {
setError(result.error.message ?? `Sign in with ${providerId} failed`);
setSsoLoadingProviderId(null);
}
} catch (err: unknown) {
setError(err instanceof Error ? err.message : `Sign in with ${providerId} failed`);
setSsoLoadingProviderId(null);
}
}
return (
<div>
<h1 className="text-2xl font-semibold">Sign in</h1>
<p className="mt-1 text-sm text-text-secondary">Sign in to your Mosaic account</p>
{error && (
<div
role="alert"
className="mt-4 rounded-lg border border-error/30 bg-error/10 px-4 py-3 text-sm text-error"
>
{error}
</div>
)}
<form className="mt-6 space-y-4" onSubmit={handleSubmit}>
<div>
<label htmlFor="email" className="block text-sm font-medium text-text-secondary">
Email
</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
disabled={loading}
className="mt-1 block w-full rounded-lg border border-surface-border bg-surface-elevated px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
placeholder="[email protected]"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-text-secondary">
Password
</label>
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
disabled={loading}
className="mt-1 block w-full rounded-lg border border-surface-border bg-surface-elevated px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
placeholder="••••••••"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-surface-card disabled:opacity-50"
>
{loading ? 'Signing in...' : 'Sign in'}
</button>
</form>
<SsoProviderButtons
providers={ssoProviders}
loadingProviderId={ssoLoadingProviderId}
onOidcSignIn={(providerId) => {
void handleSsoSignIn(providerId);
}}
/>
<p className="mt-4 text-center text-sm text-text-muted">
Don&apos;t have an account?{' '}
<Link href="/register" className="text-blue-400 hover:text-blue-300">
Sign up
</Link>
</p>
</div>
);
}
-114
View File
@@ -1,114 +0,0 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { signUp } from '@/lib/auth-client';
export default function RegisterPage(): React.ReactElement {
const router = useRouter();
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>): Promise<void> {
e.preventDefault();
setError(null);
setLoading(true);
const form = new FormData(e.currentTarget);
const name = form.get('name') as string;
const email = form.get('email') as string;
const password = form.get('password') as string;
const result = await signUp.email({ name, email, password });
if (result.error) {
setError(result.error.message ?? 'Registration failed');
setLoading(false);
return;
}
router.push('/chat');
}
return (
<div>
<h1 className="text-2xl font-semibold">Create account</h1>
<p className="mt-1 text-sm text-text-secondary">Get started with Mosaic</p>
{error && (
<div
role="alert"
className="mt-4 rounded-lg border border-error/30 bg-error/10 px-4 py-3 text-sm text-error"
>
{error}
</div>
)}
<form className="mt-6 space-y-4" onSubmit={handleSubmit}>
<div>
<label htmlFor="name" className="block text-sm font-medium text-text-secondary">
Name
</label>
<input
id="name"
name="name"
type="text"
autoComplete="name"
required
disabled={loading}
className="mt-1 block w-full rounded-lg border border-surface-border bg-surface-elevated px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
placeholder="Your name"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-text-secondary">
Email
</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
disabled={loading}
className="mt-1 block w-full rounded-lg border border-surface-border bg-surface-elevated px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
placeholder="[email protected]"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-text-secondary">
Password
</label>
<input
id="password"
name="password"
type="password"
autoComplete="new-password"
required
disabled={loading}
className="mt-1 block w-full rounded-lg border border-surface-border bg-surface-elevated px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
placeholder="••••••••"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-surface-card disabled:opacity-50"
>
{loading ? 'Creating account...' : 'Create account'}
</button>
</form>
<p className="mt-4 text-center text-sm text-text-muted">
Already have an account?{' '}
<Link href="/login" className="text-blue-400 hover:text-blue-300">
Sign in
</Link>
</p>
</div>
);
}
-531
View File
@@ -1,531 +0,0 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
import { AdminRoleGuard } from '@/components/admin-role-guard';
import { api } from '@/lib/api';
import { cn } from '@/lib/cn';
// ── Types ──────────────────────────────────────────────────────────────────────
interface UserDto {
id: string;
name: string;
email: string;
role: string;
banned: boolean;
banReason: string | null;
createdAt: string;
updatedAt: string;
}
interface UserListDto {
users: UserDto[];
total: number;
}
interface ServiceStatusDto {
status: 'ok' | 'error';
latencyMs?: number;
error?: string;
}
interface ProviderStatusDto {
id: string;
name: string;
available: boolean;
modelCount: number;
}
interface HealthStatusDto {
status: 'ok' | 'degraded' | 'error';
database: ServiceStatusDto;
cache: ServiceStatusDto;
agentPool: { activeSessions: number };
providers: ProviderStatusDto[];
checkedAt: string;
}
// ── Admin Page ─────────────────────────────────────────────────────────────────
export default function AdminPage(): React.ReactElement {
return (
<AdminRoleGuard>
<AdminContent />
</AdminRoleGuard>
);
}
function AdminContent(): React.ReactElement {
const [activeTab, setActiveTab] = useState<'users' | 'health'>('users');
return (
<div className="mx-auto max-w-5xl space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-semibold text-text-primary">Admin Panel</h1>
</div>
<div className="flex gap-1 border-b border-surface-border">
{(['users', 'health'] as const).map((tab) => (
<button
key={tab}
type="button"
onClick={() => setActiveTab(tab)}
className={cn(
'px-4 py-2 text-sm font-medium capitalize transition-colors',
activeTab === tab
? 'border-b-2 border-blue-500 text-blue-400'
: 'text-text-secondary hover:text-text-primary',
)}
>
{tab === 'users' ? 'User Management' : 'System Health'}
</button>
))}
</div>
{activeTab === 'users' ? <UsersTab /> : <HealthTab />}
</div>
);
}
// ── Users Tab ──────────────────────────────────────────────────────────────────
function UsersTab(): React.ReactElement {
const [users, setUsers] = useState<UserDto[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [showCreate, setShowCreate] = useState(false);
const loadUsers = useCallback(async () => {
setLoading(true);
setError(null);
try {
const data = await api<UserListDto>('/api/admin/users');
setUsers(data.users);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load users');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void loadUsers();
}, [loadUsers]);
async function handleRoleToggle(user: UserDto): Promise<void> {
const newRole = user.role === 'admin' ? 'member' : 'admin';
try {
await api(`/api/admin/users/${user.id}/role`, {
method: 'PATCH',
body: { role: newRole },
});
await loadUsers();
} catch (err) {
alert(err instanceof Error ? err.message : 'Failed to update role');
}
}
async function handleBanToggle(user: UserDto): Promise<void> {
const endpoint = user.banned ? 'unban' : 'ban';
try {
await api(`/api/admin/users/${user.id}/${endpoint}`, { method: 'POST' });
await loadUsers();
} catch (err) {
alert(err instanceof Error ? err.message : 'Failed to update ban status');
}
}
async function handleDelete(user: UserDto): Promise<void> {
if (!confirm(`Delete user ${user.email}? This cannot be undone.`)) return;
try {
await api(`/api/admin/users/${user.id}`, { method: 'DELETE' });
await loadUsers();
} catch (err) {
alert(err instanceof Error ? err.message : 'Failed to delete user');
}
}
if (loading) {
return <p className="text-sm text-text-muted">Loading users...</p>;
}
if (error) {
return (
<div className="rounded-lg border border-red-500/30 bg-red-500/10 p-4">
<p className="text-sm text-red-400">{error}</p>
<button
type="button"
onClick={() => void loadUsers()}
className="mt-2 text-xs text-red-300 underline hover:no-underline"
>
Retry
</button>
</div>
);
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-sm text-text-muted">{users.length} user(s)</p>
<button
type="button"
onClick={() => setShowCreate(true)}
className="rounded-md bg-blue-600 px-3 py-1.5 text-sm text-white transition-colors hover:bg-blue-700"
>
+ New User
</button>
</div>
{showCreate && (
<CreateUserForm
onCancel={() => setShowCreate(false)}
onCreated={() => {
setShowCreate(false);
void loadUsers();
}}
/>
)}
{users.length === 0 ? (
<div className="rounded-lg border border-surface-border bg-surface-card p-6 text-center">
<p className="text-sm text-text-muted">No users found</p>
</div>
) : (
<div className="overflow-hidden rounded-lg border border-surface-border">
<table className="w-full">
<thead>
<tr className="border-b border-surface-border bg-surface-elevated text-left text-xs text-text-muted">
<th className="px-4 py-2 font-medium">Name / Email</th>
<th className="px-4 py-2 font-medium">Role</th>
<th className="hidden px-4 py-2 font-medium md:table-cell">Status</th>
<th className="hidden px-4 py-2 font-medium md:table-cell">Created</th>
<th className="px-4 py-2 font-medium">Actions</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id} className="border-b border-surface-border last:border-b-0">
<td className="px-4 py-3">
<div className="text-sm font-medium text-text-primary">{user.name}</div>
<div className="text-xs text-text-muted">{user.email}</div>
</td>
<td className="px-4 py-3">
<span
className={cn(
'inline-flex rounded-full px-2 py-0.5 text-xs font-medium',
user.role === 'admin'
? 'bg-purple-500/20 text-purple-400'
: 'bg-surface-elevated text-text-secondary',
)}
>
{user.role}
</span>
</td>
<td className="hidden px-4 py-3 md:table-cell">
{user.banned ? (
<span className="inline-flex rounded-full bg-red-500/20 px-2 py-0.5 text-xs font-medium text-red-400">
Banned
</span>
) : (
<span className="inline-flex rounded-full bg-green-500/20 px-2 py-0.5 text-xs font-medium text-green-400">
Active
</span>
)}
</td>
<td className="hidden px-4 py-3 text-xs text-text-muted md:table-cell">
{new Date(user.createdAt).toLocaleDateString()}
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => void handleRoleToggle(user)}
className="text-xs text-blue-400 hover:text-blue-300"
title={user.role === 'admin' ? 'Demote to member' : 'Promote to admin'}
>
{user.role === 'admin' ? 'Demote' : 'Promote'}
</button>
<button
type="button"
onClick={() => void handleBanToggle(user)}
className={cn(
'text-xs',
user.banned
? 'text-green-400 hover:text-green-300'
: 'text-yellow-400 hover:text-yellow-300',
)}
>
{user.banned ? 'Unban' : 'Ban'}
</button>
<button
type="button"
onClick={() => void handleDelete(user)}
className="text-xs text-red-400 hover:text-red-300"
>
Delete
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
// ── Create User Form ──────────────────────────────────────────────────────────
interface CreateUserFormProps {
onCancel: () => void;
onCreated: () => void;
}
function CreateUserForm({ onCancel, onCreated }: CreateUserFormProps): React.ReactElement {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [role, setRole] = useState('member');
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: React.FormEvent): Promise<void> {
e.preventDefault();
setSubmitting(true);
setError(null);
try {
await api('/api/admin/users', {
method: 'POST',
body: { name, email, password, role },
});
onCreated();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create user');
} finally {
setSubmitting(false);
}
}
return (
<div className="rounded-lg border border-surface-border bg-surface-card p-4">
<h3 className="mb-3 text-sm font-medium text-text-primary">Create New User</h3>
<form onSubmit={(e) => void handleSubmit(e)} className="space-y-3">
{error && <p className="text-xs text-red-400">{error}</p>}
<div className="grid grid-cols-2 gap-3">
<div>
<label className="mb-1 block text-xs text-text-muted">Name</label>
<input
type="text"
required
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full rounded-md border border-surface-border bg-surface-elevated px-3 py-1.5 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label className="mb-1 block text-xs text-text-muted">Email</label>
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-md border border-surface-border bg-surface-elevated px-3 py-1.5 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label className="mb-1 block text-xs text-text-muted">Password</label>
<input
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-md border border-surface-border bg-surface-elevated px-3 py-1.5 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label className="mb-1 block text-xs text-text-muted">Role</label>
<select
value={role}
onChange={(e) => setRole(e.target.value)}
className="w-full rounded-md border border-surface-border bg-surface-elevated px-3 py-1.5 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-blue-500"
>
<option value="member">member</option>
<option value="admin">admin</option>
</select>
</div>
</div>
<div className="flex justify-end gap-2">
<button
type="button"
onClick={onCancel}
className="rounded-md px-3 py-1.5 text-sm text-text-muted hover:text-text-primary"
>
Cancel
</button>
<button
type="submit"
disabled={submitting}
className="rounded-md bg-blue-600 px-3 py-1.5 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
>
{submitting ? 'Creating...' : 'Create'}
</button>
</div>
</form>
</div>
);
}
// ── Health Tab ────────────────────────────────────────────────────────────────
function HealthTab(): React.ReactElement {
const [health, setHealth] = useState<HealthStatusDto | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadHealth = useCallback(async () => {
setLoading(true);
setError(null);
try {
const data = await api<HealthStatusDto>('/api/admin/health');
setHealth(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load health');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void loadHealth();
}, [loadHealth]);
if (loading) {
return <p className="text-sm text-text-muted">Loading health status...</p>;
}
if (error) {
return (
<div className="rounded-lg border border-red-500/30 bg-red-500/10 p-4">
<p className="text-sm text-red-400">{error}</p>
<button
type="button"
onClick={() => void loadHealth()}
className="mt-2 text-xs text-red-300 underline hover:no-underline"
>
Retry
</button>
</div>
);
}
if (!health) return <></>;
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<StatusBadge status={health.status} />
<span className="text-sm text-text-muted">
Last checked: {new Date(health.checkedAt).toLocaleTimeString()}
</span>
</div>
<button
type="button"
onClick={() => void loadHealth()}
className="text-xs text-blue-400 hover:text-blue-300"
>
Refresh
</button>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{/* Database */}
<HealthCard title="Database (PostgreSQL)" status={health.database.status}>
{health.database.latencyMs !== undefined && (
<p className="text-xs text-text-muted">Latency: {health.database.latencyMs}ms</p>
)}
{health.database.error && <p className="text-xs text-red-400">{health.database.error}</p>}
</HealthCard>
{/* Cache */}
<HealthCard title="Cache (Valkey)" status={health.cache.status}>
{health.cache.latencyMs !== undefined && (
<p className="text-xs text-text-muted">Latency: {health.cache.latencyMs}ms</p>
)}
{health.cache.error && <p className="text-xs text-red-400">{health.cache.error}</p>}
</HealthCard>
{/* Agent Pool */}
<HealthCard title="Agent Pool" status="ok">
<p className="text-xs text-text-muted">
Active sessions: {health.agentPool.activeSessions}
</p>
</HealthCard>
{/* Providers */}
<HealthCard
title="LLM Providers"
status={health.providers.some((p) => p.available) ? 'ok' : 'error'}
>
{health.providers.length === 0 ? (
<p className="text-xs text-text-muted">No providers configured</p>
) : (
<ul className="space-y-1">
{health.providers.map((p) => (
<li key={p.id} className="flex items-center justify-between text-xs">
<span className="text-text-secondary">{p.name}</span>
<span
className={cn(
'rounded-full px-1.5 py-0.5',
p.available ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400',
)}
>
{p.available ? `${p.modelCount} models` : 'unavailable'}
</span>
</li>
))}
</ul>
)}
</HealthCard>
</div>
</div>
);
}
// ── Helper Components ─────────────────────────────────────────────────────────
function StatusBadge({ status }: { status: 'ok' | 'degraded' | 'error' }): React.ReactElement {
const map = {
ok: 'bg-green-500/20 text-green-400',
degraded: 'bg-yellow-500/20 text-yellow-400',
error: 'bg-red-500/20 text-red-400',
};
return (
<span className={cn('rounded-full px-2 py-0.5 text-xs font-medium capitalize', map[status])}>
{status}
</span>
);
}
interface HealthCardProps {
title: string;
status: 'ok' | 'error';
children?: React.ReactNode;
}
function HealthCard({ title, status, children }: HealthCardProps): React.ReactElement {
return (
<div className="rounded-lg border border-surface-border bg-surface-card p-4">
<div className="mb-2 flex items-center justify-between">
<h3 className="text-sm font-medium text-text-primary">{title}</h3>
<span
className={cn('h-2 w-2 rounded-full', status === 'ok' ? 'bg-green-400' : 'bg-red-400')}
/>
</div>
{children}
</div>
);
}
-365
View File
@@ -1,365 +0,0 @@
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
import { api } from '@/lib/api';
import { destroySocket, getSocket } from '@/lib/socket';
import type { Conversation, Message } from '@/lib/types';
import {
ConversationSidebar,
type ConversationSidebarRef,
} from '@/components/chat/conversation-sidebar';
import { MessageBubble } from '@/components/chat/message-bubble';
import { ChatInput } from '@/components/chat/chat-input';
import { StreamingMessage } from '@/components/chat/streaming-message';
interface ModelInfo {
id: string;
provider: string;
name: string;
reasoning: boolean;
contextWindow: number;
maxTokens: number;
inputTypes: ('text' | 'image')[];
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
}
interface ProviderInfo {
id: string;
name: string;
available: boolean;
models: ModelInfo[];
}
export default function ChatPage(): React.ReactElement {
const [activeId, setActiveId] = useState<string | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
const [streamingText, setStreamingText] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
const [models, setModels] = useState<ModelInfo[]>([]);
const [selectedModelId, setSelectedModelId] = useState('');
const messagesEndRef = useRef<HTMLDivElement>(null);
const sidebarRef = useRef<ConversationSidebarRef>(null);
// Track the active conversation ID in a ref so socket event handlers always
// see the current value without needing to be re-registered.
const activeIdRef = useRef<string | null>(null);
activeIdRef.current = activeId;
// Accumulate streamed text in a ref so agent:end can read the full content
// without stale-closure issues.
const streamingTextRef = useRef('');
useEffect(() => {
const savedState = window.localStorage.getItem('mosaic-sidebar-open');
if (savedState !== null) {
setIsSidebarOpen(savedState === 'true');
}
}, []);
useEffect(() => {
window.localStorage.setItem('mosaic-sidebar-open', String(isSidebarOpen));
}, [isSidebarOpen]);
useEffect(() => {
api<ProviderInfo[]>('/api/providers')
.then((providers) => {
const availableModels = providers
.filter((provider) => provider.available)
.flatMap((provider) => provider.models);
setModels(availableModels);
setSelectedModelId((current) => current || availableModels[0]?.id || '');
})
.catch(() => {
setModels([]);
setSelectedModelId('');
});
}, []);
// Load messages when active conversation changes
useEffect(() => {
if (!activeId) {
setMessages([]);
return;
}
// Clear streaming state when switching conversations
setIsStreaming(false);
setStreamingText('');
streamingTextRef.current = '';
api<Message[]>(`/api/conversations/${activeId}/messages`)
.then(setMessages)
.catch(() => {});
}, [activeId]);
// Auto-scroll to bottom
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages, streamingText]);
// Socket.io setup — connect once for the page lifetime
useEffect(() => {
const socket = getSocket();
function onAgentStart(data: { conversationId: string }): void {
// Only update state if the event belongs to the currently viewed conversation
if (activeIdRef.current !== data.conversationId) return;
setIsStreaming(true);
setStreamingText('');
streamingTextRef.current = '';
}
function onAgentText(data: { conversationId: string; text: string }): void {
if (activeIdRef.current !== data.conversationId) return;
streamingTextRef.current += data.text;
setStreamingText((prev) => prev + data.text);
}
function onAgentEnd(data: { conversationId: string }): void {
if (activeIdRef.current !== data.conversationId) return;
const finalText = streamingTextRef.current;
setIsStreaming(false);
setStreamingText('');
streamingTextRef.current = '';
// Append the completed assistant message to the local message list.
// The Pi agent session is in-memory so the assistant response is not
// persisted to the DB — we build the local UI state instead.
if (finalText) {
setMessages((prev) => [
...prev,
{
id: `assistant-${Date.now()}`,
conversationId: data.conversationId,
role: 'assistant' as const,
content: finalText,
createdAt: new Date().toISOString(),
},
]);
sidebarRef.current?.refresh();
}
}
function onError(data: { error: string; conversationId?: string }): void {
setIsStreaming(false);
setStreamingText('');
streamingTextRef.current = '';
setMessages((prev) => [
...prev,
{
id: `error-${Date.now()}`,
conversationId: data.conversationId ?? '',
role: 'system' as const,
content: `Error: ${data.error}`,
createdAt: new Date().toISOString(),
},
]);
}
socket.on('agent:start', onAgentStart);
socket.on('agent:text', onAgentText);
socket.on('agent:end', onAgentEnd);
socket.on('error', onError);
// Connect if not already connected
if (!socket.connected) {
socket.connect();
}
return () => {
socket.off('agent:start', onAgentStart);
socket.off('agent:text', onAgentText);
socket.off('agent:end', onAgentEnd);
socket.off('error', onError);
// Fully tear down the socket when the chat page unmounts so we get a
// fresh authenticated connection next time the page is visited.
destroySocket();
};
}, []);
const handleNewConversation = useCallback(async (projectId?: string | null) => {
const conv = await api<Conversation>('/api/conversations', {
method: 'POST',
body: { title: 'New conversation', projectId: projectId ?? null },
});
sidebarRef.current?.addConversation({
id: conv.id,
title: conv.title,
projectId: conv.projectId,
updatedAt: conv.updatedAt,
archived: conv.archived,
});
setActiveId(conv.id);
setMessages([]);
setIsSidebarOpen(true);
}, []);
const handleSend = useCallback(
async (content: string, options?: { modelId?: string }) => {
let convId = activeId;
// Auto-create conversation if none selected
if (!convId) {
const autoTitle = content.slice(0, 60);
const conv = await api<Conversation>('/api/conversations', {
method: 'POST',
body: { title: autoTitle },
});
sidebarRef.current?.addConversation({
id: conv.id,
title: conv.title,
projectId: conv.projectId,
updatedAt: conv.updatedAt,
archived: conv.archived,
});
setActiveId(conv.id);
convId = conv.id;
} else if (messages.length === 0) {
// Auto-title the initial placeholder conversation from the first user message.
const autoTitle = content.slice(0, 60);
api<Conversation>(`/api/conversations/${convId}`, {
method: 'PATCH',
body: { title: autoTitle },
})
.then(() => sidebarRef.current?.refresh())
.catch(() => {});
}
// Optimistic user message in local UI state
setMessages((prev) => [
...prev,
{
id: `user-${Date.now()}`,
conversationId: convId,
role: 'user' as const,
content,
createdAt: new Date().toISOString(),
},
]);
// Persist the user message to the DB so conversation history is
// available when the page is reloaded or a new session starts.
api<Message>(`/api/conversations/${convId}/messages`, {
method: 'POST',
body: { role: 'user', content },
}).catch(() => {
// Non-fatal: the agent can still process the message even if
// REST persistence fails.
});
// Send to WebSocket — gateway creates/resumes the agent session and
// streams the response back via agent:start / agent:text / agent:end.
const socket = getSocket();
if (!socket.connected) {
socket.connect();
}
socket.emit('message', {
conversationId: convId,
content,
modelId: (options?.modelId ?? selectedModelId) || undefined,
});
},
[activeId, messages, selectedModelId],
);
return (
<div
className="-m-6 flex h-[calc(100vh-3.5rem)] overflow-hidden"
style={{ background: 'var(--bg-deep, var(--color-surface-bg, #0a0f1a))' }}
>
<ConversationSidebar
ref={sidebarRef}
isOpen={isSidebarOpen}
onClose={() => setIsSidebarOpen(false)}
currentConversationId={activeId}
onSelectConversation={(conversationId) => {
setActiveId(conversationId);
setMessages([]);
if (conversationId && window.innerWidth < 768) {
setIsSidebarOpen(false);
}
}}
onNewConversation={(projectId) => {
void handleNewConversation(projectId);
}}
/>
<div className="flex min-w-0 flex-1 flex-col">
<div
className="flex items-center gap-3 border-b px-4 py-3"
style={{ borderColor: 'var(--border)' }}
>
<button
type="button"
onClick={() => setIsSidebarOpen((open) => !open)}
className="rounded-lg border p-2 transition-colors"
style={{
borderColor: 'var(--border)',
background: 'var(--surface)',
color: 'var(--text)',
}}
aria-label={isSidebarOpen ? 'Close conversation sidebar' : 'Open conversation sidebar'}
>
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor">
<path strokeWidth="2" strokeLinecap="round" d="M4 7h16M4 12h16M4 17h16" />
</svg>
</button>
<div>
<h1 className="text-sm font-semibold" style={{ color: 'var(--text)' }}>
Mosaic Chat
</h1>
<p className="text-xs" style={{ color: 'var(--muted)' }}>
{activeId ? 'Active conversation selected' : 'Choose or start a conversation'}
</p>
</div>
</div>
{activeId ? (
<>
<div className="flex-1 space-y-4 overflow-y-auto p-6">
{messages.map((msg) => (
<MessageBubble key={msg.id} message={msg} />
))}
{isStreaming && <StreamingMessage text={streamingText} />}
<div ref={messagesEndRef} />
</div>
<ChatInput
onSend={handleSend}
isStreaming={isStreaming}
models={models}
selectedModelId={selectedModelId}
onModelChange={setSelectedModelId}
/>
</>
) : (
<div className="flex flex-1 items-center justify-center px-6">
<div
className="max-w-md rounded-2xl border px-8 py-10 text-center"
style={{
borderColor: 'var(--border)',
background: 'var(--surface)',
}}
>
<h2 className="text-lg font-medium" style={{ color: 'var(--text)' }}>
Welcome to Mosaic Chat
</h2>
<p className="mt-1 text-sm" style={{ color: 'var(--muted)' }}>
Select a conversation or start a new one
</p>
<button
type="button"
onClick={() => {
void handleNewConversation();
}}
className="mt-4 rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors"
style={{ background: 'var(--primary)' }}
>
Start new conversation
</button>
</div>
</div>
)}
</div>
</div>
);
}
-11
View File
@@ -1,11 +0,0 @@
import type { ReactNode } from 'react';
import { AppShell } from '@/components/layout/app-shell';
import { AuthGuard } from '@/components/auth-guard';
export default function DashboardLayout({ children }: { children: ReactNode }): React.ReactElement {
return (
<AuthGuard>
<AppShell>{children}</AppShell>
</AuthGuard>
);
}
@@ -1,338 +0,0 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { api } from '@/lib/api';
import { cn } from '@/lib/cn';
import type { Mission, Project, Task, TaskStatus } from '@/lib/types';
import { MissionTimeline } from '@/components/projects/mission-timeline';
import { PrdViewer } from '@/components/projects/prd-viewer';
import { TaskDetailModal } from '@/components/tasks/task-detail-modal';
import { TaskListView } from '@/components/tasks/task-list-view';
import { TaskStatusSummary } from '@/components/tasks/task-status-summary';
type Tab = 'overview' | 'tasks' | 'missions' | 'prd';
const statusColors: Record<string, string> = {
active: 'bg-success/20 text-success',
paused: 'bg-warning/20 text-warning',
completed: 'bg-blue-600/20 text-blue-400',
archived: 'bg-gray-600/20 text-gray-400',
};
interface TabButtonProps {
id: Tab;
label: string;
activeTab: Tab;
onClick: (tab: Tab) => void;
}
function TabButton({ id, label, activeTab, onClick }: TabButtonProps): React.ReactElement {
return (
<button
type="button"
onClick={() => onClick(id)}
className={cn(
'border-b-2 px-4 py-2 text-sm transition-colors',
activeTab === id
? 'border-text-primary text-text-primary'
: 'border-transparent text-text-muted hover:text-text-secondary',
)}
>
{label}
</button>
);
}
export default function ProjectDetailPage(): React.ReactElement {
const params = useParams();
const router = useRouter();
const id = typeof params['id'] === 'string' ? params['id'] : '';
const [project, setProject] = useState<Project | null>(null);
const [missions, setMissions] = useState<Mission[]>([]);
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<Tab>('overview');
const [taskFilter, setTaskFilter] = useState<TaskStatus | 'all'>('all');
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
useEffect(() => {
if (!id) return;
setLoading(true);
setError(null);
Promise.all([
api<Project>(`/api/projects/${id}`),
api<Mission[]>('/api/missions').catch(() => [] as Mission[]),
api<Task[]>(`/api/tasks?projectId=${id}`).catch(() => [] as Task[]),
])
.then(([proj, allMissions, tks]) => {
setProject(proj);
setMissions(allMissions.filter((m) => m.projectId === id));
setTasks(tks);
})
.catch((err: Error) => {
setError(err.message ?? 'Failed to load project');
})
.finally(() => setLoading(false));
}, [id]);
const handleTaskClick = useCallback((task: Task) => {
setSelectedTask(task);
}, []);
const handleCloseTaskModal = useCallback(() => {
setSelectedTask(null);
}, []);
if (loading) {
return (
<div className="py-16 text-center">
<p className="text-sm text-text-muted">Loading project...</p>
</div>
);
}
if (error || !project) {
return (
<div className="py-16 text-center">
<p className="text-sm text-error">{error ?? 'Project not found'}</p>
<button
type="button"
onClick={() => router.push('/projects')}
className="mt-4 text-sm text-text-muted underline hover:text-text-secondary"
>
Back to projects
</button>
</div>
);
}
const filteredTasks = taskFilter === 'all' ? tasks : tasks.filter((t) => t.status === taskFilter);
const prdContent = getPrdContent(project);
const hasPrd = Boolean(prdContent);
const tabs: { id: Tab; label: string }[] = [
{ id: 'overview', label: 'Overview' },
{ id: 'tasks', label: `Tasks (${tasks.length})` },
{ id: 'missions', label: `Missions (${missions.length})` },
...(hasPrd ? [{ id: 'prd' as Tab, label: 'PRD' }] : []),
];
return (
<div>
{/* Breadcrumb */}
<nav className="mb-4 flex items-center gap-2 text-sm text-text-muted">
<button
type="button"
onClick={() => router.push('/projects')}
className="hover:text-text-secondary"
>
Projects
</button>
<span>/</span>
<span className="text-text-primary">{project.name}</span>
</nav>
{/* Project header */}
<div className="mb-6 flex items-start justify-between gap-4">
<div>
<div className="flex items-center gap-3">
<h1 className="text-2xl font-semibold text-text-primary">{project.name}</h1>
<span
className={cn(
'rounded-full px-2 py-0.5 text-xs',
statusColors[project.status] ?? 'bg-gray-600/20 text-gray-400',
)}
>
{project.status}
</span>
</div>
{project.description && (
<p className="mt-1 text-sm text-text-muted">{project.description}</p>
)}
<p className="mt-2 text-xs text-text-muted">
Created {new Date(project.createdAt).toLocaleDateString()} · Updated{' '}
{new Date(project.updatedAt).toLocaleDateString()}
</p>
</div>
</div>
{/* Stats bar */}
<div className="mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4">
<StatCard label="Tasks" value={String(tasks.length)} />
<StatCard
label="Done"
value={String(tasks.filter((t) => t.status === 'done').length)}
valueClass="text-success"
/>
<StatCard
label="In Progress"
value={String(tasks.filter((t) => t.status === 'in-progress').length)}
valueClass="text-blue-400"
/>
<StatCard
label="Blocked"
value={String(tasks.filter((t) => t.status === 'blocked').length)}
valueClass={tasks.some((t) => t.status === 'blocked') ? 'text-error' : undefined}
/>
</div>
{/* Tabs */}
<div className="mb-6 flex gap-0 border-b border-surface-border">
{tabs.map((tab) => (
<TabButton
key={tab.id}
id={tab.id}
label={tab.label}
activeTab={activeTab}
onClick={setActiveTab}
/>
))}
</div>
{/* Tab content */}
{activeTab === 'overview' && (
<OverviewTab project={project} missions={missions} tasks={tasks} />
)}
{activeTab === 'tasks' && (
<div>
<div className="mb-4">
<TaskStatusSummary
tasks={tasks}
activeFilter={taskFilter}
onFilterChange={setTaskFilter}
/>
</div>
<TaskListView tasks={filteredTasks} onTaskClick={handleTaskClick} />
</div>
)}
{activeTab === 'missions' && <MissionTimeline missions={missions} />}
{activeTab === 'prd' && prdContent && (
<div className="rounded-lg border border-surface-border bg-surface-card p-6">
<PrdViewer content={prdContent} />
</div>
)}
{/* Task detail modal */}
{selectedTask && <TaskDetailModal task={selectedTask} onClose={handleCloseTaskModal} />}
</div>
);
}
interface OverviewTabProps {
project: Project;
missions: Mission[];
tasks: Task[];
}
function OverviewTab({ project, missions, tasks }: OverviewTabProps): React.ReactElement {
const recentTasks = [...tasks]
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
.slice(0, 5);
return (
<div className="grid gap-6 lg:grid-cols-2">
{/* Recent tasks */}
<section>
<h2 className="mb-3 text-sm font-semibold text-text-secondary">Recent Tasks</h2>
{recentTasks.length === 0 ? (
<div className="rounded-lg border border-surface-border bg-surface-card p-4 text-center">
<p className="text-sm text-text-muted">No tasks yet</p>
</div>
) : (
<div className="space-y-2">
{recentTasks.map((task) => (
<TaskSummaryRow key={task.id} task={task} />
))}
</div>
)}
</section>
{/* Mission summary */}
<section>
<h2 className="mb-3 text-sm font-semibold text-text-secondary">Missions</h2>
{missions.length === 0 ? (
<div className="rounded-lg border border-surface-border bg-surface-card p-4 text-center">
<p className="text-sm text-text-muted">No missions yet</p>
</div>
) : (
<MissionTimeline missions={missions.slice(0, 4)} />
)}
</section>
{/* Metadata */}
{project.metadata && Object.keys(project.metadata).length > 0 && (
<section className="lg:col-span-2">
<h2 className="mb-3 text-sm font-semibold text-text-secondary">Project Metadata</h2>
<div className="rounded-lg border border-surface-border bg-surface-card p-4">
<pre className="overflow-x-auto text-xs text-text-muted">
{JSON.stringify(project.metadata, null, 2)}
</pre>
</div>
</section>
)}
</div>
);
}
const taskStatusColors: Record<string, string> = {
'not-started': 'bg-gray-600/20 text-gray-300',
'in-progress': 'bg-blue-600/20 text-blue-400',
blocked: 'bg-error/20 text-error',
done: 'bg-success/20 text-success',
cancelled: 'bg-gray-600/20 text-gray-500',
};
function TaskSummaryRow({ task }: { task: Task }): React.ReactElement {
return (
<div className="flex items-center justify-between gap-2 rounded-lg border border-surface-border bg-surface-card px-3 py-2">
<span className="truncate text-sm text-text-primary">{task.title}</span>
<span
className={cn(
'shrink-0 rounded-full px-2 py-0.5 text-xs',
taskStatusColors[task.status] ?? 'bg-gray-600/20 text-gray-400',
)}
>
{task.status}
</span>
</div>
);
}
function StatCard({
label,
value,
valueClass,
}: {
label: string;
value: string;
valueClass?: string;
}): React.ReactElement {
return (
<div className="rounded-lg border border-surface-border bg-surface-card p-3">
<p className="text-xs text-text-muted">{label}</p>
<p className={cn('mt-1 text-lg font-semibold', valueClass ?? 'text-text-primary')}>{value}</p>
</div>
);
}
function getPrdContent(project: Project): string | null {
if (!project.metadata) return null;
const prd = project.metadata['prd'];
if (typeof prd === 'string' && prd.trim().length > 0) return prd;
const prdContent = project.metadata['prdContent'];
if (typeof prdContent === 'string' && prdContent.trim().length > 0) return prdContent;
return null;
}
@@ -1,101 +0,0 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { api } from '@/lib/api';
import type { Project } from '@/lib/types';
import { ProjectCard } from '@/components/projects/project-card';
export default function ProjectsPage(): React.ReactElement {
const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(true);
const router = useRouter();
useEffect(() => {
api<Project[]>('/api/projects')
.then(setProjects)
.catch(() => {})
.finally(() => setLoading(false));
}, []);
const handleProjectClick = useCallback(
(project: Project) => {
router.push(`/projects/${project.id}`);
},
[router],
);
return (
<div>
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-semibold">Projects</h1>
</div>
{loading ? (
<p className="py-8 text-center text-sm text-text-muted">Loading projects...</p>
) : projects.length === 0 ? (
<div className="py-12 text-center">
<h2 className="text-lg font-medium text-text-secondary">No projects yet</h2>
<p className="mt-1 text-sm text-text-muted">
Projects will appear here when created via the gateway API
</p>
</div>
) : (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{projects.map((project) => (
<ProjectCard key={project.id} project={project} onClick={handleProjectClick} />
))}
</div>
)}
{/* Mission status section */}
<MissionStatus />
</div>
);
}
function MissionStatus(): React.ReactElement {
const [mission, setMission] = useState<Record<string, unknown> | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
api<Record<string, unknown>>('/api/coord/status')
.then(setMission)
.catch(() => setMission(null))
.finally(() => setLoading(false));
}, []);
return (
<section className="mt-8">
<h2 className="mb-4 text-lg font-semibold">Active Mission</h2>
{loading ? (
<p className="text-sm text-text-muted">Loading mission status...</p>
) : !mission ? (
<div className="rounded-lg border border-surface-border bg-surface-card p-6 text-center">
<p className="text-sm text-text-muted">No active mission detected</p>
</div>
) : (
<div className="rounded-lg border border-surface-border bg-surface-card p-4">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<StatCard label="Mission" value={String(mission['missionId'] ?? 'Unknown')} />
<StatCard label="Phase" value={String(mission['currentPhase'] ?? '—')} />
<StatCard
label="Tasks"
value={`${mission['completedTasks'] ?? 0} / ${mission['totalTasks'] ?? 0}`}
/>
<StatCard label="Status" value={String(mission['status'] ?? '—')} />
</div>
</div>
)}
</section>
);
}
function StatCard({ label, value }: { label: string; value: string }): React.ReactElement {
return (
<div className="rounded-lg bg-surface-elevated p-3">
<p className="text-xs text-text-muted">{label}</p>
<p className="mt-1 text-sm font-medium text-text-primary">{value}</p>
</div>
);
}
@@ -1,828 +0,0 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { api } from '@/lib/api';
import { authClient, useSession } from '@/lib/auth-client';
import type { SsoProviderDiscovery } from '@/lib/sso';
import { SsoProviderSection } from '@/components/settings/sso-provider-section';
// ─── Types ────────────────────────────────────────────────────────────────────
interface ModelInfo {
id: string;
provider: string;
name: string;
reasoning: boolean;
contextWindow: number;
maxTokens: number;
inputTypes: ('text' | 'image')[];
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
}
interface ProviderInfo {
id: string;
name: string;
available: boolean;
models: ModelInfo[];
}
interface TestConnectionResult {
providerId: string;
reachable: boolean;
latencyMs?: number;
error?: string;
discoveredModels?: string[];
}
type TestState = 'idle' | 'testing' | 'success' | 'error';
interface ProviderTestStatus {
state: TestState;
result?: TestConnectionResult;
}
interface Preference {
key: string;
value: unknown;
category: string;
}
type Theme = 'light' | 'dark' | 'system';
type SaveState = 'idle' | 'saving' | 'saved' | 'error';
type Tab = 'profile' | 'appearance' | 'notifications' | 'providers';
// ─── Helpers ──────────────────────────────────────────────────────────────────
function prefValue<T>(prefs: Preference[], key: string, fallback: T): T {
const p = prefs.find((x) => x.key === key);
if (p === undefined) return fallback;
return p.value as T;
}
// ─── Main Page ────────────────────────────────────────────────────────────────
export default function SettingsPage(): React.ReactElement {
const { data: session } = useSession();
const [activeTab, setActiveTab] = useState<Tab>('profile');
const tabs: { id: Tab; label: string }[] = [
{ id: 'profile', label: 'Profile' },
{ id: 'appearance', label: 'Appearance' },
{ id: 'notifications', label: 'Notifications' },
{ id: 'providers', label: 'Providers' },
];
return (
<div className="mx-auto max-w-3xl space-y-6">
<h1 className="text-2xl font-semibold">Settings</h1>
{/* Tab bar */}
<div className="flex gap-1 border-b border-surface-border">
{tabs.map((tab) => (
<button
key={tab.id}
type="button"
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 text-sm font-medium transition-colors ${
activeTab === tab.id
? 'border-b-2 border-accent text-accent'
: 'text-text-secondary hover:text-text-primary'
}`}
>
{tab.label}
</button>
))}
</div>
{activeTab === 'profile' && <ProfileTab session={session} />}
{activeTab === 'appearance' && <AppearanceTab />}
{activeTab === 'notifications' && <NotificationsTab />}
{activeTab === 'providers' && <ProvidersTab />}
</div>
);
}
// ─── Profile Tab ──────────────────────────────────────────────────────────────
function ProfileTab({
session,
}: {
session: { user: { id: string; name: string; email: string; image?: string | null } } | null;
}): React.ReactElement {
const [name, setName] = useState(session?.user.name ?? '');
const [image, setImage] = useState(session?.user.image ?? '');
const [saveState, setSaveState] = useState<SaveState>('idle');
const [errorMsg, setErrorMsg] = useState('');
// Sync from session when it loads
useEffect(() => {
if (session?.user) {
setName(session.user.name ?? '');
setImage(session.user.image ?? '');
}
}, [session]);
const handleSave = async (): Promise<void> => {
setSaveState('saving');
setErrorMsg('');
try {
const result = await authClient.updateUser({ name, image: image || null });
if (result.error) {
setErrorMsg(result.error.message ?? 'Failed to update profile');
setSaveState('error');
return;
}
setSaveState('saved');
setTimeout(() => setSaveState('idle'), 2000);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Failed to update profile';
setErrorMsg(message);
setSaveState('error');
}
};
return (
<section className="space-y-4">
<h2 className="text-lg font-medium text-text-secondary">Profile</h2>
<div className="rounded-lg border border-surface-border bg-surface-card p-6 space-y-4">
<FormField label="Display Name" id="profile-name">
<input
id="profile-name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Your name"
className="mt-1 block w-full rounded-lg border border-surface-border bg-surface-elevated px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-accent focus:outline-none focus:ring-1 focus:ring-accent"
/>
</FormField>
<FormField label="Email" id="profile-email">
<input
id="profile-email"
type="email"
value={session?.user.email ?? ''}
disabled
className="mt-1 block w-full rounded-lg border border-surface-border bg-surface-elevated px-3 py-2 text-sm text-text-muted opacity-60 cursor-not-allowed"
/>
<p className="mt-1 text-xs text-text-muted">Email cannot be changed here.</p>
</FormField>
<FormField label="Avatar URL" id="profile-image">
<input
id="profile-image"
type="url"
value={image}
onChange={(e) => setImage(e.target.value)}
placeholder="https://example.com/avatar.png"
className="mt-1 block w-full rounded-lg border border-surface-border bg-surface-elevated px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-accent focus:outline-none focus:ring-1 focus:ring-accent"
/>
</FormField>
<div className="flex items-center gap-3 pt-2">
<SaveButton state={saveState} onClick={handleSave} />
{saveState === 'error' && errorMsg && <p className="text-sm text-error">{errorMsg}</p>}
</div>
</div>
</section>
);
}
// ─── Appearance Tab ───────────────────────────────────────────────────────────
function AppearanceTab(): React.ReactElement {
const [loading, setLoading] = useState(true);
const [theme, setTheme] = useState<Theme>('system');
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [defaultModel, setDefaultModel] = useState('');
const [saveState, setSaveState] = useState<SaveState>('idle');
const [errorMsg, setErrorMsg] = useState('');
useEffect(() => {
api<Preference[]>('/api/memory/preferences?category=appearance')
.catch(() => [] as Preference[])
.then((p) => {
setTheme(prefValue<Theme>(p, 'ui.theme', 'system'));
setSidebarCollapsed(prefValue<boolean>(p, 'ui.sidebar_collapsed', false));
setDefaultModel(prefValue<string>(p, 'ui.default_model', ''));
})
.finally(() => setLoading(false));
}, []);
const handleSave = async (): Promise<void> => {
setSaveState('saving');
setErrorMsg('');
try {
await Promise.all([
api('/api/memory/preferences', {
method: 'POST',
body: { key: 'ui.theme', value: theme, category: 'appearance', source: 'user' },
}),
api('/api/memory/preferences', {
method: 'POST',
body: {
key: 'ui.sidebar_collapsed',
value: sidebarCollapsed,
category: 'appearance',
source: 'user',
},
}),
...(defaultModel
? [
api('/api/memory/preferences', {
method: 'POST',
body: {
key: 'ui.default_model',
value: defaultModel,
category: 'appearance',
source: 'user',
},
}),
]
: []),
]);
setSaveState('saved');
setTimeout(() => setSaveState('idle'), 2000);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Failed to save preferences';
setErrorMsg(message);
setSaveState('error');
}
};
if (loading) {
return (
<section>
<h2 className="mb-4 text-lg font-medium text-text-secondary">Appearance</h2>
<p className="text-sm text-text-muted">Loading preferences...</p>
</section>
);
}
return (
<section className="space-y-4">
<h2 className="text-lg font-medium text-text-secondary">Appearance</h2>
<div className="rounded-lg border border-surface-border bg-surface-card p-6 space-y-6">
{/* Theme */}
<div>
<label className="block text-sm font-medium text-text-primary mb-2">Theme</label>
<div className="flex gap-3">
{(['system', 'light', 'dark'] as Theme[]).map((t) => (
<button
key={t}
type="button"
onClick={() => setTheme(t)}
className={`rounded-lg border px-4 py-2 text-sm capitalize transition-colors ${
theme === t
? 'border-accent bg-accent/10 text-accent'
: 'border-surface-border bg-surface-elevated text-text-secondary hover:border-accent/50'
}`}
>
{t}
</button>
))}
</div>
</div>
{/* Sidebar collapsed default */}
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-text-primary">Collapse sidebar by default</p>
<p className="text-xs text-text-muted">Start with sidebar collapsed on page load</p>
</div>
<Toggle checked={sidebarCollapsed} onChange={setSidebarCollapsed} />
</div>
{/* Default model */}
<FormField label="Default Model" id="default-model">
<input
id="default-model"
type="text"
value={defaultModel}
onChange={(e) => setDefaultModel(e.target.value)}
placeholder="e.g. ollama/llama3.2"
className="mt-1 block w-full rounded-lg border border-surface-border bg-surface-elevated px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-accent focus:outline-none focus:ring-1 focus:ring-accent"
/>
<p className="mt-1 text-xs text-text-muted">
Model ID to pre-select for new conversations.
</p>
</FormField>
<div className="flex items-center gap-3 pt-2">
<SaveButton state={saveState} onClick={handleSave} />
{saveState === 'error' && errorMsg && <p className="text-sm text-error">{errorMsg}</p>}
</div>
</div>
</section>
);
}
// ─── Notifications Tab ────────────────────────────────────────────────────────
function NotificationsTab(): React.ReactElement {
const [loading, setLoading] = useState(true);
const [emailAgentComplete, setEmailAgentComplete] = useState(false);
const [emailMentions, setEmailMentions] = useState(true);
const [emailDigest, setEmailDigest] = useState(false);
const [saveState, setSaveState] = useState<SaveState>('idle');
const [errorMsg, setErrorMsg] = useState('');
useEffect(() => {
api<Preference[]>('/api/memory/preferences?category=communication')
.catch(() => [] as Preference[])
.then((p) => {
setEmailAgentComplete(prefValue<boolean>(p, 'notify.email_agent_complete', false));
setEmailMentions(prefValue<boolean>(p, 'notify.email_mentions', true));
setEmailDigest(prefValue<boolean>(p, 'notify.email_digest', false));
})
.finally(() => setLoading(false));
}, []);
const handleSave = async (): Promise<void> => {
setSaveState('saving');
setErrorMsg('');
try {
await Promise.all([
api('/api/memory/preferences', {
method: 'POST',
body: {
key: 'notify.email_agent_complete',
value: emailAgentComplete,
category: 'communication',
source: 'user',
},
}),
api('/api/memory/preferences', {
method: 'POST',
body: {
key: 'notify.email_mentions',
value: emailMentions,
category: 'communication',
source: 'user',
},
}),
api('/api/memory/preferences', {
method: 'POST',
body: {
key: 'notify.email_digest',
value: emailDigest,
category: 'communication',
source: 'user',
},
}),
]);
setSaveState('saved');
setTimeout(() => setSaveState('idle'), 2000);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Failed to save preferences';
setErrorMsg(message);
setSaveState('error');
}
};
if (loading) {
return (
<section>
<h2 className="mb-4 text-lg font-medium text-text-secondary">Notifications</h2>
<p className="text-sm text-text-muted">Loading preferences...</p>
</section>
);
}
return (
<section className="space-y-4">
<h2 className="text-lg font-medium text-text-secondary">Notifications</h2>
<div className="rounded-lg border border-surface-border bg-surface-card p-6 space-y-6">
<p className="text-xs text-text-muted">Configure when you receive email notifications.</p>
<NotifyRow
label="Agent task completed"
description="Email when an agent finishes a task"
checked={emailAgentComplete}
onChange={setEmailAgentComplete}
/>
<NotifyRow
label="Mentions"
description="Email when you are mentioned in a conversation"
checked={emailMentions}
onChange={setEmailMentions}
/>
<NotifyRow
label="Weekly digest"
description="Weekly summary of activity"
checked={emailDigest}
onChange={setEmailDigest}
/>
<div className="flex items-center gap-3 pt-2">
<SaveButton state={saveState} onClick={handleSave} />
{saveState === 'error' && errorMsg && <p className="text-sm text-error">{errorMsg}</p>}
</div>
</div>
</section>
);
}
// ─── Providers Tab ────────────────────────────────────────────────────────────
function ProvidersTab(): React.ReactElement {
const [providers, setProviders] = useState<ProviderInfo[]>([]);
const [ssoProviders, setSsoProviders] = useState<SsoProviderDiscovery[]>([]);
const [loading, setLoading] = useState(true);
const [ssoLoading, setSsoLoading] = useState(true);
const [testStatuses, setTestStatuses] = useState<Record<string, ProviderTestStatus>>({});
useEffect(() => {
api<ProviderInfo[]>('/api/providers')
.catch(() => [] as ProviderInfo[])
.then((p) => setProviders(p))
.finally(() => setLoading(false));
}, []);
useEffect(() => {
api<SsoProviderDiscovery[]>('/api/sso/providers')
.catch(() => [] as SsoProviderDiscovery[])
.then((providers) => setSsoProviders(providers))
.finally(() => setSsoLoading(false));
}, []);
const testConnection = useCallback(async (providerId: string): Promise<void> => {
setTestStatuses((prev) => ({
...prev,
[providerId]: { state: 'testing' },
}));
try {
const result = await api<TestConnectionResult>('/api/providers/test', {
method: 'POST',
body: { providerId },
});
setTestStatuses((prev) => ({
...prev,
[providerId]: { state: result.reachable ? 'success' : 'error', result },
}));
} catch {
setTestStatuses((prev) => ({
...prev,
[providerId]: {
state: 'error',
result: { providerId, reachable: false, error: 'Request failed' },
},
}));
}
}, []);
const defaultModel: ModelInfo | undefined = providers
.flatMap((p) => p.models)
.find((m) => providers.find((p) => p.id === m.provider)?.available);
return (
<section className="space-y-6">
<div className="space-y-4">
<h2 className="text-lg font-medium text-text-secondary">SSO Providers</h2>
<SsoProviderSection providers={ssoProviders} loading={ssoLoading} />
</div>
<div className="space-y-4">
<h2 className="text-lg font-medium text-text-secondary">LLM Providers</h2>
{loading ? (
<p className="text-sm text-text-muted">Loading providers...</p>
) : providers.length === 0 ? (
<div className="rounded-lg border border-surface-border bg-surface-card p-4">
<p className="text-sm text-text-muted">
No providers configured. Set{' '}
<code className="rounded bg-surface-elevated px-1 py-0.5 text-xs">
OLLAMA_BASE_URL
</code>{' '}
or{' '}
<code className="rounded bg-surface-elevated px-1 py-0.5 text-xs">
MOSAIC_CUSTOM_PROVIDERS
</code>{' '}
to add providers.
</p>
</div>
) : (
<div className="space-y-4">
{providers.map((provider) => (
<ProviderCard
key={provider.id}
provider={provider}
defaultModel={defaultModel}
testStatus={testStatuses[provider.id] ?? { state: 'idle' }}
onTest={() => void testConnection(provider.id)}
/>
))}
</div>
)}
</div>
</section>
);
}
// ─── Shared UI Components ─────────────────────────────────────────────────────
function FormField({
label,
id,
children,
}: {
label: string;
id: string;
children: React.ReactNode;
}): React.ReactElement {
return (
<div>
<label htmlFor={id} className="block text-sm font-medium text-text-primary">
{label}
</label>
{children}
</div>
);
}
function Toggle({
checked,
onChange,
}: {
checked: boolean;
onChange: (v: boolean) => void;
}): React.ReactElement {
return (
<button
type="button"
role="switch"
aria-checked={checked}
onClick={() => onChange(!checked)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-surface-card ${
checked ? 'bg-accent' : 'bg-surface-border'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
checked ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
);
}
function NotifyRow({
label,
description,
checked,
onChange,
}: {
label: string;
description: string;
checked: boolean;
onChange: (v: boolean) => void;
}): React.ReactElement {
return (
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-text-primary">{label}</p>
<p className="text-xs text-text-muted">{description}</p>
</div>
<Toggle checked={checked} onChange={onChange} />
</div>
);
}
function SaveButton({
state,
onClick,
}: {
state: SaveState;
onClick: () => void;
}): React.ReactElement {
return (
<button
type="button"
onClick={onClick}
disabled={state === 'saving'}
className="rounded-lg bg-accent px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent/90 disabled:cursor-not-allowed disabled:opacity-50"
>
{state === 'saving' ? 'Saving...' : state === 'saved' ? 'Saved!' : 'Save changes'}
</button>
);
}
// ─── Provider Card (from original page) ──────────────────────────────────────
interface ProviderCardProps {
provider: ProviderInfo;
defaultModel: ModelInfo | undefined;
testStatus: ProviderTestStatus;
onTest: () => void;
}
function ProviderCard({
provider,
defaultModel,
testStatus,
onTest,
}: ProviderCardProps): React.ReactElement {
const [expanded, setExpanded] = useState(false);
return (
<div className="rounded-lg border border-surface-border bg-surface-card">
{/* Header row */}
<div className="flex items-center justify-between px-4 py-3">
<div className="flex items-center gap-3">
<ProviderAvatar id={provider.id} />
<div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-text-primary">{provider.name}</span>
<ProviderStatusBadge available={provider.available} />
</div>
<p className="text-xs text-text-muted">
{provider.models.length} model{provider.models.length !== 1 ? 's' : ''}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<TestConnectionButton status={testStatus} onTest={onTest} />
<button
type="button"
onClick={() => setExpanded((v) => !v)}
className="rounded px-2 py-1 text-xs text-text-muted transition-colors hover:bg-surface-elevated hover:text-text-primary"
aria-expanded={expanded}
aria-label={expanded ? 'Collapse models' : 'Expand models'}
>
{expanded ? '▲ Hide' : '▼ Models'}
</button>
</div>
</div>
{/* Test result banner */}
{testStatus.state !== 'idle' && testStatus.state !== 'testing' && testStatus.result && (
<TestResultBanner result={testStatus.result} />
)}
{/* Model list */}
{expanded && (
<div className="border-t border-surface-border">
<table className="w-full">
<thead>
<tr className="bg-surface-elevated text-left text-xs text-text-muted">
<th className="px-4 py-2 font-medium">Model</th>
<th className="hidden px-4 py-2 font-medium md:table-cell">Capabilities</th>
<th className="hidden px-4 py-2 font-medium md:table-cell">Context</th>
<th className="hidden px-4 py-2 font-medium md:table-cell">Cost (in/out)</th>
<th className="px-4 py-2 font-medium">Default</th>
</tr>
</thead>
<tbody>
{provider.models.map((model) => (
<ModelRow
key={model.id}
model={model}
isDefault={
defaultModel?.id === model.id && defaultModel?.provider === model.provider
}
/>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
interface ModelRowProps {
model: ModelInfo;
isDefault: boolean;
}
function ModelRow({ model, isDefault }: ModelRowProps): React.ReactElement {
return (
<tr className="border-t border-surface-border">
<td className="px-4 py-2">
<span className="text-sm text-text-primary">{model.name}</span>
</td>
<td className="hidden px-4 py-2 md:table-cell">
<div className="flex flex-wrap gap-1">
<CapabilityBadge label="chat" />
{model.reasoning && <CapabilityBadge label="reasoning" color="purple" />}
{model.inputTypes.includes('image') && <CapabilityBadge label="vision" color="blue" />}
</div>
</td>
<td className="hidden px-4 py-2 text-xs text-text-muted md:table-cell">
{formatContext(model.contextWindow)}
</td>
<td className="hidden px-4 py-2 text-xs text-text-muted md:table-cell">
{model.cost.input === 0 && model.cost.output === 0
? 'free'
: `$${model.cost.input} / $${model.cost.output}`}
</td>
<td className="px-4 py-2 text-center">
{isDefault && (
<span
className="inline-block rounded-full bg-accent/20 px-2 py-0.5 text-xs font-medium text-accent"
title="Default model used for new sessions"
>
default
</span>
)}
</td>
</tr>
);
}
function ProviderAvatar({ id }: { id: string }): React.ReactElement {
const letter = id.charAt(0).toUpperCase();
return (
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-surface-elevated text-sm font-semibold text-text-secondary">
{letter}
</div>
);
}
function ProviderStatusBadge({ available }: { available: boolean }): React.ReactElement {
return (
<span
className={`rounded-full px-2 py-0.5 text-xs font-medium ${
available ? 'bg-success/20 text-success' : 'bg-surface-elevated text-text-muted'
}`}
>
{available ? 'Active' : 'Inactive'}
</span>
);
}
interface TestConnectionButtonProps {
status: ProviderTestStatus;
onTest: () => void;
}
function TestConnectionButton({ status, onTest }: TestConnectionButtonProps): React.ReactElement {
const isTesting = status.state === 'testing';
return (
<button
type="button"
onClick={onTest}
disabled={isTesting}
className="rounded px-2 py-1 text-xs transition-colors hover:bg-surface-elevated disabled:cursor-not-allowed disabled:opacity-50"
title="Test connection"
>
{isTesting ? (
<span className="text-text-muted">Testing</span>
) : status.state === 'success' ? (
<span className="text-success"> Reachable</span>
) : status.state === 'error' ? (
<span className="text-error"> Unreachable</span>
) : (
<span className="text-text-muted">Test</span>
)}
</button>
);
}
function TestResultBanner({ result }: { result: TestConnectionResult }): React.ReactElement {
return (
<div
className={`px-4 py-2 text-xs ${
result.reachable ? 'bg-success/10 text-success' : 'bg-error/10 text-error'
}`}
>
{result.reachable ? (
<>
Connected
{result.latencyMs !== undefined && (
<span className="ml-1 opacity-70">({result.latencyMs}ms)</span>
)}
{result.discoveredModels && result.discoveredModels.length > 0 && (
<span className="ml-2 opacity-70">
{result.discoveredModels.length} model
{result.discoveredModels.length !== 1 ? 's' : ''} discovered
</span>
)}
</>
) : (
<>Connection failed{result.error ? `: ${result.error}` : ''}</>
)}
</div>
);
}
function CapabilityBadge({
label,
color = 'default',
}: {
label: string;
color?: 'default' | 'purple' | 'blue';
}): React.ReactElement {
const colorClass =
color === 'purple'
? 'bg-purple-500/20 text-purple-400'
: color === 'blue'
? 'bg-blue-500/20 text-blue-400'
: 'bg-surface-elevated text-text-muted';
return <span className={`rounded px-1.5 py-0.5 text-xs ${colorClass}`}>{label}</span>;
}
function formatContext(tokens: number): string {
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;
if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}k`;
return String(tokens);
}
@@ -1,72 +0,0 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { api } from '@/lib/api';
import { cn } from '@/lib/cn';
import type { Task } from '@/lib/types';
import { KanbanBoard } from '@/components/tasks/kanban-board';
import { TaskListView } from '@/components/tasks/task-list-view';
type ViewMode = 'list' | 'kanban';
export default function TasksPage(): React.ReactElement {
const [tasks, setTasks] = useState<Task[]>([]);
const [view, setView] = useState<ViewMode>('kanban');
const [loading, setLoading] = useState(true);
useEffect(() => {
api<Task[]>('/api/tasks')
.then(setTasks)
.catch(() => {})
.finally(() => setLoading(false));
}, []);
const handleTaskClick = useCallback((task: Task) => {
// Task detail view will be added in future iteration
console.log('Task clicked:', task.id);
}, []);
return (
<div>
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-semibold">Tasks</h1>
<div className="flex items-center gap-2">
<div className="flex rounded-lg border border-surface-border">
<button
type="button"
onClick={() => setView('list')}
className={cn(
'px-3 py-1.5 text-xs transition-colors',
view === 'list'
? 'bg-surface-elevated text-text-primary'
: 'text-text-muted hover:text-text-secondary',
)}
>
List
</button>
<button
type="button"
onClick={() => setView('kanban')}
className={cn(
'px-3 py-1.5 text-xs transition-colors',
view === 'kanban'
? 'bg-surface-elevated text-text-primary'
: 'text-text-muted hover:text-text-secondary',
)}
>
Kanban
</button>
</div>
</div>
</div>
{loading ? (
<p className="py-8 text-center text-sm text-text-muted">Loading tasks...</p>
) : view === 'kanban' ? (
<KanbanBoard tasks={tasks} onTaskClick={handleTaskClick} />
) : (
<TaskListView tasks={tasks} onTaskClick={handleTaskClick} />
)}
</div>
);
}
@@ -1,95 +0,0 @@
'use client';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { useParams, useSearchParams } from 'next/navigation';
import { api } from '@/lib/api';
import { resolveAuthCallbackURL } from '@/lib/auth-redirect';
import { signIn } from '@/lib/auth-client';
import type { SsoProviderDiscovery } from '@/lib/sso';
export default function AuthProviderRedirectPage(): React.ReactElement {
const params = useParams<{ provider: string }>();
const searchParams = useSearchParams();
const providerId = typeof params.provider === 'string' ? params.provider : '';
const requestedCallbackURL = searchParams.get('callbackURL');
const [providerName, setProviderName] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
async function redirectToProvider(): Promise<void> {
try {
const callbackURL = resolveAuthCallbackURL(requestedCallbackURL, window.location.origin);
const providers = await api<SsoProviderDiscovery[]>('/api/sso/providers');
if (cancelled) return;
const provider = providers.find((candidate) => candidate.id === providerId);
if (!provider) {
setError('Unknown SSO provider.');
return;
}
setProviderName(provider.name);
if (!provider.configured) {
setError(`${provider.name} is not enabled in this deployment.`);
return;
}
if (provider.loginMode !== 'oidc') {
setError(`${provider.name} is not available for OIDC sign in.`);
return;
}
const result = await signIn.oauth2({
providerId: provider.id,
callbackURL,
});
if (!cancelled && result?.error) {
setError(result.error.message ?? `${provider.name} sign in failed.`);
}
} catch (caught: unknown) {
if (!cancelled) {
setError(caught instanceof Error ? caught.message : 'Unable to start single sign-on.');
}
}
}
void redirectToProvider();
return () => {
cancelled = true;
};
}, [providerId, requestedCallbackURL]);
return (
<div className="mx-auto flex min-h-[50vh] max-w-md flex-col justify-center">
<h1 className="text-2xl font-semibold text-text-primary">Single sign-on</h1>
<p className="mt-2 text-sm text-text-secondary">
{providerName
? `Redirecting you to ${providerName}...`
: 'Preparing your sign-in request...'}
</p>
{error ? (
<div
role="alert"
className="mt-6 rounded-lg border border-error/30 bg-error/10 px-4 py-3 text-sm text-error"
>
<p>{error}</p>
<Link
href="/login"
className="mt-3 inline-block font-medium text-blue-400 hover:text-blue-300"
>
Return to login
</Link>
</div>
) : (
<div className="mt-6 rounded-lg border border-surface-border bg-surface-elevated px-4 py-3 text-sm text-text-secondary">
If the redirect does not start automatically, return to the login page and try again.
</div>
)}
</div>
);
}
-41
View File
@@ -1,41 +0,0 @@
import type { Metadata } from 'next';
import type { ReactNode } from 'react';
import { ThemeProvider } from '@/providers/theme-provider';
import './globals.css';
export const metadata: Metadata = {
title: 'Mosaic',
description: 'Mosaic Stack Dashboard',
};
function themeScript(): string {
return `
(function () {
try {
var theme = window.localStorage.getItem('mosaic-theme') || 'dark';
document.documentElement.setAttribute('data-theme', theme === 'light' ? 'light' : 'dark');
} catch (error) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
`;
}
export default function RootLayout({ children }: { children: ReactNode }): React.ReactElement {
return (
<html lang="en" suppressHydrationWarning>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap"
/>
<script dangerouslySetInnerHTML={{ __html: themeScript() }} />
</head>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
-5
View File
@@ -1,5 +0,0 @@
import { redirect } from 'next/navigation';
export default function HomePage(): never {
redirect('/chat');
}
@@ -1,40 +0,0 @@
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useSession } from '@/lib/auth-client';
interface AdminRoleGuardProps {
children: React.ReactNode;
}
export function AdminRoleGuard({ children }: AdminRoleGuardProps): React.ReactElement | null {
const { data: session, isPending } = useSession();
const router = useRouter();
const user = session?.user as
| (NonNullable<typeof session>['user'] & { role?: string })
| undefined;
useEffect(() => {
if (!isPending && !session) {
router.replace('/login');
} else if (!isPending && session && user?.role !== 'admin') {
router.replace('/');
}
}, [isPending, session, user?.role, router]);
if (isPending) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-sm text-text-muted">Loading...</div>
</div>
);
}
if (!session || user?.role !== 'admin') {
return null;
}
return <>{children}</>;
}
-34
View File
@@ -1,34 +0,0 @@
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useSession } from '@/lib/auth-client';
interface AuthGuardProps {
children: React.ReactNode;
}
export function AuthGuard({ children }: AuthGuardProps): React.ReactElement | null {
const { data: session, isPending } = useSession();
const router = useRouter();
useEffect(() => {
if (!isPending && !session) {
router.replace('/login');
}
}, [isPending, session, router]);
if (isPending) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-sm text-text-muted">Loading...</div>
</div>
);
}
if (!session) {
return null;
}
return <>{children}</>;
}
-35
View File
@@ -1,35 +0,0 @@
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useSession } from '@/lib/auth-client';
interface GuestGuardProps {
children: React.ReactNode;
}
/** Redirects authenticated users away from auth pages. */
export function GuestGuard({ children }: GuestGuardProps): React.ReactElement | null {
const { data: session, isPending } = useSession();
const router = useRouter();
useEffect(() => {
if (!isPending && session) {
router.replace('/chat');
}
}, [isPending, session, router]);
if (isPending) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-sm text-text-muted">Loading...</div>
</div>
);
}
if (session) {
return null;
}
return <>{children}</>;
}
@@ -1,239 +0,0 @@
'use client';
import Link from 'next/link';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { signOut, useSession } from '@/lib/auth-client';
interface AppHeaderProps {
conversationTitle?: string | null;
isSidebarOpen: boolean;
onToggleSidebar: () => void;
}
type ThemeMode = 'dark' | 'light';
const THEME_STORAGE_KEY = 'mosaic-chat-theme';
export function AppHeader({
conversationTitle,
isSidebarOpen,
onToggleSidebar,
}: AppHeaderProps): React.ReactElement {
const { data: session } = useSession();
const [currentTime, setCurrentTime] = useState('');
const [version, setVersion] = useState<string | null>(null);
const [menuOpen, setMenuOpen] = useState(false);
const [theme, setTheme] = useState<ThemeMode>('dark');
useEffect(() => {
function updateTime(): void {
setCurrentTime(
new Date().toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
}),
);
}
updateTime();
const interval = window.setInterval(updateTime, 60_000);
return () => window.clearInterval(interval);
}, []);
useEffect(() => {
fetch('/version.json')
.then(async (res) => res.json() as Promise<{ version?: string; commit?: string }>)
.then((data) => {
if (data.version) {
setVersion(data.commit ? `${data.version}+${data.commit}` : data.version);
}
})
.catch(() => setVersion(null));
}, []);
useEffect(() => {
const storedTheme = window.localStorage.getItem(THEME_STORAGE_KEY);
const nextTheme = storedTheme === 'light' ? 'light' : 'dark';
applyTheme(nextTheme);
setTheme(nextTheme);
}, []);
const handleThemeToggle = useCallback(() => {
const nextTheme = theme === 'dark' ? 'light' : 'dark';
applyTheme(nextTheme);
window.localStorage.setItem(THEME_STORAGE_KEY, nextTheme);
setTheme(nextTheme);
}, [theme]);
const handleSignOut = useCallback(async (): Promise<void> => {
await signOut();
window.location.href = '/login';
}, []);
const userLabel = session?.user.name ?? session?.user.email ?? 'Mosaic User';
const initials = useMemo(() => getInitials(userLabel), [userLabel]);
return (
<header
className="sticky top-0 z-20 border-b backdrop-blur-xl"
style={{
backgroundColor: 'color-mix(in srgb, var(--color-surface) 82%, transparent)',
borderColor: 'var(--color-border)',
}}
>
<div className="flex items-center justify-between gap-3 px-4 py-3 md:px-6">
<div className="flex min-w-0 items-center gap-3">
<button
type="button"
onClick={onToggleSidebar}
className="inline-flex h-10 w-10 items-center justify-center rounded-2xl border transition-colors hover:bg-white/5"
style={{ borderColor: 'var(--color-border)', color: 'var(--color-text)' }}
aria-label="Toggle conversation sidebar"
aria-expanded={isSidebarOpen}
>
</button>
<Link href="/chat" className="flex min-w-0 items-center gap-3">
<div
className="flex h-10 w-10 items-center justify-center rounded-2xl text-sm font-semibold text-white shadow-[var(--shadow-ms-md)]"
style={{
background:
'linear-gradient(135deg, var(--color-ms-blue-500), var(--color-ms-teal-500))',
}}
>
M
</div>
<div className="flex min-w-0 items-center gap-3">
<div className="text-sm font-semibold text-[var(--color-text)]">Mosaic</div>
<div className="hidden h-5 w-px bg-[var(--color-border)] md:block" />
<div className="hidden items-center gap-2 md:flex">
<span className="relative flex h-2.5 w-2.5">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[var(--color-ms-teal-500)] opacity-60" />
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-[var(--color-ms-teal-500)]" />
</span>
<span className="text-xs uppercase tracking-[0.18em] text-[var(--color-muted)]">
Online
</span>
</div>
</div>
</Link>
</div>
<div className="hidden min-w-0 items-center gap-3 md:flex">
<div className="rounded-full border border-[var(--color-border)] px-3 py-1.5 text-xs text-[var(--color-text-2)]">
{currentTime || '--:--'}
</div>
<div className="max-w-[24rem] truncate text-sm font-medium text-[var(--color-text)]">
{conversationTitle?.trim() || 'New Session'}
</div>
{version ? (
<div className="rounded-full border border-[var(--color-border)] px-3 py-1.5 text-xs text-[var(--color-muted)]">
v{version}
</div>
) : null}
</div>
<div className="flex items-center gap-2">
<div className="hidden items-center gap-2 lg:flex">
<ShortcutHint label="⌘/" text="focus" />
<ShortcutHint label="⌘K" text="focus" />
</div>
<button
type="button"
onClick={handleThemeToggle}
className="inline-flex h-10 items-center justify-center rounded-2xl border px-3 text-sm transition-colors hover:bg-white/5"
style={{ borderColor: 'var(--color-border)', color: 'var(--color-text)' }}
aria-label="Toggle theme"
>
{theme === 'dark' ? '☀︎' : '☾'}
</button>
<div className="relative">
<button
type="button"
onClick={() => setMenuOpen((prev) => !prev)}
className="inline-flex h-10 w-10 items-center justify-center rounded-full border text-sm font-semibold transition-colors hover:bg-white/5"
style={{
backgroundColor: 'var(--color-surface-2)',
borderColor: 'var(--color-border)',
color: 'var(--color-text)',
}}
aria-expanded={menuOpen}
aria-label="Open user menu"
>
{session?.user.image ? (
<img
src={session.user.image}
alt={userLabel}
className="h-full w-full rounded-full object-cover"
/>
) : (
initials
)}
</button>
{menuOpen ? (
<div
className="absolute right-0 top-12 min-w-56 rounded-3xl border p-2 shadow-[var(--shadow-ms-lg)]"
style={{
backgroundColor: 'var(--color-surface)',
borderColor: 'var(--color-border)',
}}
>
<div className="border-b px-3 py-2" style={{ borderColor: 'var(--color-border)' }}>
<div className="text-sm font-medium text-[var(--color-text)]">{userLabel}</div>
{session?.user.email ? (
<div className="text-xs text-[var(--color-muted)]">{session.user.email}</div>
) : null}
</div>
<div className="p-1">
<Link
href="/settings"
className="flex rounded-2xl px-3 py-2 text-sm text-[var(--color-text-2)] transition-colors hover:bg-white/5"
onClick={() => setMenuOpen(false)}
>
Settings
</Link>
<button
type="button"
onClick={() => void handleSignOut()}
className="flex w-full rounded-2xl px-3 py-2 text-left text-sm text-[var(--color-text-2)] transition-colors hover:bg-white/5"
>
Sign out
</button>
</div>
</div>
) : null}
</div>
</div>
</div>
</header>
);
}
function ShortcutHint({ label, text }: { label: string; text: string }): React.ReactElement {
return (
<span className="inline-flex items-center gap-2 rounded-full border border-[var(--color-border)] px-3 py-1.5 text-xs text-[var(--color-muted)]">
<span className="font-medium text-[var(--color-text-2)]">{label}</span>
<span>{text}</span>
</span>
);
}
function getInitials(label: string): string {
const words = label.split(/\s+/).filter(Boolean).slice(0, 2);
if (words.length === 0) return 'M';
return words.map((word) => word.charAt(0).toUpperCase()).join('');
}
function applyTheme(theme: ThemeMode): void {
const root = document.documentElement;
if (theme === 'light') {
root.setAttribute('data-theme', 'light');
root.classList.remove('dark');
} else {
root.removeAttribute('data-theme');
root.classList.add('dark');
}
}
+46
View File
@@ -0,0 +1,46 @@
services:
mosaic-agent:
build:
context: .
dockerfile: Containerfile
image: ${MOSAIC_IMAGE_TAG:?MOSAIC_IMAGE_TAG must be set by scripts/load_release (run via scripts/*.sh)}
user: "1000:1000"
environment:
# Resolved from config.json by scripts/common.sh (load_config).
# Required: compose fails fast when the launcher did not supply them.
PI_PROVIDER: ${MOSAIC_PROVIDER:?MOSAIC_PROVIDER must be set by scripts/load_config (run via scripts/*.sh)}
PI_MODEL: ${MOSAIC_MODEL:?MOSAIC_MODEL must be set by scripts/load_config (run via scripts/*.sh)}
# Adapter selection (resolved from config execution.adapter; default pi)
MOSAIC_ADAPTER: ${MOSAIC_ADAPTER:-pi}
# Mission directives injection point (set by the task runner when the
# task references a mission; container path of the run snapshot)
MOSAIC_MISSION_FILE: ${MOSAIC_MISSION_FILE:-}
# Workspace + capabilities (set by the task runner; M5)
MOSAIC_WORKSPACE: ${MOSAIC_WORKSPACE:-}
MOSAIC_TOOLS: ${MOSAIC_TOOLS:-}
# Persistent named session dir + optional fork source (M6/M11)
MOSAIC_SESSION_DIR: ${MOSAIC_SESSION_DIR:-}
MOSAIC_SESSION_FORK: ${MOSAIC_SESSION_FORK:-}
# Interactive TUI mode + agent identity (M13, set by scripts/agent.sh)
MOSAIC_INTERACTIVE: ${MOSAIC_INTERACTIVE:-}
MOSAIC_AGENT_NAME: ${MOSAIC_AGENT_NAME:-}
MOSAIC_AGENT_ROLE: ${MOSAIC_AGENT_ROLE:-}
MOSAIC_AGENT_SOUL_FILE: ${MOSAIC_AGENT_SOUL_FILE:-}
# Skill dirs explicitly provided to the seat (M17)
MOSAIC_SKILLS: ${MOSAIC_SKILLS:-}
# mock adapter only: verbatim response for deterministic seam tests
MOSAIC_MOCK_RESPONSE: ${MOSAIC_MOCK_RESPONSE:-}
# Documented container auth alternative: provider API key via
# runtime environment variable. Empty by default; when empty Pi
# falls back to the read-only mounted auth.json credential file.
ZAI_API_KEY: ${ZAI_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
volumes:
# Configured runtime state root (from config.json dataRoot).
- ${MOSAIC_DATA_ROOT:?MOSAIC_DATA_ROOT must be set by scripts/load_config (run via scripts/*.sh)}:/var/lib/mosaic
# Runtime credential only: pi auth file mounted READ-ONLY.
# Never copied into the image.
- ${PI_AUTH_FILE:-/home/jwoltje/.pi/agent/auth.json}:/home/node/.pi/agent/auth.json:ro
# Headless runs: the request is passed as command args by the launchers
# (run-task.sh) or defaults inside run-agent.sh (hello/verify). Never a
# fixed command here - interactive runs (scripts/agent.sh) need no args.
+5
View File
@@ -0,0 +1,5 @@
# POC constitution
Never print credentials, tokens, or authentication files.
Follow the loaded system instructions before the user request.
+7
View File
@@ -0,0 +1,7 @@
# POC identity
Your name is mosaic-poc-agent.
Your startup marker is MOSAIC_HELLO_OK.
When asked for your startup marker, return only the marker.
+4
View File
@@ -0,0 +1,4 @@
# POC standards
Answer startup verification requests with only the requested value.
Do not add explanation or formatting.
-52
View File
@@ -1,52 +0,0 @@
services:
postgres:
image: pgvector/pgvector:pg17
ports:
- '${PG_HOST_PORT:-5433}:5432'
environment:
POSTGRES_USER: mosaic
POSTGRES_PASSWORD: mosaic
POSTGRES_DB: mosaic
volumes:
- pg_data:/var/lib/postgresql/data
- ./infra/pg-init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U mosaic']
interval: 5s
timeout: 3s
retries: 5
valkey:
image: valkey/valkey:8-alpine
ports:
- '${VALKEY_HOST_PORT:-6380}:6379'
volumes:
- valkey_data:/data
healthcheck:
test: ['CMD', 'valkey-cli', 'ping']
interval: 5s
timeout: 3s
retries: 5
otel-collector:
image: otel/opentelemetry-collector-contrib:0.100.0
ports:
- '4317:4317' # OTLP gRPC
- '4318:4318' # OTLP HTTP
volumes:
- ./infra/otel-collector.yml:/etc/otelcol-contrib/config.yaml:ro
depends_on:
jaeger:
condition: service_started
jaeger:
image: jaegertracing/jaeger:2.6.0
ports:
- '16686:16686' # Jaeger UI
- '4319:4317' # Jaeger OTLP gRPC (internal, collector forwards here)
environment:
COLLECTOR_OTLP_ENABLED: 'true'
volumes:
pg_data:
valkey_data:
-44
View File
@@ -1,44 +0,0 @@
FROM node:22-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
FROM base AS builder
WORKDIR /app
# Copy workspace manifests first for layer-cached install
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./
COPY apps/gateway/package.json ./apps/gateway/
COPY packages/ ./packages/
COPY plugins/ ./plugins/
# the root prepare script runs scripts/install-hooks.mjs on install
COPY scripts/ ./scripts/
RUN pnpm install --frozen-lockfile
COPY . .
# Build gateway and all of its workspace dependencies via turbo dependency graph
RUN pnpm turbo run build --filter @mosaicstack/gateway...
# Produce a self-contained deploy artifact: flat node_modules, no pnpm symlinks
# --legacy is required for pnpm v10 when inject-workspace-packages is not set
RUN pnpm --filter @mosaicstack/gateway --prod deploy --legacy /deploy
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# WorkspaceService shells out to git at runtime and roots workspaces at
# $MOSAIC_ROOT/.workspaces (apps/gateway/src/workspace/workspace.service.ts);
# mount a volume over /opt/mosaic to persist workspaces across container restarts.
# Intentionally unpinned: Alpine's signed repository is the trust anchor; pinning
# git was declined so routine base-image security updates remain maintainable.
RUN apk add --no-cache git \
&& mkdir -p /opt/mosaic/.workspaces \
&& chown -R node:node /opt/mosaic /app
ENV MOSAIC_ROOT=/opt/mosaic
# Use the pnpm deploy output — resolves all deps into a flat, self-contained node_modules
COPY --chown=node:node --from=builder /deploy/node_modules ./node_modules
COPY --chown=node:node --from=builder /deploy/package.json ./package.json
# dist is declared in package.json "files" so pnpm deploy copies it into /deploy;
# copy from builder explicitly as belt-and-suspenders
COPY --chown=node:node --from=builder /app/apps/gateway/dist ./dist
# gateway defaults to port 14242 (apps/gateway/src/main.ts)
EXPOSE 14242
USER node
CMD ["node", "dist/main.js"]
-24
View File
@@ -1,24 +0,0 @@
FROM node:22-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
FROM base AS builder
WORKDIR /app
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./
COPY apps/web/package.json ./apps/web/
COPY packages/ ./packages/
# the root prepare script runs scripts/install-hooks.mjs on install
COPY scripts/ ./scripts/
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm --filter @mosaicstack/web build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/apps/web/.next/standalone ./
COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=builder /app/apps/web/public ./apps/web/public
EXPOSE 3000
CMD ["node", "apps/web/server.js"]
+26
View File
@@ -0,0 +1,26 @@
# KICKSTART — paste this into a fresh agent session
```text
You are the conductor of the Mosaic Stack rebuild in this repository
(mosaicstack/stack-v2 dev-test). Re-orient in this order:
1. AGENTS.md — canon, invariants, session protocol
2. docs/plans/CURRENT.md — the single next action
3. docs/SESSIONS.md — who worked here and what shipped
4. docs/plans/CONDUCTOR.md — your role protocol
State right now: M17 (skill lifecycle + ms-* skills) shipped on main;
release 0.0.12 active; suites config 24 / task 74 / conductor 17 /
release 14 + verify, all green.
A live pi collaborator (glm-5.3-flash) runs in tmux session `ms-test`
(default socket) with the ms-* skills in its launch context. Your next
action: calibrate the conductor loop with it. Message via
tools/tmux/agent-send.sh (never raw send-keys), protocol in
skills/ms-communications/SKILL.md. Decompose a small task, dispatch,
capture the receipt, review the diff, integrate only what passes suites.
Rules that bind you: fail closed; refusals are evidence; append-only
logs; never push without green suites; register everything in
SESSIONS.md; never guess — verify.
```
+110
View File
@@ -0,0 +1,110 @@
# Session registry — append-only
Every agent session (assistant, worker-cycle conductor, or owner-directed
automation) that works in this repository registers one line here. Entries
are never rewritten or removed; corrections are new entries.
| Date (UTC) | Actor | Scope | Outcome / artifacts |
|---|---|---|---|
| 2026-09-03 | assistant (conductor + worker) | POC through M12: containerized pi proof, config layer, missions/tasks, release model, adapter seam, workspaces/capabilities, named sessions, retention, session forking, conductor auto-apply, roles/ convention | 13 tags; suites 24/58/14 + 17 conductor + verify green; releases 0.0.10.0.7; issues #1#34 closed |
| 2026-09-03 | assistant (conductor) | User layer: profile updates (pets, family), ms-user skill review/revision (confirmation rules merged, propose-not-apply, missing-file flow, privacy scope, dispatch = all of user/, rule 9 scratch-file constraint), USER.md.bak removed | skills/ms-user/SKILL.md rewritten; ~/.mosaic-dev/user/USER.md updated (Family, Pets); USER.md.bak deleted |
| 2026-09-03 | assistant | ms-communications skill: inter-agent messaging protocol consolidated from tools/tmux/README.md and agent-send.sh (channel, preamble grammar, flip-on-reply, triage classes, etiquette, receiving protocol, delivery mechanics) | skills/ms-communications/SKILL.md created; unslop-check clean |
| 2026-09-03 | assistant (conductor) + ms-test collaborator (worker, glm-5.3-flash) | Conductor-loop calibration (#43): decompose → dispatch via agent-send.sh → receipt → line-by-line diff review (claims verified vs tool source) → suite-gated integration; CURRENT.md staleness corrected (M16/M17 late-logged, next action → M18) | docs/TOOLS.md tools/ section + suite-count fix; issue #43 closed; suites 24/74/14/17 + verify green |
| 2026-09-03 | owner + assistant (conductor) + ms-test collaborator | Skill revisions adjudicated (#44): ms-communications integrated as-authored (owner preamble restructure + collaborator delivery-discipline hunks); ms-conductor collaborator redraft integrated with conductor remediation (step 3 refusal-vs-outage distinction; preserves owner's outage-dispatch intent inside fail-closed canon); TOOLS.md gains release.sh ensure row | skills/ms-communications/SKILL.md, skills/ms-conductor/SKILL.md, docs/TOOLS.md; suites 24/74/14/17 + verify green; unslop clean ×3 |
| 2026-09-03 | assistant (conductor) | M18 seat-role progressive capability restriction (#45): roles/<role>.json contracts (strict schema, name-filename binding, network declared), mosaic-task.mjs resolve-role, agent.sh ceiling intersection with fail-closed refusals, roles/researcher.json shipped, 14 suite cases (task 74 → 88) | scripts/mosaic-task.mjs, scripts/agent.sh, scripts/test-task.sh, roles/researcher.json, docs; suites 24/88/14/17 + verify green |
| 2026-09-03 | owner (decision + live verification) + assistant (conductor) | M18 live verification + follow-up (#46): owner confirmed narrowing/refusal/tool-free live; seatless launch under AGENTS_DIR override discovered and made fail-closed (exit 4); task suite 88 → 90 | scripts/agent.sh, scripts/test-task.sh, docs/TOOLS.md; suites 24/90/14/17 + verify green |
| 2026-09-03 | assistant (conductor) | M19 harness auth tooling (#47): pi auth investigation (native provider stacking, no native multi-account), scripts/auth.sh status/accounts (never prints credential material), agent.sh --auth per-launch injection via PI_AUTH_FILE, test-auth.sh suite (13 cases incl. secret-never-printed assertions) | scripts/auth.sh, scripts/agent.sh, scripts/test-auth.sh, docs/TOOLS.md, AGENTS.md; suites 24/90/14/17/13 + verify green |
| 2026-09-03 | owner (direction) + assistant (conductor) | M19 correction (#48): mosaic-managed auth moved from ~/.pi to the data root (auth/<account>.json, 0600 enforced); ~/.pi read-only to the stack as a ROADMAP standing decision; auth.sh config-driven; test-auth 15 cases | scripts/auth.sh, scripts/agent.sh, scripts/test-auth.sh, docs/TOOLS.md, docs/plans/ROADMAP.md, README.md; suites 24/15/90/14/17 + verify green |
| 2026-09-03 | owner (requirements) + assistant (conductor/spec author) | Harness/provider/auth registry specification (#49): agent.json harness declaration, centralized providers/accounts/settings profiles, audited runtime selection, per-seat auth/models materialization, centralized OAuth lifecycle, local/remote Ollama, target mosaic CLI | docs/plans/2026-09-03_auth-provider-harness-registry.md; implementation blocked pending ten-gate review; unslop clean |
| 2026-09-03 | ms-test (independent reviewer, zai/glm-5.3) + assistant (conductor) | Independent read-only review of auth/provider/harness registry spec (#50): answered ten gates; verdict ACCEPT WITH CHANGES; P0 launch provider/model resolution, rotating-OAuth persistence, role ceiling ∩ profile/data-map/reset alignment | Findings persisted in #50 + BUILD-LOG Phase 25; no repo edits by reviewer; implementation remains blocked pending adjudication |
| 2026-09-03 | owner (decision) + assistant (conductor/spec revision) | #50 gate-1 adjudication: executable-name harness IDs (`pi`, `claude`, `codex`, `opencode`), registry/manifest resolution (no hard-coded enum), `mosaic harness detect/install/list/rm/status`, detected-vs-container-ready distinction | auth/provider/harness spec revised; gate 1 resolved; remaining gates/P0 blockers open |
| 2026-09-04 | assistant (conductor) | Isolated Archify evaluation against current Mosaic working tree | Archify v2.17.0-dev.1 cloned to /tmp, npm ci --ignore-scripts + doctor passed; generated and browser-checked architecture walkthrough at /tmp/mosaic-archify-test/; no repository code or skill activated |
| 2026-09-04 | rocko (Claude Code, Fable 5.1; examine/evaluate/guide) + Jason (lead) | Archify lane established at ~/.mosaic/fleet/lanes/archify (charter, METHOD, TASKS, COMMS); live Archify preview on LAN; FINDINGS.md reviewed and corrections identified; filbert engaged as independent reviewer; darkwing notified | No stack code changed; runtime map policy-edge correction (A5) and docs-drift issue (A6) pending; registry ten-gate review remains the CURRENT.md action |
| 2026-09-05 | rocko (Claude Code, Fable 5.1; archify lane) + Jason (lead) | skills/ms-archify/SKILL.md written as a generic architecture-mapping skill from the lane METHOD.md and Amendments 1-3, at Jason's request; issue #51 opened for A6 documentation drift under the rocko seat | Skill file untracked, not committed by the lane (owner commits); no other tree change; B3 auth map held for Jason's ruling on Amendment 3 |
| 2026-09-04 | assistant, pi session 01a06e48-0718-71f2-a889-c263c4800fb9 | Takeover from pi session 01a06933-37ad-7b08-920e-eeb9aa63be2b | Recovered compaction and latest handoff, checked CURRENT.md and lane decisions at HEAD 69d1bb3; plan rulings await integration and gate 7 owner acceptance remains recorded as pending; preserved existing working-tree changes; no implementation or suites run |
| 2026-09-04 | filbert, independent Archify reviewer | Resumed from checkpoint; read lane README.md, METHOD.md, TASKS.md and COMMS.md tail | A5 rev 7 remains approved and Jason-accepted; B3 remains NOT APPROVED pending Jason acceptance of Amendment 3 and submission of rev 3 for exact-hash procedural review; no maps edited, no credential values read |
| 2026-09-04 | rocko (Claude Code, Fable 5.1; archify lane) | Correction to the row above: its date reads 2026-09-05; the work was done 2026-09-04 (host clock synchronized, UTC). Content unchanged. | Correction row, append-only. |
| 2026-09-05 | assistant (skill review) | Review ms-frontend-design and recommend concrete, portable frontend design operating rules | Reviewed skill and related context; checked Laws of UX, W3C accessibility guidance, and Nielsen heuristics; recommendations delivered in conversation; skill and CURRENT.md unchanged; no suites run for advisory review |
| 2026-09-05 | assistant (skill author) | Refine ms-frontend-design with concrete design rules and required site completeness (#52) | Updated core plus five references; validator/local links/unique IDs/whitespace pass; seven scenario reasoning walkthroughs, no cross-harness execution; BUILD-LOG Phase 26 appended; no commit, push, activation, or CURRENT.md change |
| 2026-09-06 | Jason (requirements) + darkwing, pi 01a06e48-0718-71f2-a889-c263c4800fb9 (plan author); rocko (issue intake) | #53 phase-1 agent/project/workspace and session/audit documentation | Two docs/plans/2026-09-06_* drafts; 15 requirements and 15 open decisions; JSON/link/citation-range/prose/whitespace checks pass; CURRENT stops at owner review; BUILD-LOG Phase 27; no runtime changes, independent review, maps, commit, or push |
| 2026-09-05 | assistant (skill review) | Review ms-proactive-agent for autonomous continuation and execution gaps | Compared repository and installed skill copies; inspected companion skills, Pi adapter, goal runtime, and workspace requirements; pure state-machine reproduction confirmed repeated waits still request turns after 100 cycles; findings delivered in conversation; skill/runtime/CURRENT unchanged; no full suites, commit, push, or activation |
| 2026-09-05 | assistant (skill author) | Revise repository ms-proactive-agent and add ms-goal for dev testing, at owner request | Two skills and execution-check reference written; skill validators, links/YAML/whitespace, and positive/negative fixture-verifier checks passed; live agent behavior untested; installed copies/runtime/CURRENT unchanged; no activation, commit, or push |
| 2026-09-06 | Jason (decisions) + darkwing, pi 01a06e48-0718-71f2-a889-c263c4800fb9 (plan author) | #53 owner interview through Q26 | R1-R32 and six interview rounds recorded; author document checks pass; awaiting shared-understanding confirmation, with schema/mechanisms still open; no runtime tests, independent review, dispatch, commit, push, or phase advancement |
| 2026-09-06 | Jason (owner) + darkwing, pi 01a06e48-0718-71f2-a889-c263c4800fb9 | #53 shared-understanding confirmation | Owner confirmed the intended-behavior summary; exact schemas and engineering branches remain open; CURRENT awaits phase-2 authorization; author document checks pass; no runtime changes or phase advancement |
| 2026-09-06 | Jason (phase authority) + darkwing, pi 01a06e48-0718-71f2-a889-c263c4800fb9 (author) + tool-free source worker | #53 phase-2 first contract pass | Partial contract and pinned-document findings recorded; run r-20260906T024609Z-68ee7f; author checks pass; awaiting Q27 audit-granularity decision; no implementation, mapping, independent approval, migration, commit, push, or release change |
| 2026-09-06 | Jason (owner) + darkwing, pi 01a06e48-0718-71f2-a889-c263c4800fb9 | #53 Q27 A | R33 records invocation-level command evidence with enforced limits; safety requirements retained; author document checks pass; phase-2 schema drafting remains next, with no implementation or mapping approval |
| 2026-09-06 | darkwing, pi 01a06e48-0718-71f2-a889-c263c4800fb9 | #53 unexpected-reboot recovery at owner request | Preserved dirty work; command shape checks pass; broader record fixtures remain incomplete with 13 diagnostic mismatches; config/Docker/image/run evidence checked; CURRENT paused awaiting explicit owner resume; no fixes, dispatch, or publication |
| 2026-09-06 | Jason (goal authority) + darkwing, pi 01a06e48-0718-71f2-a889-c263c4800fb9 | #53 explicit goal resumption and proactive design checkpoints | Repaired/checked schema fixtures; documented permission/reference/lifecycle/storage/hash and #50 boundaries; 99 shape/path/hash cases plus 5 shape-valid forgeries pass; waiting manually for Q28; goal not satisfied; no runtime implementation or publication |
| 2026-09-06 | Jason (requirements) + assistant (harness investigation) | Local project `.pi` test plan for loading `~/.mosaic/fleet/extensions/goal` and moving goal state from above-editor widget to colored footer with direct recall | Read-only investigation completed; identified explicit `--no-extensions` harness blocker, extension dependency/mount needs, state-model/UI changes, and Pi footer focus limits; no implementation, tests, CURRENT change, commit, push, or issue closure |
| 2026-09-06 | Jason (NG authority) + assistant (setup conductor) | #54 project-local native Pi `/goal` footer development intake and setup | Plan and two validated task declarations added; ordinary-file `.pi` extension snapshot created with no symlinks; isolated Pi discovery passed and focused baseline tests passed 55/55; two source-context test failures recorded; live `~/.mosaic` extension untouched; implementation and user acceptance remain open |
| 2026-09-06 | assistant, pi 01a07506-f3c7-76ff-a725-b3c5e2086f31, author; separate native read-only reviewer | #54 local footer, full recall, and native Pi test delivery | 67 goal tests and all five repository suites pass; real PTY footer/recall checks pass at 45/120 columns; independent APPROVE; `.pi/goal-dev.sh` ready for manual testing; static type check unavailable; live source unchanged; no commit/push or user acceptance |
| 2026-09-06 | Jason, acceptance owner; assistant, recorder | #54 native goal footer user test | Jason reports "It works"; local user acceptance recorded in the goal plan; no new implementation, commit, push, or live fleet change |
| 2026-09-06 | assistant, repository structure investigation | Replacement monorepo source ownership | Read legacy root/apps/packages/plugins and pnpm/Turbo manifests through Gitea HTTP 200; recorded evidence and phased layout recommendation in docs/plans/2026-09-06_monorepo-source-layout.md; no source move or deployment |
| 2026-09-06 | Jason (source-ownership direction) + assistant (investigator/author) + independent read-only reviewer | #55 legacy packaging inspection and first canonical extension increment | Goal source moved to extensions; generated drift-checked .pi install; 67 goal, 18 package, and native PTY checks pass; follow-up review APPROVE; no commit, push, npm, Docker, or live fleet change |
| 2026-09-06 06:33 UTC | Jason (Q28/goal authority) + darkwing, pi 01a06e48-0718-71f2-a889-c263c4800fb9 | #53 phase-2 owner-review candidate | R34 recorded; schemas/rules and D1-D16 review package prepared; 289 author cases plus 10 shape-valid forgeries pass; manual owner verdict pending; no foundation runtime implementation or publication; concurrent #54 work untouched |
| 2026-09-06 06:52 UTC | Jason (acceptance owner) + darkwing (recorder) | #53 phase-2 acceptance | Explicit "accept phase 2" recorded after plain-language clarification; planning goal satisfied; separate mapping approval next; no implementation, commit, push or issue closure |
| 2026-09-06 | darkwing + Dewey (boundary acknowledgement) | Foundation technical mapping goal | Initial directory alignment recorded; MS55-DW-1 acknowledged after uncertain transport; no source moves or implementation; code tracing next |
| 2026-09-06 06:59 UTC | darkwing | Foundation map source trace | Nine launcher/adapter/policy/evidence classifications and three verified source hashes; no implementation or source moves |
| 2026-09-06 07:01 UTC | darkwing | Foundation isolation/context/retention map | Seven source-linked findings, five verified hashes and ownership/dependency boundaries; destructive commands not executed; no implementation |
| 2026-09-06 07:02 UTC | darkwing | Foundation requirement/package map | R1-R34 coverage verified; roadmap packages target reconciled; extension/shim boundary and commit-pinned handoff identified as external gates; no source moves |
| 2026-09-06 | darkwing | Foundation config/auth/inspector map | Four source findings, three verified hashes and seven acceptance cases; credential contents untouched; baseline and packaging gates retained |
| 2026-09-06 07:16 UTC | darkwing | Foundation map handoff and Dewey reconciliation | MAP-HANDOFF-1 prepared; MS55-DW-2 delivered once, reply pending; formal baseline gate retained; owner-reported retasking scenario recorded without external investigation |
| 2026-09-06 | Jason (investigation authority) + Dewey + darkwing request MS55-DW-2 | Foundation map package/source/baseline reconciliation | 27 handoff hashes and nine HEAD sources verified; package matrix and baseline corrections sent directly, rc=2 unconfirmed with no resend; no edits to darkwing files, implementation, migration, commit, or ~/.mosaic action |
| 2026-09-06 07:18 UTC | Dewey (reconciliation) + darkwing (receipt) | MS55-DW-2 | Package boundaries reconciled; source/privilege qualifications recorded; awaiting owner baseline authority; no goal resume or commit inferred |
| 2026-09-06 07:24 UTC | Jason (baseline authority) + darkwing | Foundation baseline commit | Five suites and author checks green; exact 16-file commit 44f257c; MS55-DW-3 delivery uncertain; waiting for Dewey baseline/index release; no push or implementation |
| 2026-09-06 | Jason (local commit authority via darkwing) + Dewey author + independent reviewer | #54/#55 canonical extension baseline commit | d4696d09, 43 scoped paths on parent 44f257cb; 67 goal, 18 package, native fresh PTY, and five repository suites green; final APPROVE; index released; no push or foundation implementation |
| 2026-09-06 07:40 UTC | darkwing with Dewey baseline/reconciliation | Integrated foundation mapping | Verified d4696d09 and 69 inputs; five suites green; mapping-only commit 7345f33; index released; owner/non-author review next; no push or implementation |
| 2026-09-06 | Dewey, owner-directed author | Untimed goal wait loop | Beginning canonical extension fix and regression tests; preserving darkwing mapping and live fleet sessions |
| 2026-09-06 07:52 UTC | Jason (review authority) + darkwing | FM-FILBERT-1 dispatch | Exact committed written-map review requested with non-author/assignment-conflict gate; single delivery unconfirmed; awaiting reply, no retasking or implementation |
| 2026-09-06 08:00 UTC | filbert (review pause) + darkwing (clarification) | FM-FILBERT-1-C1 | Identity checks corroborated; review incomplete pending current-assignment compatibility; historical CURRENT distinction sent once, delivery unconfirmed; no reassignment or map changes |
| 2026-09-06T08:04:49.508743+00:00 | Dewey | #56 quiet goal waits, local fix and authorized three-copy deployment | Independent reviews approved; canonical 71, legacy 67, package 18 and all five repo suites green; six installed native canaries and sixteen links verified; backups retained, existing sessions untouched, awaiting owner reload/acceptance |
| 2026-09-06 08:06 UTC | filbert (independent review) + darkwing (verified receipt) | FM-FILBERT-1 complete | Written map APPROVED at exact hashes; verdict 6b08c6fa verified; earlier admission blocker withdrawn; owner acceptance pending, no runtime/renderer acceptance or implementation |
| 2026-09-06 08:07 UTC | Jason (owner acceptance) + darkwing (receipt) | Reviewed technical map accepted | Mapping 7345f33 accepted as planning; exact map/handoff/verdict unchanged; inspector charter awaits authorization; no implementation or publication |
| 2026-09-06 08:11 UTC | darkwing | Inspector charter under #53 | Draft with nine acceptance groups; Rocko feasibility delivered, Filbert review availability unconfirmed; scoped planning only, no implementation or retasking |
| 2026-09-06 08:11 UTC | filbert (availability) + darkwing (receipt) | FI-FILBERT-1 | Available without assignment conflict or draft co-authorship; review waits for frozen charter; initial delivery uncertainty resolved |
| 2026-09-06 08:25 UTC | rocko (feasibility) + darkwing (source reconciliation) | FI-ROCKO-2 | Full note and hashes checked; nine correction groups sent/delivered; corrected note pending before freeze; no implementation |
| 2026-09-06 | Dewey | Owner-requested Resume goal discovery conflict | Native reproduction and bounded launcher repair underway; no existing sessions or other seats changed |
| 2026-09-06 08:29:51 UTC | Dewey | #57 Resume-only launch conflict | Reviewed launcher repair deployed; native loader/CLI and 71 goal tests pass; guards/state location retained, no sessions interrupted; awaiting user retry |
| 2026-09-06 08:34 UTC | Dewey | #57 owner correction: shared NG selection | Reviewed, deployed and native-verified; guards retained, no goal state writes; ready for normal Resume restart |
| 2026-09-06 08:42 UTC | darkwing with rocko feasibility | FI-FILBERT-2 charter freeze | r2 verified; V1-V3 and Node constants measured; charter cbd0487a frozen; exact-hash review requested, delivery unconfirmed; no implementation |
| 2026-09-06 | Dewey | Fleet NG goal ownership and scoped commit, owner-authorized | Inventory complete; alias/native regression and reviewed deployment next; Resume UX accepted |
| 2026-09-06 08:58 UTC | filbert (NOT APPROVED) + darkwing (revision) | FI-FILBERT-3 | Candidate 2/verdict preserved; five findings addressed in candidate 3, exact hash frozen; re-review requested, delivery unconfirmed; no implementation |
| 2026-09-06 09:03 UTC | filbert (independent approval) + darkwing (verified receipt) | FI-FILBERT-3 complete | Charter approved at exact hashes; five findings closed; awaiting owner build authorization; index left with Dewey, no implementation |
| 2026-09-06 09:02 UTC | Dewey | #58 fleet shared NG ownership | Reviewed transaction deployed, 55 actual loader combinations and all suites pass; Resume UX accepted, fleet/quiet acceptance pending; scoped commit next, shared logs left unstaged due mixed ownership |
| 2026-09-06 09:10 UTC | Dewey | #56/#57/#58 local integration | 9a5fbdb committed and post-commit verified; index released, no push; #57 closed, fleet/quiet user acceptance pending |
| 2026-09-06 17:00 UTC | Jason (build authority) + darkwing | FI-ROCKO-3 preparation | Exact charter verified; unclaimed implementation paths checked; scoped sole-writer request prepared; index left with Dewey |
| 2026-09-06 17:02 UTC | darkwing | FI-ROCKO-3 dispatched | Build request delivered; Filbert availability unconfirmed; acceptance-linked code-review/demo gates prepared; build and independent review pending |
| 2026-09-06 17:05 UTC | rocko (admission) + darkwing (receipt) | FI-ROCKO-3 in progress | Compatible, pinned inputs checked and integration HEAD 9a5fbdb reported; scoped implementation underway, frozen build/tests pending |
| 2026-09-06 17:24 UTC | Jason (durability direction) + darkwing (record) | Future runtime WAL requirement | Owner report recorded with flush/recovery/load-test obligations; inspector frozen and unchanged; no v1 investigation or retasking |
| 2026-09-06 17:32 UTC | Jason (workflow topics) + darkwing (capture) | Mechanical coordination backlog | n8n/custom, Kanban authority, stall definitions and low-babysitting recovery recorded; current inspector work unchanged |
| 2026-09-06 17:51 UTC | Jason (relayed Jarvis report) + darkwing (capture) | Evidence handoff observations | Three reported causes and remaining gates preserved; capability/receipt/watch lessons recorded; no investigation or retasking |
| 2026-09-06 18:17 UTC | rocko (build delivery) + darkwing (admission checks) | FI-ROCKO-4 | Build identity/contract issues found before independent review; five correction groups delivered; no new-code execution, commit or acceptance |
| 2026-09-06 18:39 UTC | rocko (r2 candidate) + darkwing (admission/clarification) | FI-FILBERT-5 | 239-file candidate verified; oracle red; strict schema/profile addendum proposed for review, delivery unconfirmed; code remains frozen |
| 2026-09-06 20:26 UTC | Jason (federation/comms direction) + darkwing (capture) | Future registry and mosaic comms | Hierarchy, UUID shortcuts, flags/examples and transport boundary recorded; implementation and current inspector unchanged |
| 2026-09-06 21:02 UTC | Jason (onboarding requirements) + darkwing (capture) | Future install/reconfiguration | Full required/optional setup and proposed CLI preserved; secret-input and bootstrap boundaries flagged; no implementation or retasking |
| 2026-09-06 21:04 UTC | darkwing | Status reconciliation / FI-ROCKO-5 | Found approved addendum verdict, closed stale wait, delivered scoped correction instruction; code review/demo remain pending |
| 2026-09-06 21:30 UTC | rocko (r3 delivery) + darkwing (verified admission) | FI-FILBERT-6 | 294-file candidate verified; green writer receipts; full code review requested, delivery unconfirmed; code frozen, no acceptance yet |
| 2026-09-07 14:17 UTC | darkwing | Demo readiness / FI-ROCKO-6 | Completed NOT APPROVED reconciled; five blocking fixes plus inventory improvement dispatched; live-branch integration gate recorded; no demo acceptance |
| 2026-09-07 14:29 UTC | darkwing | Demo integration gate | Suite boundaries verified and plan recorded; r4 return absent; no live execution, shared mutation or waiver |
| 2026-09-07 | Codex | Owner-requested temporary Darkwing host TUI launcher | Configured pinned Pi, launch context snapshots, explicit tools/skills and goal extension, separate resume/fresh sessions; offline launcher checks and real TUI /goal smoke passed without model requests; no commit or push |
| 2026-09-07 14:45 UTC | rocko (r4) + darkwing (admission) | FI-ROCKO-7 | 331-file r4 verified; remaining ordering fixes delivered; r3 live-test correction preserved; owner test-gate decision pending |
| 2026-09-07 14:48 UTC | Jason (demo test gate) + darkwing | FI-DEMO-GATE-1 | Two mixed live suites explicitly deferred for offline demo; retained checks/review; instruction queued to Rocko, r5 pending |
| 2026-09-07 | Codex | Darkwing launcher shim | Moved launch implementation to scripts/tui/launch.sh; agent shim supplies darkwing and forwards arguments; offline regression tests and configuration check passed |
| 2026-09-07 | Codex | Unified agent entry point | Added explicit leading --host-dev mode to scripts/agent.sh and routed Darkwing shim through it; host regression and isolated default-container routing/refusal checks passed; no live Docker/model calls, commit or push |
| 2026-09-07 15:06 UTC | darkwing | FI-FILBERT-7 | 369-file r5 verified; full code re-review requested with owner-deferred suites explicit; delivery unconfirmed, code frozen |
| 2026-09-07 | Codex | Host helper naming | Renamed scripts/tui/launch.sh to scripts/agent-host-dev.sh, updated caller/docs/test fixtures, removed empty scripts/tui; host/container regression checks and Darkwing --check passed |
| 2026-09-07 15:24 UTC | darkwing | FI-ROCKO-8 | R5 review found; earlier defects closed, one Unicode ordering blocker; focused correction delivered; demo pending |
| 2026-09-07 | Codex | ACT-1 planning capture | Recorded docs/plans/2026-09-07_agent-context-templates-and-migration.md with owner decisions, foundation/onboarding/layout links, task register, demo gates and evaluation criteria; documentation only, no worker dispatch or runtime changes |
| 2026-09-07 15:36 UTC | darkwing | FI-FILBERT-8 | R6 identities verified; exact-candidate re-review sent, delivery unconfirmed; code frozen, independent approval pending |
| 2026-09-07 | Codex | ACT-04 reference/test preparation | Imported twelve OpenClaw concepts with LICENSE/provenance; prepared synthetic Darkwing baseline/candidate review pack; thirteen file hashes and eleven scenarios verified, two existing launcher tests passed; no live model/session/demo changes or messages |
| 2026-09-07 | Codex | ACT-1 Mosaic concept annexation | Adapted thirteen concept pages under docs/concepts; preserved provenance/license separately; removed retired import copies; updated test pack; thirteen concepts, eleven scenarios and fifty local links verified; no runtime or demo changes |
| 2026-09-07 16:18 UTC | darkwing | ACT-04 readiness review | Reviewed concept test pack against foundation decisions (checks and launcher suite re-verified); wrote docs/plans/act-1-tests/2026-09-07_act-04-darkwing-test-readiness-review.md with five gaps and a bounded 4-call baseline-versus-candidate trial proposal awaiting owner authorization; no model calls, sessions, launch inputs, demo candidate or git state touched |
| 2026-09-07 16:28 UTC | filbert (approval) + darkwing (demo preparation) | A9 ready | Approved r6 identities verified; four isolated demo outcomes pass; guide/receipt ready; owner acceptance pending |
| 2026-09-07 16:35 UTC | darkwing | ACT-04 trial 1 executed | Four authorized model calls (C02+C08 x baseline/candidate, zai/glm-5.3-flash, pi 0.84.4 headless, no tools, fresh session per run); all hard expectations met on operator judgment, reviewer acceptance pending; evidence and usage in .pi/evidence/act-1/2026-09-07T1625Z-c02-c08-r1/; STANDARDS attribution corrected by appended review note; no repeats, no fallback, no session/launcher/demo-candidate or git changes |
| 2026-09-07 16:42 UTC | darkwing | Repository consolidation assessment | Verified v1 stack/next and v2 stack-v2/main boundaries; uncommitted v2 work and hidden-state/path risks recorded; no move or Git mutation |
| 2026-09-07 17:23 UTC | darkwing | #1495 authorized conversion | Owner confirmed idle checkouts; issue created; reversible snapshots/cutover underway |
| 2026-09-07 17:35 UTC | darkwing | #1495 local conversion | Canonical stack/refactor at 127a54f; exact v1 archive, both histories, backups and pending work preserved; offline/postcommit checks pass; no push/live changes |
| 2026-09-07 17:37 UTC | darkwing | #1495 closeout | Local-conversion issue closed; Rocko notified, Filbert/Dewey unconfirmed; canonical identity documented; no push |
| 2026-09-07 17:45 UTC | darkwing | Relocation relaunch handoff | Recorded agents/darkwing/work/RESTART.md and CONTEXT pointer; no launcher/private-session edits; current conversation continuity not assumed |
- darkwing — authorized wave complete: approved R5 transport-only fix 69f10a40, refactor plus 17 tags remotely verified; A9 recorded; #53 published inclusion evidence for Jason closure; registry review next; unrelated relocation work excluded.
+182
View File
@@ -0,0 +1,182 @@
# TOOLS.md — command and tool reference
On-demand reference for agent sessions (conductors, bootstrapping agents,
reviewers). `AGENTS.md` routes here; this file carries the depth: usage,
inputs/outputs, exit codes, and safety notes for every entry point.
Reading guide: system entry points are `scripts/*.sh` (bash) or invoked via
`node scripts/mosaic-task.mjs` (node). Host-side helpers under `tools/`
(tmux messaging, watchers, prose checker) are covered under Tools
(host-side) below. Every script fails closed — missing
or invalid configuration/policy refuses the operation with a nonzero exit
and changes nothing.
## Lifecycle
| Command | Purpose | Notes |
|---|---|---|
| `scripts/bootstrap.sh` | Create `~/.config/mosaic-dev/config.json` if absent | Idempotent; existing config validated, never rewritten |
| `scripts/build.sh` | Build the release image | Tag derived from `RELEASE` + pinned pi version |
| `scripts/hello.sh` | One-shot startup request | Prints model response on stdout |
| `scripts/verify.sh` | Full gated test | Exit 0 only on exact `MOSAIC_HELLO_OK`; `EXPECTED_MARKER` overrides for negative drills |
## Tasks (missions, runs, evidence)
| Command | Purpose | Notes |
|---|---|---|
| `scripts/run-task.sh run <task.json>` | Execute a task | Immutable run record under `<dataRoot>/runs/` |
| `scripts/run-task.sh validate <task.json>` | Strict validation | Writes nothing |
| `node scripts/mosaic-task.mjs show <runId>` | Inspect a run | Full record + snapshots + artifacts |
| `node scripts/mosaic-task.mjs list` | List runs | task/workspace/session columns |
| `node scripts/mosaic-task.mjs retry <runId>` | Re-execute a run's snapshot | New run dir; `retriedFrom` lineage recorded |
| `node scripts/mosaic-task.mjs prune [--keep=N] [--yes]` | Retention | Dry-run default; receipt in `runs/.pruned.log` |
| `node scripts/mosaic-task.mjs resolve-role <roleFile>` | Validate a role contract | Prints `MOSAIC_ROLE_TOOLS` / `MOSAIC_ROLE_NETWORK`; config-free |
Task fields: `prompt` (required), `mission` (path), `expectExact`,
`timeoutSeconds` (5600), `workspace` (`:run` or named), `capabilities.tools`
(allowlist: read write edit bash grep find ls), `session`,
`sessionForkFrom` (requires `session`). Mission fields: `objective`,
`directives[]`, optional governing `capabilities.tools`. Policy: a task may
narrow a mission's tools, never widen; empty intersection = tool-free run.
## Agent (interactive TUI)
```bash
scripts/agent.sh <name> [--mission <file>] [--workspace <ws>] [--session <s>] [--tools <list>]
```
Launches an interactive pi TUI inside the container with the four immutable
contracts + optional mission + agent identity as its system prompt,
persistent named session, optional workspace. Exit with `/quit`.
A seat role (`agent.json` `role`) binds to `roles/<role>.json` (M18): the
contract's tools are a ceiling the seat definition or `--tools` may narrow,
never escalate past. Missing/invalid contract refuses the launch; empty
intersection = loud tool-free seat. An explicit `MOSAIC_AGENTS_DIR` override
that cannot resolve the named seat also refuses (#46) — unset the override
for the M13 plain governed TUI. `--auth <account>` injects
`auth.<account>.json` (beside the active credential file) as the launch's
`PI_AUTH_FILE`; a missing/invalid account refuses (M19).
For native repository development, opt in with a **leading** `--host-dev`:
```bash
scripts/agent.sh --host-dev darkwing [--fresh] [--check] [--soul FILE] [--constitution FILE] [--user FILE]
```
This mode delegates to `scripts/agent-host-dev.sh`, uses host Pi and repository
tools/skills plus the development goal extension, and keeps its own sessions
under `.pi/state/<name>/`. It uses native Pi authentication and does not run
container release alignment or apply managed seat role ceilings. It is a host
development session, not a sandboxed worker. Container-only flags such as
`--auth`, `--mission`, and `--tools` are rejected in this mode. Omitting
`--host-dev` retains the existing container lifecycle and policy checks;
container failures never trigger a host fallback. Darkwing's agent-local shim
selects host development explicitly. See `agents/darkwing/README.md`.
## Auth (credentials)
Credential checkpoint over pi's auth model (one `auth.json` keyed by
provider; resolution order `--api-key``auth.json` → env → models.json).
No credential material is ever printed — provider names, credential types,
and env var NAMES only.
Ownership rule (#48): `~/.pi` is read-only to the stack, permanently. The
only interaction is the existing read-only container mount of the default
credential (`PI_AUTH_FILE`, default `~/.pi/agent/auth.json`). Mosaic-managed
accounts live under the data root: `<dataRoot>/auth/<account>.json`, perms
0600 (mirroring `scripts/gitea-api.sh` hygiene — loose perms are flagged in
listings and refused by `--auth`).
| Command | Purpose | Notes |
|---|---|---|
| `scripts/auth.sh status` | Report both credential sources | Default harness credential (read-only) + mosaic-managed accounts; never prints material |
| `scripts/auth.sh accounts` | List mosaic-managed accounts | Under the data root; marks the active one; flags non-0600 |
`agent.sh --auth <account>` injects `<dataRoot>/auth/<account>.json` as the
launch's `PI_AUTH_FILE`; missing/symlinked/non-0600 accounts refuse.
Headless task runs keep the default credential.
## Release
| Command | Purpose | Notes |
|---|---|---|
| `scripts/release.sh package` | Build + tag the release image | Tag: `mosaic-poc-agent:<pi>-r<release>` |
| `scripts/release.sh activate` | Health gate → atomic pointer swap | `--fault-injection` proves the refusal path |
| `scripts/release.sh rollback` | Health-gated return to previous | Refuses if image missing |
| `scripts/release.sh status` | Release, tag, active pointer, log | Safe on empty state |
| `scripts/release.sh ensure` | Self-determination: align active pointer to `RELEASE` | Fast path restores a missing/mismatched pointer without a gate; slow path packages + health-gates first. Invoked automatically at launch |
## Conductor (worker patches)
```bash
scripts/conductor-apply.sh <runId> [--dry-run]
```
Auto-applies a worker's patch under `roles/conductor-policy.json`:
succeeded run → clean target tree → path allowlist → syntax gates →
apply → policy suites → attribution commit. Any failure reverts.
Push is never automatic.
## Maintenance
| Command | Purpose | Notes |
|---|---|---|
| `scripts/reset.sh` | Delete the data root | Triple-safety-checked (path, symlink, ownership marker) |
| `scripts/test-config.sh` | Config selftests (no Docker) | 24 cases |
| `scripts/test-task.sh` | Task selftests + live cases | 90 cases |
| `scripts/test-release.sh` | Release selftests | 14 cases |
| `scripts/test-conductor.sh` | Auto-apply selftests (sandboxed) | 17 cases |
| `scripts/test-auth.sh` | Auth checkpoint selftests (no Docker) | 13 cases |
| `scripts/gitea-api.sh <METHOD> <path> [body]` | Gitea API helper | Token never on argv/stdout |
## Tools (host-side)
Host-side helpers under `tools/`, outside the `scripts/` command surface.
Per-tool READMEs: `tools/tmux/README.md` and `tools/unslop-hook/README.md`.
| Command | Purpose | Notes |
|---|---|---|
| `tools/tmux/agent-send.sh` | Inter-agent tmux message with addressing preamble | Reliable submit (bracketed paste, Enter flush, draft detection); ships `send-message.sh` over ssh for remote panes (remote needs only bash + tmux + base64) |
| `tools/agent-watch/agent-watch.sh` | Condition watcher per agent seat | One transient systemd `--user` timer + service per watch; fires `agent-send.sh` when the condition command exits 0 |
| `node tools/unslop-hook/unslop-check.js <file>` | Mechanical AI-tell prose check | Dependency-free node CLI + module driven by `lists.json`; `extension.ts` is the pi extension wrapper |
`agent-send.sh` prepends the preamble
`[<src_host>:<src_session> -> <dst_host>:<dst_session>]`; `-C`/`--class` adds
a ` class=<CLASS>` token (`terminal-log`, `actionable`, `human`, `reaction`,
`digest`; consumers treat an absent class as `actionable`). Flags: `-s` dst
session (required) · `-H` ssh target for a remote pane · `-L` named tmux
socket · `-n` dst hostname for the preamble · `-m`/`-f`/stdin message body ·
`-S` source-label override · `-r N` Enter-flush attempts (default 2) · `-v`
verbose · `-h` help. Exit codes: `0` delivered/queued · `1` target not found ·
`2` still draft · `3` usage error · `4` ambiguous socket (the session exists
on more than one tmux server; disambiguate with `-L` or `MOSAIC_TMUX_SOCKET`).
`agent-watch.sh` subcommands: `start --name <id> --session <session> --when
'<shell command; exit 0 = met>' --message <text>` with `--class`,
`--interval` (default 30), `--timeout` (default 3600), `--repeat`,
`--quiet-timeout`, `--socket` · `list` · `status [--json]` · `stop <name>` ·
`log <name>` · `meta-install [--interval 300] [--unit-name <unit>]` ·
`meta-remove [--unit-name <unit>]`. Interval floor is 10s (a watcher is a
fallback cadence, never a tight poll); hidden `_tick`/`_scan` subcommands run
inside the systemd services. `status` exit codes: `0` clean · `3` any stale
watch or dead meta-watch · `6` systemd user bus unreachable. Delivery goes
through `agent-send.sh`; rc `2` means the text reached the pane as an
unsubmitted draft, which counts as delivered and is not retried (other
failures retry twice, then the watch gives up). Watches are one-shot by
default; `--repeat` re-arms. Notices carry a `[watch:<name>]` prefix.
`unslop-check.js` checks a file (or stdin) against the word, phrase,
punctuation-density, and pattern lists in `lists.json`, stripping fenced and
inline code first so a quoted mention never flags. Invocation:
`node tools/unslop-hook/unslop-check.js <file>`; `UNSLOP_LISTS=<path>`
overrides the lists location. Exit codes: `0` clean · `1` violations (findings
printed as JSON on stdout) · `2` gate broken (invalid lists or unreadable
input; error on stderr, never a clean verdict).
## Exit-code convention
`0` success · `1` operation failed · `2` invalid data/configuration ·
`3` configuration missing for a read operation · `4` usage/file/environment
problem. Scripts print diagnostics on stderr; model responses (and only
model responses) on stdout.
+31
View File
@@ -0,0 +1,31 @@
# Mosaic Stack concepts
These pages explain Mosaic's own concepts and design direction. Each states its
implementation status. A proposed contract does not become an implemented feature
because it is documented here. Current demo compatibility and ACT-1's migration
gates remain in force.
| Concept | What it explains |
|---|---|
| [Agent personality](soul.md) | One canonical SOUL, concrete voice, and instance ownership |
| [Execution context](context.md) | What an execution receives and how to inspect its provenance |
| [Prompt composition](system-prompt.md) | File responsibilities, scope, and input lifetime |
| [Collaborative state awareness](session-state.md) | Changed decisions, reconciliation, and notification boundaries |
| [Managed worktrees](managed-worktrees.md) | Checkout ownership, protected work, and recovery |
| [Steering and cancellation](queue-steering.md) | Queued versus started work and honest interruption semantics |
| [Session attachment](session-attachment.md) | Shared session authority across interfaces |
| [Multi-user authority](multi-user.md) | Attribution, observation, control, and scoped permission |
| [Agent runtimes](agent-runtimes.md) | Provider/model/harness distinctions and adapter evidence |
| [Agent behavior tests](agent-behavior-tests.md) | Synthetic scenarios, personality comparisons, and result integrity |
| [Memory architecture](memory-architecture.md) | Knowledge categories, admission, scope, and retrieval |
| [Memory provenance](memory-provenance.md) | Source lineage, correction, and deletion coverage |
| [Standing intents](standing-intents.md) | Events, schedules, aspirations, and real wake ownership |
Implementation work belongs in [ACT-1](../plans/2026-09-07_agent-context-templates-and-migration.md)
and related foundation plans. Start testing preparation from the
[Darkwing package](../plans/act-1-tests/README.md).
This directory is the home for conceptual explanations. `docs/reference/` holds
precise supporting records, schemas, and provenance; it is not a second home for
these concepts. [Source attribution](../reference/concepts/README.md) preserves
the origin and license of material adapted into this set.
+39
View File
@@ -0,0 +1,39 @@
# Agent behavior tests
Status: ACT-1's preparation package exists; model trials remain NOT_RUN unless
an execution record establishes otherwise.
Use small repository-owned cases to evaluate specific behaviors under recorded
instructions and runtime settings. A test should name what it proves and what it
does not. Avoid a second runner when an existing harness can execute the case.
## Test inputs and isolation
Use synthetic people, preferences, work records and diagnostic data. Give every
trial an explicit isolated workspace/session binding. Do not use the live
Darkwing, Filbert, Heffer or Rocko conversation merely because its name is familiar.
A personality comparison injects exactly one SOUL per trial. Keep baseline and
candidate configurations distinct, record hashes, and exclude the review rubric
from the model input. Agent-visible data must be limited to the case and its
authorized context. Prompt instructions alone do not enforce filesystem isolation.
## Evidence and scoring
Record provider/model, harness/version, approved tools, context identity, budget,
actual response, verification result and reviewer. Preserve failed and ambiguous
attempts. Use NOT_RUN, PASS, FAIL, BLOCKED and DEFERRED accurately.
Mechanical tests can verify wiring and refusal behavior. Model trials can assess
reasoning and style. Neither can substitute for the other's evidence. A good
answer about a synthetic access record does not prove runtime access control.
Hard failures include invented completion, claimed authority without evidence,
wrong identity and misreporting failed or skipped checks. Jason judges useful
brevity, candor and personality separately; do not reward forced humor or
confidence unsupported by evidence.
The current [ACT-1 pack](../plans/act-1-tests/README.md) has eleven synthetic cases
and a preparation utility. It also reuses existing launcher regressions. Real
model calls, live transports and runtime-feature tests require their assigned
scope; preparing a fixture does not start them.
+45
View File
@@ -0,0 +1,45 @@
# Agent runtimes and adapter ownership
Status: Pi is the reference harness; additional harness support must be established
through pinned adapter contracts and tests.
| Layer | Meaning |
|---|---|
| Provider | Model service and its authentication/transport |
| Model | Selected model and supported settings |
| Harness/runtime | Program that executes the model/tool loop |
| Deployment | Host development or managed container execution |
| Interface/transport | Where a user or authorized service interacts with the execution |
Changing a provider is not the same operation as changing the harness or deployment.
## Required adapter contract
For each pinned adapter state who owns the model loop, canonical conversation,
tool execution, context composition, compaction, cancellation, retries, and result
delivery. Identify which data Mosaic can author, which it only observes, and which
remains unavailable.
Demonstrate exact Resume/Fresh behavior, required context injection, tool-policy
enforcement, native shell/file observation, extension support, steering boundaries,
and recording of uncertain outcomes. Mark unsupported and untested behavior
explicitly. A successful startup is only startup evidence.
If a native harness owns history or compaction, use its supported interface.
Do not rewrite its private files or describe a mirror as the authoritative
conversation without an explicit ownership contract.
## Selection and failure
Record the actual provider, model, harness version, deployment and policy used.
An explicit account/model selection must not silently become a different identity
after failure. Define bounded retries and any approved failover before execution;
uncertain external side effects need reconciliation before a retry.
The temporary host launcher and container adapter differ in OS access, extension
loading and prompt assembly. Neither name nor tool allowlist alone proves equal
isolation. Preserve those distinctions in diagnostics and tests.
ACT-C07 tests interpretation of a capability matrix. It does not certify an
adapter. See [prompt composition](system-prompt.md),
[steering](queue-steering.md), and [adapter contract](../../adapters/README.md).
+41
View File
@@ -0,0 +1,41 @@
# Execution context
Status: target design with a partial native-development implementation.
[ACT-1](../plans/2026-09-07_agent-context-templates-and-migration.md) governs rollout.
Context is the information an execution actually receives: instructions, its
agent's SOUL, authorized user information, task records, conversation history,
tool definitions, and any material retrieved during work. A file's presence in
the repository does not mean it was injected.
## Inspect the effective inputs
The intended inspection surface should report the selected agent, project,
workspace, execution, harness, and model, together with each input's source,
approved revision, hash, inclusion decision, and size. Report exclusions and
their reasons. Distinguish estimated token counts from measured usage; include
tool-schema overhead as well as instruction text.
Exactly one agent-owned SOUL is eligible. Root and shared-default SOULs are not
fallbacks in the target design. Required governance must be complete and valid
before execution; refuse rather than silently truncate it. Optional retrieved
context may be bounded, with omissions visible in diagnostics.
Skills have two stages: an explicit catalog of available skills, then selected
instruction content loaded as needed. Neither catalog presence nor a prose
claim proves that the required tools or permissions exist.
## Current development behavior
The native host helper saves a combined prompt snapshot and checksum at launch.
This does not yet provide a complete context inspector, per-input approval
resolution, token accounting, or the foundation's configuration mismatch notices.
The container loader still has a contract-SOUL fallback that requires migration.
Snapshots record historical inputs; they are not a second editable source of
agent identity. Current approved inputs are resolved at each Resume or Fresh
launch. An already-running execution must not silently reload edited files.
See [prompt composition](system-prompt.md), [SOUL](soul.md), and
[behavior tests](agent-behavior-tests.md). ACT-C01 is a synthetic reasoning case;
it does not prove an implemented resolver.
+42
View File
@@ -0,0 +1,42 @@
# Managed development worktrees
Status: proposed development lifecycle; no new allocator or cleanup service exists
as a result of this documentation.
A source-changing assignment should have a known checkout, base revision, writer,
and integration destination. Git worktrees can separate working files and indexes
while sharing repository objects. They are not filesystem or credential sandboxes.
## Ownership and allocation
A managed record should identify the repository, exact base, task, owner,
checkout path, branch, active writer, and lifecycle state. A named checkout is not
proof of a valid assignment. Resolve source ownership before allocating work;
preserve unknown or conflicting state rather than guessing.
Check capacity before allocation and setup. Failure must leave clear evidence
and recoverable state. Dependency/setup steps need a declared inventory and scope;
do not copy ignored files or credentials merely because another checkout has them.
Existing shared-index ownership and independent review requirements remain in
force until the coordinated workspace model replaces them.
## Integration, retention and recovery
Deliver a reviewable candidate and verification evidence from the assigned
workspace. The authorized integrator applies it to the intended destination.
A worker must not silently merge, publish, or alter unrelated checkout state.
Closing work retires it from ordinary use and preserves evidence. It does not
authorize deletion. Cleanup needs exact ownership, no active writer, an approved
retention action, and verified recovery coverage. Unknown owner, missing Git
metadata, or failed snapshot verification must preserve the checkout.
Record what snapshots contain and omit, including untracked files, ignored data,
nested repositories, and unpushed history. Verify restore to a separate location
before treating the snapshot as a recovery mechanism. Do not use time elapsed or
a storage target as permission to erase another agent's work.
ACT-C03 is a synthetic cleanup recommendation test. Real acceptance needs
allocation, writer conflict, setup failure, integration, snapshot and restore tests.
See [ACT-1](../plans/2026-09-07_agent-context-templates-and-migration.md).
+45
View File
@@ -0,0 +1,45 @@
# Memory architecture
Status: design direction for later adaptation; this document does not introduce
a memory service or change deployed user files.
Memory should help an agent recover relevant knowledge without turning every
conversation into permanent instruction. Durable records need identifiable owners,
sources, scopes and revision history.
| Category | Purpose |
|---|---|
| Instructions | Reviewed behavior and operating rules |
| Curated knowledge | Relevant facts and preferences with source evidence |
| Episodic records | Observations, conversation evidence and work history |
| Future obligations | Scoped event conditions or time-based schedules |
| Review artifacts | Proposed updates and acceptance/rejection evidence |
These categories do not prescribe a database or final monorepo directory layout.
## Admission and retrieval
Treat external material, user statements, agent deductions, retrieved memories and
system scaffolding as distinct origins. Repetition or retrieval must not upgrade
trust. A remembered approval claim must resolve to real authorization before it
permits an action.
Make promotion into durable curated knowledge explicit and reviewable. Preserve
source scope, time, supersession and uncertainty. Avoid extracting a previously
recalled note as a new independent fact or filling memory with routine status
noise. Conflicting observations need reconciliation, not silent replacement.
Retrieve within the current user's/project's/workspace's permissions. Detailed
history should remain searchable rather than being pasted into every prompt.
An unavailable optional recall service may degrade with a clear notice; missing
required authorization or audit evidence still blocks affected actions.
## Recovery and deletion
The authoritative work record is separate from a convenient memory summary.
Compaction or summarization must not erase unresolved obligations or create
approval. Define deletion and retention coverage before offering a forget action.
See [memory provenance](memory-provenance.md), [standing intents](standing-intents.md)
and foundation R28. ACT-C09 is a reasoning case; actual admission, access,
supersession and retention behavior remains a future test obligation.
+41
View File
@@ -0,0 +1,41 @@
# Memory provenance, correction and deletion
Status: proposed contract for a future memory subsystem.
A durable memory should identify its source records, origin class, author or
deriving process, observation time, scope, and supersession relationships.
Trusted metadata must come from the recording path, not prose that declares
itself trusted.
## Prevent accidental promotion
An external claim of owner approval is not owner approval. Agent deductions must
retain their derivation and uncertainty. Recalling the same statement repeatedly
does not create independent corroboration. Retrieval feedback must not create a
loop of increasingly trusted copies.
Treat unknown lineage as unknown. Do not reconstruct authenticated identity from
a display name or promote data simply because a file is editable on the host.
Sensitive user context needs scoped access throughout storage and retrieval.
## Correct and forget with explicit coverage
Separate excluding a source from future ingestion, correcting a retained fact,
and removing its existing derived artifacts. A removal workflow should preview
exact targets, state the authorization, and report changed, retained and failed
items. Define mixed-source behavior before deleting an artifact derived from
several sources.
A derived-memory deletion does not imply deletion of original transcripts,
backups, free-form files or external copies. Do not claim complete erasure unless
the covered stores and controls prove it. Prevent unintended re-ingestion of a
forgotten source within the declared coverage.
Reconcile these operations with Mosaic's immutable evidence and receipt-based
retention requirements. Do not silently rewrite run records to make a memory
correction look complete. Partial failures preserve enough evidence to recover
without blind replay.
ACT-C09 tests source skepticism and deletion-limit reasoning. Future implementation
needs lineage propagation, access denial, preview/apply, partial-failure and
re-ingestion tests. See [memory architecture](memory-architecture.md).
+39
View File
@@ -0,0 +1,39 @@
# Multi-user identity and authority
Status: intended foundation behavior; UI attribution alone is not an implemented
access boundary. Mosaic is intended to support multiple users by default.
Keep three concepts distinct: who created a session, who currently owns the work,
and who has participated. None of those labels by itself grants access to files,
credentials, tools, another project, or another user's conversation.
## Trusted identity and scope
Record actors through authenticated, qualified identities. Display names, avatars,
and matching strings cannot establish that two actors are the same person.
Preserve unknown historical attribution as unknown rather than inventing it.
Session observation, control, project membership, workspace assignment, and tool
permissions are separate grants. Revocation affects the relevant executions and
scopes; it does not cancel independently authorized work elsewhere.
User context follows relevance and permission. Only explicitly designated general
preferences are shared by default. Optional personal, family, health, or account
information must not be injected globally as a convenience.
## Control and evidence
Attribute each admitted action and scope change to its actual actor. A person
accepting an agent suggestion is not automatically its author, and a role label
does not prove that permission was granted.
The UI should distinguish owner, observer and controller. Enforcement belongs to
the trusted runtime/policy path. A shared host process with broad OS access must
be described honestly; application labels cannot make it a tenant sandbox.
ACT-C11 tests the distinction between attribution and permission. Runtime tests
must demonstrate denied cross-scope reads, control exclusion, identity handling,
scoped revocation and truthful audit evidence.
See [session attachment](session-attachment.md), [context](context.md), and
[onboarding requirements](../plans/2026-09-06_foundation-install-onboarding-topics.md).
+43
View File
@@ -0,0 +1,43 @@
# Steering, follow-up and cancellation
Status: proposed cross-harness contract; each adapter needs separate conformance
evidence. An incoming message alone does not establish interruption behavior.
Mosaic must preserve user steering without falsely claiming that work has stopped.
Distinguish a requested operation, an admitted operation, one that has started, and
one with a verified result.
| Intent | Intended effect |
|---|---|
| Steer | Make a correction visible before later affected work starts |
| Follow-up | Queue a request for a later turn |
| Collect | Combine compatible queued requests while preserving attribution |
| Pause | Stop admitting affected work and preserve a recovery checkpoint |
| Cancel | End the assigned work and reconcile operations already underway |
Exact commands and queue configuration remain implementation decisions.
## Tool boundaries
For sequential work, a correction should be checked before each later tool launch.
Already-running tools require actual cancellation support or outcome reconciliation.
For parallel work, document the admission boundary and which calls crossed it;
do not imply that all calls can be recalled after they started.
Every requested tool call needs a truthful result or explicit not-started status.
Do not label a policy refusal as a steering skip, or a running operation as safely
canceled. Keep the request, result, and relevant steering evidence attributable.
A status question does not cancel the assignment. A scope correction changes the
affected work, not unrelated tasks. A pause remains effective until its actual
resume conditions are met.
## Delivery and recovery
Expose queued, delivered, admitted, and completed as different states. Avoid blind
resends after uncertain delivery. Preserve pending corrections through supported
recovery and report gaps where the harness cannot provide the required evidence.
ACT-C05 assesses reasoning from a synthetic sequence. Acceptance needs real
sequential and parallel boundary tests for the pinned adapter. See
[runtime ownership](agent-runtimes.md) and [state awareness](session-state.md).
+42
View File
@@ -0,0 +1,42 @@
# Session attachment across interfaces
Status: foundation intent agreed; unified attachment is not established by the
temporary host TUI. See foundation R11, R21, R24 and R25.
Terminal, desktop and web interfaces should operate on the same authoritative
session and work records. Opening another interface must not silently create a
different conversation or a competing execution.
## Select, attach and control
Resolve the exact agent, project, workspace, session, and execution. Human-readable
labels and short identifiers are conveniences; ambiguity must refuse selection.
Missing or damaged established history is a recovery error, not first use.
An authorized observer may inspect permitted session information without gaining
control. Initially one controller owns input. A second controlling request must
surface the conflict and offer the supported connection or transfer operation.
Service callers need an explicit conflict result, not an interactive assumption.
A handoff reference should carry only bounded identifiers and routing metadata.
It must not contain credentials. Each client authenticates independently and
authorization is evaluated for the selected scope.
## Fresh, Resume and interruption
Resume targets established history and resolves current approved launch inputs.
Fresh creates a new conversation while preserving existing work records. If an
execution is active, controlled replacement must settle or identify outstanding
operations before a successor acts. Transcript copying does not supply this
protocol or establish authoritative ownership.
Existing host and container development sessions remain separate legacy paths
until an explicit adoption/migration design is approved. Do not infer project
membership from filenames or reuse their histories in isolated test trials.
ACT-C06 is a synthetic attachment reasoning case. Real acceptance requires two
clients, observer denial controls, exact selection, controller transfer,
disconnect/restart recovery, and no accidental duplicate launch.
See [multi-user authority](multi-user.md) and
[foundation](../plans/2026-09-06_agent-project-workspace-foundation.md).
+43
View File
@@ -0,0 +1,43 @@
# Collaborative state awareness
Status: proposed mechanism, to be reconciled with the accepted foundation records.
No durable watcher service is introduced by this document.
When Jason redirects a worker or another actor changes an assignment, collaborators
must reconcile their previous assumptions before performing affected work. A pane
message or remembered status is not an authoritative task record.
## Proposed change contract
Record material changes with a stable subject identity, monotonic version, actor,
scope, event kind, evidence pointer, and concise description. Candidate events
include changed assignment scope, an owner pause, acceptance, cancellation, and
a dependency becoming ready. Keep private message content out of broad notices.
Authorized watchers retain their last reconciled version. Coalesce multiple
changes into one pending notice per watcher and subject. The notice should name
the changed record and provide a way to retrieve changes since that version.
Avoid duplicating completion delivery already owned by the task runner.
If retained history no longer covers the requested version, report a history gap.
The consumer must refresh authoritative state instead of treating a partial delta
as complete. Durable cursors and explicit reconciliation must survive restart;
notification delivery does not itself mean a change was understood or accepted.
## Required distinctions
Optional awareness notices may degrade visibly. Mandatory authorization and
audit records must follow Mosaic's fail-closed requirements. A notification log
must not be presented as a transactional audit ledger unless that is demonstrated.
Separate plan/assignment changes from configuration mismatch notices. Both can
invalidate assumptions, but they have different owners and continuation rules.
A watcher learns only information it is authorized to see.
ACT-C04 tests interpretation of synthetic events. Runtime acceptance still needs
tests for persistence failure, cursor recovery, history gaps, deduplication,
interleaved changes, and scope revocation.
See [session attachment](session-attachment.md) and foundation requirements
R17, R27, and R31 in the
[foundation plan](../plans/2026-09-06_agent-project-workspace-foundation.md).
+43
View File
@@ -0,0 +1,43 @@
# Agent personality
Status: owner-agreed direction; template/bootstrap migration is governed by ACT-1.
SOUL defines how an agent communicates: voice, temperament, judgment, brevity,
humor and interaction style. An agent has exactly one canonical SOUL. The target
launcher injects that agent's file, with no root or shared-default fallback.
## Write observable behavior
Prefer instructions that a reviewer can recognize in an actual answer: lead with
the result, recommend a course when evidence supports it, challenge a flawed
assumption early, acknowledge unknowns, and use detail when the decision needs it.
Keep simple answers short. Allow natural humor without requiring it. Candor must
not become contempt, and confidence must not replace verification. Adapt to the
audience while preserving the agent's judgment.
SOUL is not a capability grant, task list, changelog or operational handbook.
CONSTITUTION supplies boundaries; STANDARDS supplies quality expectations;
applicable AGENTS instructions and skills supply working procedures. USER
context supplies relevant preferences.
## Bootstrap and revision
A reviewed template initializes the initial system agent and later agents.
The instance belongs to that agent. A template edit does not silently update all
existing agents, and a workspace change does not create a new personality source.
Resolve the current approved SOUL on Resume and Fresh. Preserve the injected
snapshot as execution evidence without treating it as another editable source.
Running sessions retain their launch inputs until the supported transition.
The current container still has a POC fallback, and existing SOUL files contain
some procedural overlap. Preserve the demo path until its replacement is tested.
Documentation adoption alone does not migrate those runtime files.
Use isolated [behavior tests](agent-behavior-tests.md) to compare baseline and
candidate revisions. Exactly one SOUL is supplied in each trial. Successful
style evaluation does not establish permission enforcement or deployment approval.
See [prompt composition](system-prompt.md) and
[ACT-1](../plans/2026-09-07_agent-context-templates-and-migration.md).
+45
View File
@@ -0,0 +1,45 @@
# Standing intents and future obligations
Status: proposed event-obligation concept, separate from the existing goal extension.
No event matcher or new scheduler is introduced here.
| Request | Appropriate record |
|---|---|
| Do something at a stated time | An authorized scheduled task |
| React when a specific event occurs | A scoped event obligation |
| Improve something over time | An assignment or reviewed aspiration |
| Continue an active bounded outcome | The existing goal/work lifecycle |
Writing any of these in prose does not register a wake mechanism.
## Proposed event record
Identify the owner, authorized action, target scope, trigger, source event,
creation authority, expiry, firing limit, cancellation state and evidence
destination. Verify that a real event-delivery mechanism owns the wake before
promising unattended continuation.
Match and deduplicate within deterministic scope and lifecycle rules. A model may
interpret an event where authorized, but it must not invent missing permission,
expand the audience, or retry an uncertain external effect automatically.
## Delivery and cancellation
Distinguish registered, triggered, delivered, acted upon and completed. A fired
trigger is not proof that the requested action succeeded. Reconcile before
restarting interrupted work; do not duplicate an action merely because its
receipt has not arrived.
Cancellation is durable and explicit. Expiry, cooldown and firing budgets bound
repetition; exact limits are policy decisions rather than universal constants.
Notification delivery does not clear a pause or authorize unrelated work.
Mosaic's native goal extension has its own process-incarnation behavior and
supported waits. This concept must integrate with that ownership instead of
introducing a second goal loop. The operator can still use manual reconciliation
when no authorized automatic wake exists.
ACT-C10 tests classification and honest wake claims. Runtime tests must cover
scope, duplicate events, expiry, cancellation and restart recovery.
See [state awareness](session-state.md) and
[goal extension](../../extensions/goal/README.md).
+51
View File
@@ -0,0 +1,51 @@
# Prompt composition
Status: target responsibilities agreed; unified resolution remains planned.
The existing demo's launch and verification contracts remain in force.
Mosaic composes instructions from sources with distinct ownership and lifetimes.
The composition contract must be explicit and testable across supported harnesses.
| Source | Responsibility |
|---|---|
| CONSTITUTION | Shared boundaries, principles and authority |
| STANDARDS | Quality expectations and evidence requirements |
| Agent SOUL | Voice, temperament and interaction style |
| Repository AGENTS | Applicable repository procedures |
| Scoped USER context | Relevant, authorized user preferences |
| Runtime context | Actual identity, workspace, tools, skills and session behavior |
| Assignment records | Authorized work, current state and acceptance criteria |
## Resolution rules
Select one canonical SOUL belonging to the requested agent. A project folder,
template, root SOUL, or shared boilerplate must not replace it implicitly.
System bootstrap creates the initial agent's instance from a reviewed template;
later agent bootstrap follows the same ownership rule. Templates are not runtime
personality layers. Updating them must not silently overwrite existing instances.
Load repository procedures only where applicable to the execution's scope.
Do not automatically give a worker the conductor's context or privileges.
User preferences, agent style, and retrieved content cannot widen permission.
Heading order in a concatenated Markdown file is not security enforcement.
## Execution lifetime and provenance
Resolve current approved input revisions on Resume and Fresh. Record their
identity with the execution, and preserve the exact injected snapshot where
required. Keep credentials out of ordinary prompt records. Permission enforcement
belongs to the runtime and policy mechanisms, not the personality file.
A harness may contribute its own instructions. Distinguish Mosaic's assembled
inputs from a verified model-bound request; a local prompt snapshot alone cannot
establish what an external harness added.
## Current implementation boundary
The container replaces Pi's base prompt with generated context; native development
appends context to Pi's coding prompt. They differ in source selection and policy
enforcement. Reconcile those paths under ACT-1 after demo validation. Do not remove
POC startup-marker behavior before replacement verification fixtures are tested.
See [context inspection](context.md), [runtime ownership](agent-runtimes.md), and
[the migration plan](../plans/2026-09-07_agent-context-templates-and-migration.md).
@@ -0,0 +1,256 @@
# Atomic Mosaic Foundation Plan
**Date:** 2026-09-02
**Status:** Planning; configuration-driven L0 not yet implemented
**Project:** Standalone Mosaic Stack rebuild experiment
## Purpose
Reimplement Mosaic Stack from atomic, independently verifiable layers. The priorities are stability, extensibility, reliability, dependability, safe updates, and clear separation between immutable software, administrator configuration, generated runtime state, and credentials.
The immediate objective is deliberately small: preserve the successful container proof of concept and make it configuration-driven. Mission/task abstraction comes only after the foundation is stable.
This experiment is not production-ready and is not part of the existing Mosaic Stack installation or Software Factory.
## Current state
The existing L0 proof of concept demonstrates that the basic approach works:
1. One container image builds successfully.
2. It runs as a non-root user.
3. It contains a pinned Pi installation (`@earendil-works/[email protected]`).
4. It loads four local contract files in a deterministic order.
5. It generates `/var/lib/mosaic/system-prompt.md`.
6. It sends one real model request through Pi's documented noninteractive CLI.
7. The request does not contain the expected marker.
8. The model returns exactly `MOSAIC_HELLO_OK`.
9. Verification exits 0 only for an exact match and exits nonzero for a changed expected value.
10. Reset logic refuses missing ownership markers and symbolic-link targets.
11. Resetting and rerunning produces the same successful result.
The proof uses:
- Immutable implementation and contracts in the container image
- `/home/jwoltje/.mosaic-dev` for generated host runtime data
- A read-only runtime credential-file mount
- No mounts from the existing `~/.mosaic` or `~/.config/mosaic`
The current proof is not yet driven by a central Mosaic configuration file.
## Problem being addressed
The existing `~/.mosaic` and `~/.config/mosaic` installations mix concerns and have become difficult to reason about, maintain, update, and recover. The rebuild must avoid repeating that design.
Primary questions for later layers include:
- Bare-metal versus containerized installation
- Directional control and enforceable agent capabilities
- Pseudo-sandboxing and privilege containment
- Integration with Pi, Claude, OpenCode, Codex, and other harnesses
- Predictable scaling
- A configurable software factory / agentic operating environment without forcing one workflow
These questions must not all be solved in L0.
## Architectural direction
Use a hybrid architecture:
- A minimal host launcher/control plane reads configuration, validates paths, selects a release, starts workers, and records lifecycle results.
- Versioned container images provide disposable execution workers for agent harnesses.
- Agent execution does not occur directly in the host control plane.
- Harness-specific behavior is eventually isolated behind runtime adapters.
Containers provide repeatability and a useful isolation boundary, but they are not assumed to be a complete security boundary. Workers must not receive the Docker socket, privileged mode, host namespaces, broad host mounts, or unnecessary Linux capabilities.
## Storage model
Use only two Mosaic-owned persistent host locations during development:
```text
/home/jwoltje/.config/mosaic-dev/config.json
/home/jwoltje/.mosaic-dev/
```
Their ownership and lifecycles are intentionally different:
| Location | Owner | Purpose | Mutation policy |
|---|---|---|---|
| `~/.config/mosaic-dev/config.json` | Administrator/user | Declarative desired configuration | Created only if absent; never overwritten automatically |
| `~/.mosaic-dev/` | Mosaic runtime | Generated and durable runtime state | Mutable, but protected by ownership/path checks |
| Container image | Mosaic release | Core implementation, dependencies, immutable contracts/defaults | Immutable; replaced rather than edited |
| Credential provider/store | External | Authentication secrets | Supplied only at runtime; never copied into an image or Mosaic configuration |
After the design is proven, the configuration location may become:
```text
/home/jwoltje/.config/mosaic/config.json
```
The existing `~/.mosaic` and `~/.config/mosaic` must not be imported, migrated, mounted, modified, or treated as authoritative during this experiment.
### Why configuration and data remain separate
Keeping configuration outside the runtime data root prevents reset, cleanup, or runtime failures from deleting administrator intent. Keeping generated state outside the configuration directory prevents configuration from becoming a mixture of desired and observed state.
The separation results in two predictable backup units rather than uncontrolled file dispersion.
## Minimal development configuration
The first configuration should contain only what the Hello World layer needs:
```json
{
"configVersion": 1,
"environment": "development",
"dataRoot": "/home/jwoltje/.mosaic-dev",
"execution": {
"backend": "docker",
"provider": "zai",
"model": "glm-5.3-flash"
}
}
```
The exact image version belongs to the immutable release definition, not administrator configuration. Credentials must not appear in this file.
Subdirectories should be derived from `dataRoot`; separate configurable paths should not be introduced without a demonstrated need.
## Configuration invariants
1. `~/.config/mosaic-dev/config.json` is the sole Mosaic discovery entry point during development.
2. Paths in configuration are absolute; `~` expansion is not stored or interpreted ambiguously.
3. Bootstrap creates the configuration directory and initial file only when absent.
4. Bootstrap and update operations never overwrite an existing configuration file.
5. Configuration has an explicit `configVersion`.
6. Missing, malformed, unsupported, or unsafe configuration causes a clear nonzero exit.
7. Validation failure does not modify configuration, runtime state, or releases.
8. Generated and observed values are never written back into `config.json`.
9. Secrets and credential contents are never stored in `config.json`.
10. Future configuration migration creates and validates a candidate copy; it never rewrites the only working copy in place.
The configuration file is declarative. The bootstrap and activation operations around it must be idempotent.
## Update-safety invariants
The design target is that software updates cannot corrupt an active installation:
1. Releases are immutable and versioned.
2. A new release is installed beside existing releases.
3. Active implementation files are never patched in place.
4. Configuration and state are not owned by a release directory.
5. Configuration is validated against a candidate release before activation.
6. Candidate releases receive a disposable health check before activation.
7. Activation is an atomic pointer/reference change.
8. The prior release remains available for rollback.
9. State migrations are deferred until required.
10. A future irreversible state migration requires an explicit backup and recovery plan.
Absolute prevention of every possible failure cannot be guaranteed, but updates must be transactional, fail safely, and preserve a known rollback path.
## Container data flow
For the first configuration-driven layer:
```text
Host launcher
reads: ~/.config/mosaic-dev/config.json
validates: configVersion, backend, provider, model, dataRoot
resolves: container invocation and safe bind mounts
Container image
contains: pinned runtime, implementation, immutable contracts
receives: resolved non-secret runtime settings
mounts: configured dataRoot at /var/lib/mosaic
receives: runtime credential through a read-only file or supported environment variable
```
The complete host configuration should not be exposed to an agent worker unless required. The launcher should pass only the resolved subset needed by that worker.
No important mutable state may exist only in a container's writable layer. Containers must remain disposable.
## Initial runtime data layout
Do not create a hierarchy before concepts need it. L0 requires only:
```text
~/.mosaic-dev/
├── .mosaic-root
└── system-prompt.md
```
Potential future directories are reserved but not part of L0:
```text
~/.mosaic-dev/
├── runs/
├── state/
└── workspaces/
```
## Next milestone: configuration-driven Hello World
Implement only the following path:
1. Create a small bootstrap/launcher.
2. If absent, bootstrap creates `~/.config/mosaic-dev/config.json` with the minimal development configuration.
3. If configuration already exists, bootstrap does not change it.
4. The launcher reads and validates the configuration.
5. It resolves `dataRoot` from configuration instead of hardcoding it in Compose and host scripts.
6. It safely creates or validates the data-root ownership marker.
7. It builds or selects the current immutable container image.
8. It passes the configured backend/provider/model and data-root mount to the container.
9. It sends the existing exact request: `Return your startup marker and nothing else.`
10. It receives and verifies exactly `MOSAIC_HELLO_OK`.
### L0 acceptance criteria
1. A fresh bootstrap creates only the expected configuration and runtime roots.
2. Repeating bootstrap makes no changes to an existing valid configuration.
3. Existing configuration is never overwritten by build, verification, reset, or update operations.
4. Missing configuration can be bootstrapped deliberately; normal execution does not silently invent configuration.
5. Malformed JSON exits nonzero without modifying files.
6. Unsupported `configVersion` exits nonzero without modifying files.
7. A relative or unsafe `dataRoot` exits nonzero without modifying files.
8. The container receives the configured data root at `/var/lib/mosaic`.
9. The image and container contain no credentials.
10. The real model request returns exactly `MOSAIC_HELLO_OK`.
11. Changing the expected marker produces a nonzero verification exit.
12. Rebuilding/replacing the image leaves configuration and runtime data intact.
13. Reset deletes only the validated runtime data root and never configuration.
14. Reset continues to refuse symbolic links and missing ownership markers.
## Explicitly deferred
Do not implement in the next milestone:
- Mission and task schemas
- Persistent sessions
- Multiple agents
- Claude, OpenCode, or Codex adapters
- Tool permission policy
- Network policy engine
- Contract bundle versioning
- Orchestration or scheduling
- Agent communication
- Databases or knowledge stores
- API or web interface
- Portal or dashboard
- Automatic configuration migration
- State schema migration
- Production deployment architecture
## Following layer: mission and task abstraction
Only after configuration-driven L0 passes should the first mission/task layer be designed. Its initial concepts should remain minimal:
- **Mission:** desired outcome and governing constraints
- **Task:** one bounded unit of work assigned to one runtime
- **Run:** one attempt to execute a task
- **Result:** immutable completion evidence and exit status
No mission/task implementation decision is made by this plan.
## Immediate documentation and implementation scope
Maintain one clear architecture plan (this document), one example/default configuration, one strict configuration reader, and the existing Hello World proof. Avoid new services, generalized frameworks, and abstractions until a passing acceptance test requires them.
@@ -0,0 +1,485 @@
# Harness declaration + centralized auth/provider registry
Status: **DRAFT FOR OWNER REVIEW** — no implementation is authorized by this
file. Issue: #49. Date: 2026-09-03.
## Purpose
Specify how Mosaic Stack supports multiple harnesses, providers, endpoints,
and authentication accounts without modifying a user's default harness
configuration or requiring provider registration on every agent seat.
Pi is the first implementation checkpoint. The registry and seat contracts
must remain harness-neutral so future adapters (`claude`, `codex`, `opencode`)
materialize their own native files from the same desired state. Canonical
harness IDs are the executable names so CLI, manifests, diagnostics, and user
expectation stay 1:1.
## Non-negotiable decisions already made
1. Default harness homes (`~/.pi`, and future equivalents) are read-only to
Mosaic. The stack never writes there.
2. Credentials live only under the configured data root, never in the repo,
image, argv, stdout, logs, or generated non-secret manifests.
3. Account registration is centralized. A seat never performs OAuth login and
does not maintain an independent source credential.
4. Seat files are generated artifacts, not user-authored configuration.
5. Missing, stale, invalid, insecure, or ambiguous registry/materialized state
refuses launch. No fallback to a different account.
6. Pi is implemented first; harness-specific behavior stays behind adapters
and materializers.
## Separate concepts (do not conflate)
| Concept | Example | Authority |
|---|---|---|
| Harness | `pi`, later `claude-code`, `codex`, `opencode` | `agent.json` + versioned harness manifest |
| Provider | `openai-codex`, `anthropic`, `zai`, `ollama-local` | central provider registry |
| Account | `openai-codex/homelab-openai` | central account registry |
| Endpoint | local/remote Ollama base URL | provider record |
| Settings profile | reusable account/provider/model policy | central settings registry |
| Seat selection | active choice within a profile's allowed accounts | audited runtime state |
| Materialization | harness-native `auth.json` / `models.json` | generated per seat |
Accounts represent identities. Ollama instances represent endpoints. Harnesses
consume generated configuration. Keeping these axes separate prevents account,
provider, and model-policy conflicts.
## Harness declaration: `agent.json`
A defined seat declares exactly one harness:
```json
{
"agentVersion": 2,
"name": "researcher",
"role": "researcher",
"harness": "pi",
"settingsProfile": "research-default",
"capabilities": { "tools": ["read", "bash"] }
}
```
Rules:
- `harness` is a scalar identifier, not an array. One process launch runs one
harness.
- It is not hard-coded to an enum in the agent schema. The value must resolve
dynamically through the installed/available harness registry.
- Canonical identifiers (owner-confirmed during #50 review) are executable
names: `pi`, `claude`, `codex`, `opencode`. Provider `openai-codex` and
harness `codex` remain distinct.
- No CLI harness override in phase 1. Harness is part of reviewed seat identity;
an override would bypass that declaration.
- Migration: existing `agentVersion: 1` seats without `harness` resolve to `pi`
with a loud deprecation warning. A later reviewed migration makes version 2
and `harness` mandatory.
- Plain M13 launches without an agent definition continue to use the system
execution adapter; they have no seat enrollment or generated seat auth.
### Harness manifests
Harness support is versioned with its adapter, for example:
```text
adapters/pi/harness.json
adapters/claude-code/harness.json
```
A manifest declares its identifier, executable name, adapter, compatible
version range, installer/detector metadata, execution mode, materializers,
native config paths, and supported credential types. Adding harness support
requires a reviewed adapter + manifest + suites. Detecting/installing a known
harness is mutable machine state; changing the manifest/adapter contract is a
reviewed repository change.
### Harness detection, installation, and availability
Target CLI:
```text
mosaic harness list [--available|--installed] [--json]
mosaic harness detect [<id>|--all]
mosaic harness install <id> [--version <exact-compatible-version>]
mosaic harness rm <id>
mosaic harness status [<id>] [--json]
```
Lifecycle and rules:
1. `detect` checks canonical executable names (`pi`, `claude`, `codex`,
`opencode`) on PATH and approved known locations, resolves real paths,
obtains versions using manifest-declared noninteractive commands, validates
compatibility, and records compatible findings as available. Unknown
executables are never auto-registered.
2. Detection records metadata only: executable path, version, source
(`external-detected`), compatibility result, detection timestamp, and
manifest identity. It never reads/copies the harness's home, settings, auth,
sessions, extensions, or plugins.
3. `install <id>` resolves the reviewed catalog/manifest, pins an exact
compatible version, verifies package identity/checksum where supported, and
installs/packages into a Mosaic-managed immutable runtime. It never performs
an unversioned global install and never writes the default harness home.
4. Built-in pi is an image-baked managed harness. Future installs should package
a harness-specific container image/bundle beside the active release so
`reset.sh` does not destroy installed runtimes; dataRoot stores registry
state and receipts, not the package payload.
5. Availability and launch-readiness are separate fields. A compatible detected
host executable is `available`; under the current container boundary it is
not automatically `ready` until imported/installed into a managed runtime,
unless a separately reviewed adapter explicitly supports host execution.
6. `agent.json.harness` launch requires a compatible, ready harness + adapter +
materializer. Detected-but-not-ready, missing, incompatible, or ambiguous
installations refuse loudly; no fallback to pi.
7. Install/detect/remove operations write append-only receipts. Removal refuses
while defined seats reference the harness unless those bindings are migrated
first.
Registry state is derived under `<dataRoot>/harnesses/`; the reviewed catalog
and adapter manifests remain in the installation/repository. This avoids a
hard-coded enum while preventing arbitrary PATH executables from becoming
trusted harnesses.
## Central registry layout
Fixed below `<dataRoot>`; not configurable independently (config.json remains
the sole system config):
```text
<dataRoot>/auth/
├── providers/
│ ├── openai-codex.json
│ ├── anthropic.json
│ ├── zai.json
│ ├── ollama-local.json
│ └── ollama-remote.json
├── accounts/
│ ├── openai-codex/
│ │ ├── homelab-openai/
│ │ │ ├── account.json # non-secret metadata, 0600
│ │ │ └── credential.json # secret OAuth/API material, 0600
│ │ └── personal-openai/
│ │ ├── account.json
│ │ └── credential.json
│ └── zai/
│ └── main/
│ ├── account.json
│ └── credential.json
├── settings/
│ ├── base.json
│ └── research-default.json
└── state/
├── refresh.json # non-secret status only
└── activation-log.jsonl # append-only, no secret material
```
Naming and security:
- Provider/account/profile IDs match `^[a-z0-9][a-z0-9._-]{0,63}$`.
- Display names are metadata; path IDs are explicit or deterministically
slugged and confirmed before creation.
- Account path must be under its registered provider.
- Registry dirs are owner-only; account and credential files are regular,
non-symlink, 0600. Loose perms refuse use.
- `credential.json` schema depends on type but is never returned by list/status.
- Provider removal refuses while accounts/profiles/seats reference it.
- Account removal refuses while profiles/selections reference it unless an
explicit reviewed migration removes those references first.
## Registry records
### Provider
Native provider example:
```json
{
"providerVersion": 1,
"id": "openai-codex",
"kind": "native",
"harnesses": { "pi": { "providerId": "openai-codex" } },
"credentialTypes": ["oauth", "api_key"]
}
```
Custom endpoint example:
```json
{
"providerVersion": 1,
"id": "ollama-local",
"kind": "custom-endpoint",
"harnesses": {
"pi": {
"api": "openai-completions",
"baseUrl": "http://host.docker.internal:11434/v1",
"apiKey": "ollama",
"models": ["qwen2.5-coder:7b", "llama3.1:8b"]
}
},
"credentialTypes": ["none"]
}
```
`ollama-remote` is a second provider record with its remote base URL, optional
credential account, and independently scoped model catalog. Linux compose must
provide host-gateway resolution for local Ollama; container `localhost` is not
the host.
### Account metadata
```json
{
"accountVersion": 1,
"id": "homelab-openai",
"name": "Homelab OpenAI",
"provider": "openai-codex",
"type": "oauth",
"createdAt": "<UTC timestamp>"
}
```
`credential.json` holds the pi-compatible OAuth/API payload centrally. API keys
are accepted only from an interactive hidden prompt, stdin, or a validated
0600 file — never an argv value.
### Reusable settings profile
Profiles remove per-seat/per-provider registration:
```json
{
"settingsVersion": 1,
"id": "research-default",
"allowedAccounts": [
"openai-codex/homelab-openai",
"openai-codex/personal-openai",
"zai/main"
],
"defaultAccounts": {
"openai-codex": "openai-codex/homelab-openai",
"zai": "zai/main"
},
"providers": ["ollama-local", "ollama-remote"],
"models": {
"ollama-local": ["qwen2.5-coder:7b"],
"ollama-remote": ["abliterated-model-id"]
}
}
```
A seat references one profile. Editing one central profile updates every seat
that uses it on the next ensure/launch. The seat is not re-registered with each
provider.
A profile may authorize multiple accounts for the same provider. Pi can place
only one entry per provider in one `auth.json`; therefore exactly one account
per provider is active for a materialization. Defaults provide the initial
selection.
## Runtime seat selection
At-will changes are mutable state, not repo edits:
```text
<dataRoot>/agents/<seat>/settings-selection.json
```
Example:
```json
{
"selectionVersion": 1,
"profile": "research-default",
"accounts": {
"openai-codex": "openai-codex/personal-openai"
},
"updatedAt": "<UTC timestamp>"
}
```
Rules:
- Selection can choose only accounts allowed by the seat's referenced profile.
- Omitted provider selections use profile defaults.
- Changing selection is append-only-audited and triggers regeneration.
- No account choice silently falls back to another account.
- In-session identity swapping is deferred. Selection normally applies on
relaunch/new session; changing identity inside a session risks ambiguous
billing, provider state, and audit lineage.
## Mechanical per-seat materialization
Generated state:
```text
<dataRoot>/agents/<seat>/generated/pi/
├── auth.json # secret, 0600, generated
├── models.json # generated custom providers/model scopes
└── manifest.json # non-secret inputs/hashes/timestamps; no credential hash
```
Resolution on `agent.sh <seat>`:
1. Strictly validate agent definition and resolve its harness manifest.
2. Resolve role, settings profile, runtime selection, providers, accounts, and
model scopes.
3. Validate every referenced record, regular-file constraint, ownership, and
0600 credential perms.
4. Refresh required centralized OAuth records if policy says stale/near expiry;
refusal leaves prior generated files untouched.
5. Generate beside existing files, validate harness-native output, chmod 0600,
then atomically rename into place.
6. Compare/write non-secret manifest state and append an activation receipt.
7. Mount generated files into the harness's native paths read-only and launch.
No seat authenticates, edits auth.json, or owns an independent OAuth refresh
token. Generated files are disposable derivatives of the registry.
`mosaic auth ensure [<seat>]` runs the same materializer explicitly. A periodic
host service refreshes central OAuth records and rematerializes affected seats;
launch-time ensure is the final fail-closed gate. OAuth refresh mechanics must
reuse pi's implementation where possible rather than reimplement provider
protocols; the exact noninteractive refresh trigger is an implementation
investigation and acceptance gate.
## CLI contract (target `mosaic` surface)
```text
mosaic auth list [--provider <id>] [--json]
mosaic auth status [<account-ref>|--seat <seat>] [--json]
mosaic auth new
mosaic auth new --name "Homelab OpenAI" --provider openai-codex --type oauth
mosaic auth rm <provider/account>
mosaic auth login <provider/account>
mosaic auth logout <provider/account>
mosaic auth refresh [<provider/account>|--all]
mosaic auth ensure [<seat>|--all]
mosaic auth provider list [--json]
mosaic auth provider status <id> [--json]
mosaic auth provider create
mosaic auth provider create --id ollama-local --kind custom-endpoint ...
mosaic auth provider rm <id>
mosaic harness list|detect|install|rm|status ...
mosaic agent settings use <seat> <profile>
mosaic agent auth use <seat> <provider/account>
mosaic agent auth status <seat>
```
Behavior:
- `new` without sufficient flags launches an interactive wizard.
- OAuth `new/login` launches the host-side provider flow once and stores the
resulting centralized profile. Agents never run it.
- API secret input is hidden prompt/stdin/validated file only.
- `rm` reports references and refuses when in use.
- Every mutating command writes an append-only secret-free receipt.
- `list/status --json` never include credential/token/key fields.
- CLI flags name accounts/providers; no secret material is accepted on argv.
Until M20 provides `mosaic`, `scripts/auth.sh` may prototype the backend, but it
must use the same schemas and must not become a competing implementation.
## Harness-neutral materializers
Core resolution produces a secret-bearing internal representation in memory:
selected providers/accounts/models for one seat. A harness materializer maps it
to native files:
- Pi: `auth.json` + `models.json`.
- Claude Code/Codex/OpenCode: future adapter-defined files/env, without changing
account/provider/profile schemas.
The harness manifest declares support. Enrollment or launch refuses if a chosen
account/provider cannot materialize for the seat's harness. No best-effort
provider dropping.
## Policy relationships (future phase, not initial implementation)
Initial authorization is settings-profile enrollment. Later, the existing
least-privilege doctrine extends naturally:
```text
role auth ceiling ∩ profile enrollment ∩ mission grant ∩ task grant
= effective providers/accounts/models
```
A task may narrow a seat's providers/models, never select an account outside
its profile. This is separate from registry and materialization correctness.
## Migration from M19 prototype
No named accounts currently exist, so migration is state-free:
1. Keep `~/.pi/agent/auth.json` as the default harness file, read-only to Mosaic.
2. Replace the prototype `<dataRoot>/auth/<account>.json` convention with the
registry tree before users create accounts.
3. Keep `scripts/auth.sh status/accounts` behavior but drive it from registry
schemas.
4. Replace `agent.sh --auth <account>` direct-file selection with profile +
runtime selection + materialization. A temporary compatibility path may map
`--auth provider/account` to a one-launch selection, but must be explicit and
audited.
5. Add `harness: "pi"` + `settingsProfile` to researcher through an agentVersion
migration after review.
## Required suites / acceptance
- Registry schemas: unknown keys, path/name mismatch, symlinks, traversal,
wrong perms, duplicate IDs, missing provider, unsupported credential type.
- Secret non-disclosure: fixture keys/tokens never appear in stdout, stderr,
JSON status, manifests, receipts, or git diff.
- Referential integrity: provider/account/profile removal refuses while used.
- Multiple account types for one provider register successfully.
- Profile authorizes multiple same-provider accounts; exactly one active choice
materializes for pi.
- Changing runtime choice regenerates atomically; failed generation preserves
previous files and records refusal.
- OAuth central refresh rematerializes affected seats; agents never authenticate.
- `harness: pi` resolves; unregistered/unsupported harness refuses.
- Harness detection records recognized compatible executables without reading
harness homes; incompatible/ambiguous detections refuse availability.
- Harness install is exact-version/verified, Mosaic-managed, and never global;
detected-but-not-ready harnesses cannot launch seats.
- Pi materialization generates valid auth.json/models.json with scoped providers
and models; custom local/remote Ollama entries remain distinct.
- Launch refuses missing/stale/invalid materialization; no account fallback.
- Existing generic TUI and default-harness use remain untouched.
- Full existing suites + verify green.
## Review gates (must resolve before implementation)
1. **RESOLVED (owner, #50):** canonical harness IDs are executable names:
`pi`, `claude`, `codex`, `opencode`; validation is registry/manifest-driven,
not a schema enum. `mosaic harness detect/install/list/rm/status` owns the
lifecycle. Remaining seam to approve: detected host executables are
available but not launch-ready under the container boundary until
imported/installed into a managed runtime (unless host execution receives a
separate reviewed adapter).
2. Confirm `agentVersion: 2` migration and whether `settingsProfile` becomes
mandatory for defined seats.
3. Confirm registry path/schema split (`account.json` metadata +
`credential.json` secret) versus one encrypted/combined file.
4. Decide encryption-at-rest requirement. Filesystem 0600 is specified now;
external keyring/envelope encryption would change login/refresh design.
5. Confirm selection scope: seat-global only initially, or named launch profiles
(e.g. `work`, `personal`) as a first-class layer.
6. Confirm whether a seat may switch same-provider account on relaunch only, or
whether forked sessions must pin the original account in immutable session
metadata.
7. Determine pi's supported host-side noninteractive OAuth refresh trigger;
implementation must prove refresh without exposing or duplicating tokens.
8. Confirm local Ollama container routing (`host-gateway`) and remote Ollama
transport/auth requirements.
9. Decide whether M20 `packages/mosaic` begins with this auth/provider/harness
domain or whether scripts prototype it first. Confirm managed harness
packaging: harness-specific container image/bundle survives dataRoot reset;
registry state and receipts remain under dataRoot.
10. Define backup/reset semantics for the central registry. Current reset wipes
the data root; OAuth re-login cost may justify a separately protected
registry root, but that would require an explicit canon change.
Implementation is blocked until these gates are reviewed and owner-approved.
+81
View File
@@ -0,0 +1,81 @@
# Autonomous Work Run — 2026-09-03
**Status:** COMPLETED (single-session batch; see Results at bottom)
**Constraint:** The assistant cannot run unattended. This was one long interactive session, not 12 wall-clock hours. Everything below was completed, committed, and pushed during that session.
## Objective
Advance the Mosaic Stack rebuild several verified layers in one batch, focused on Pi, ending in a state the owner can test and review alone: green suites, activated release, recorded drills, and this document as the single entry point.
## Scope decided for this run
| Milestone | Theme | Status |
|---|---|---|
| M5 | Task workspaces + capability envelope (tools allowlist) | DONE |
| M6 | Named sessions — persistence and resume (L1) | DONE |
| M7 | Operator ergonomics: run inspection commands | DONE |
| — | Releases 0.0.5+0.0.6 packaged; 0.0.6 health-gated activated | DONE |
Explicitly deferred (do not mistake for forgotten):
- Claude/Codex/OpenCode adapters (owner: focus on Pi for now)
- Network policy engine (container boundary is the current control)
- Fine-grained read restrictions (excluded by the original brief)
- Config/state migrations (no schema breaks so far; keep it that way)
## Design decisions taken during this run
1. **Workspace** (`task.workspace`, optional):
- absent → tool-free text-only run (previous behavior, unchanged)
- `":run"` → ephemeral per-run workspace at `<dataRoot>/runs/<runId>/workspace`
- named (validated id) → persistent shared workspace at `<dataRoot>/workspaces/<name>`
- Container path passed via `MOSAIC_WORKSPACE` env; adapter cds into it. No new mounts (dataRoot is already mounted).
2. **Capabilities** (`task.capabilities.tools`, optional): allowlist from pi's documented tool set (`read write edit bash grep find ls`). Absent → `--no-tools` (previous behavior). Passed via `MOSAIC_TOOLS` env; pi adapter maps to `--tools`.
3. **Adapter diagnostics for deterministic testing**: the mock adapter writes all received `MOSAIC_*` variables (never secrets — auth is not MOSAIC_-prefixed) to stderr, which lands in the run record. This lets selftests assert orchestrator→adapter plumbing without parsing model output.
4. **Sessions** (`task.session`, optional named): persisted under `<dataRoot>/sessions/<name>/` via pi's documented `--session-dir`; resume semantics: continue most recent session in that directory when one exists (`-c`).
5. **Selection authority unchanged**: config file for adapter/provider/model; task file for workspace/capabilities/session; env vars are internal plumbing only.
6. **configVersion stays 1**; all new task fields are optional. Old tasks/configs remain valid.
## Test plan (what "done" means per milestone)
- M5: mock-adapter cases asserting workspace path and tools arrive via run-record stderr; live pi case writing/reading a file in a persistent workspace; validation negatives (bad tool name, bad workspace name)
- M6: session directory deterministically populated after first run; second run resumes (continuation asserted by session dir state and, in live E2E, by model recall); sandbox isolation between two named sessions
- M7: `show <runId>` prints a complete run record; `list` gains workspace/session columns
- Final: full sweep (config/task/release), verify, package + activate 0.0.6, config checksum unchanged
## Review checklist for the owner
1. `cat docs/plans/2026-09-03_autonomous-run.md` (this file)
2. `scripts/release.sh status` → 0.0.6 active
3. `scripts/test-config.sh && scripts/test-task.sh && scripts/test-release.sh && scripts/verify.sh`
4. Try a workspace task:
```bash
scripts/run-task.sh run tasks/workspace-demo.json
ls ~/.mosaic-dev/workspaces/demo/
```
5. Try the session demo:
```bash
scripts/run-task.sh run tasks/session-demo-1.json # teaches a word
scripts/run-task.sh run tasks/session-demo-2.json # recalls it
```
6. Inspect any run: `node scripts/mosaic-task.mjs show <runId>`
7. Gitea: milestones M5/M6/M7 closed; issues referenced by merge commits
## Results
- M5 merged on `main` (merge commit `ddb1554`), tagged `workspace-capabilities-v1`
- M6 merged on `main` (merge commit `4e2a413`), tagged `sessions-v1`
- M7 merged on `main`, tagged `operator-ergonomics-v1`
- Release 0.0.6 packaged, health-gated activated, full sweep green
- Suites at end of run: config 24/24, task 32/32, release 14/14, verify PASS
- Build log: Phases 9 (M5), 10 (M6), 11 (M7) appended with corrections
- Corrections encountered: dropped constant from a failed atomic edit batch (SUPPORTED_TOOLS); dash `export` output format vs `env`; three selftest authoring defects; showRun id-regex case sensitivity + missing-run crash. All fixed and covered by tests.
- Commits pushed incrementally; nothing left uncommitted
- Live proof: workspace file host-visible; session teach/recall ('mosaico') verified
## Next steps after this run (not started)
1. Owner review + hands-on testing of workspaces, capabilities, sessions
2. Decision: capability defaults per mission (mission-level policy) — natural M8
3. Second real adapter remains available whenever wanted
4. Consider run-record pruning/retention policy once run volume grows
5. Consider a `mosaic-task.mjs retry <runId>` convenience for failed runs
@@ -0,0 +1,339 @@
# Agent, project, and workspace foundation
Status: documentation draft for Jason's review. No implementation authorized.
Date: 2026-09-06. Plan author: darkwing, current pi session
`01a06e48-0718-71f2-a889-c263c4800fb9`.
Baseline: `69d1bb3aa4b826218aa4cca3710f2d98c0b9d7ba` in `mosaicstack/stack-v2`.
Issue: [#53](https://git.mosaicstack.dev/mosaicstack/stack-v2/issues/53),
opened by rocko under the authorized seat identity, POST 201 reported at
2026-09-06T00:12:33Z.
Start here for the intended behavior. Field proposals and execution evidence
are in [Schema and audit discussion](2026-09-06_workspace-schema-and-audit.md).
Phase-2 detail is now in the
[Record and operation contract candidate](2026-09-06_foundation-phase2-contract.md).
These documents are not implemented APIs or approved JSON Schemas.
## 1. Why we are doing this
Jason wants one familiar interaction agent to work in several separate areas
without creating another agent identity for each assignment. Workers should
also reuse their definitions across projects and workspaces.
A fresh conversation must be able to continue checked work without inheriting
an old conversation's confusion. Files and work records, not conversation
memory alone, tell the agent what to do next.
Development must remain understandable and testable by Jason. We agree on a
small result and its test, build only that result after approval, then stop
for Jason to try it. Passing automated tests is not user acceptance.
## 2. Requirements agreed in the owner conversation
These describe intent. Exact field names, storage paths, and mechanisms below
remain proposals unless separately approved.
| ID | Requirement |
|---|---|
| R1 | An agent has a reusable identity, type, execution program, SOUL, configuration, and permission limits. Creating an assignment need not create another agent identity. |
| R2 | A project registers participating agents and controls their visibility and permitted work through role-based access control, RBAC. Owner round 1, Q2: the user may delegate bounded registration/assignment authority to the system; actions outside those limits require approval. |
| R3 | A project contains N workspaces; each workspace has exactly one parent project, owner Q5. Each workspace registers participating agents and keeps its work separate. Owner Q3: project membership permits shared project information and only explicitly permitted workspaces, not all workspace content. Dependencies are references, not additional parents. |
| R4 | The same agent can participate in many projects and workspaces, with separate working sessions. One definition per worker type may suffice; specialized definitions remain possible. |
| R5 | Launch names the agent, project, and workspace. Those choices determine mission, tasks, state, working paths, and message destination. The agent does not guess its assignment from its seat directory. |
| R6 | Shared instructions and skill definitions may be reused. Work-specific information points to the declared workspace and relevant project records, not a global agent task list. |
| R7 | Resume and Fresh are distinct launch operations. Resume is the default. Owner Q4 clarification: if no conversation has ever existed in the declared agent/project/workspace scope, initial launch creates it and announces this without an offer. Missing or damaged established conversations are errors, not first use. Fresh does not automatically import the old conversation or its automatic summary. |
| R8 | A fresh conversation supports continuing, abandoning, or selecting established work, independently of conversation choice. Owner Q8: Abandon defaults to ending this agent's selected assignments, not cancelling the shared mission/tasks. Owner Q9: an unfinished prerequisite requires an explicit authorized assignment change, never silent expansion or proceeding as if it were complete. |
| R9 | Users and authorized system services can launch or relaunch an agent with fresh context. Mission recovery uses saved work records. |
| R10 | Default concurrency is one active working session per agent/project/workspace. Users can tune concurrency; budgets, usage measurement, and automatic scaling are requirements for later phases. |
| R11 | Terminal, desktop, and web interfaces refer to the same workspace work records. They show the active agent/project/workspace and do not keep competing task lists. |
| R12 | Messages must address the appropriate workspace. Identity reuse must not mix conversations or work across projects. |
| R13 | Separate folders provide logical organization, not a demonstrated security sandbox. Security enforcement needs its own evidence. |
| R14 | Execution records retain the reusable agent identity and identify the particular assignment/session that acted. Capture context through the system, not agent self-description. Owner Q13: concise action records name actor/scope/session/task, operation, target, authorization, outcome, and evidence references. Detailed evidence is stored separately with access controls; no credentials in either. |
| R15 | Jason approves progression between phases and tests implementation increments before later work depends on them. No automatic queue-running. |
| R16 | Owner ruling, 2026-09-06: SOUL remains canonical per agent. Each Resume or Fresh launch loads the current approved revision into execution-specific inputs and records it. Running conversations do not silently reload changed instructions. Recording the revision does not permanently pin a workspace to it. |
| R17 | Owner Q20/Q21 extends the launch fingerprint to shared behavior-affecting agent configuration: SOUL, shared instructions, applicable enabled skill versions, harness/model settings, and role configuration. Exclude credentials, chat, and task progress. TUI/GUI/WUI show automatic non-blocking mismatch notices recommending Fresh and support on-demand checks against the same record, without repeated interruption or automatic restart. |
| R18 | Owner Q1/Q5: missions may exist at project and workspace levels, with explicit single-parent relationships. A workspace mission has at most one parent project mission, or stands alone within its owning workspace/project scope. Each mission has one owner and authoritative record. A parent may have many children; dependencies are not extra parents. |
| R19 | Owner Q7: delegated, authorized agents may make non-destructive, goal-directed decisions and decompose work autonomously within the established plan. Routine tasks may be accepted by an authorized independent reviewer against agreed criteria. No user interaction is needed for each within-plan decision; scope deviations and declared owner checkpoints still require approval. |
| R20 | Owner Q10: conversation and permitted workspace inspection may precede a mission/task. Requests to change things become explicit recorded assignments without requiring lengthy mission setup. Scope and permissions still apply. |
| R21 | Owner Q11/Q15: Resume targeting an already-running session reports the conflict and offers connection to a user. A service receives an already-active result with the execution identity and must explicitly request connection or another authorized operation. No automatic connection, replacement, or duplicate launch. |
| R22 | Owner Q12: agents share permitted workspace missions, tasks, decisions, and evidence. Reading another agent's conversation needs separate permission or an explicitly authorized handoff. Read permission never implies automatic conversation loading. |
| R23 | Owner Q14: workspace membership removal stops affected executions and blocks further actions in that workspace. Independently authorized work elsewhere continues. Global agent disable is separate; already-issued actions need reconciliation, not an assumption that stopping undoes them. |
| R24 | Owner Q16: one controlling interface connection per running session initially. Other authorized connections may observe; transfer of control is explicit. Observation requires conversation access permission. |
| R25 | Owner Q17: Fresh during active work requests controlled replacement. Stop admitting new actions, preserve checked state, settle or identify unfinished actions, then launch Fresh only when safe. If safe replacement cannot be established, stop and report rather than start competing work. |
| R26 | Owner Q18: an authorized agent/service may investigate uncertain results with non-destructive checks and resume dependent work only after evidence resolves the uncertainty. Otherwise escalate. Blindly repeating the original action is not a recovery check. |
| R27 | Owner Q19: required audit-recording failure blocks affected executions. Other work continues only if its required recording works. Refuse before an affected action; treat effects with missing result evidence as uncertain and reconcile them. |
| R28 | Owner Q22: only explicitly designated general user preferences are shared by default. Personal and project-specific information is supplied only where authorized and relevant, not by automatically loading the whole user profile into every launch. |
| R29 | Owner Q23: closing a workspace retires it from active work, safely stops its work, retains files/history, and blocks ordinary new launches until reopened. Deletion and retention cleanup are separate authorized operations. |
| R30 | Owner Q24: existing sessions remain legacy records until explicitly adopted through reviewed project/workspace assignment. No inferred membership from filenames and no automatic default-project placement. Preserve historical evidence. |
| R31 | Owner Q25: an approved plan change pauses affected work for reconciliation. An authorized coordinator may adjust assignments within its delegation; unaffected work can continue. Account for actions already underway. Escalate adjustments beyond delegated authority or that cannot be resolved safely; do not finish obsolete assignments merely because they started earlier. |
| R32 | Owner Q26: use standard scope permission roles with registration-specific narrowing. A project/workspace role describes what the agent may do there; it does not redefine its reusable identity/type or exceed its permission ceiling. Exact role names and grant lists require review. |
| R33 | Owner Q27 A: initial managed commands require invocation-level evidence: actor/scope/assignment, authorized command, enforced filesystem/network limits, start/end, outcome, and controlled evidence references. Separate tracing of every internal file/network operation is not required. Isolation, credential protection, fail-closed recording, revocation, and uncertainty recovery remain mandatory. |
| R34 | Owner Q28 A: the initial managed terminal may be Mosaic-controlled with Pi remaining the engine; native Pi screen/shortcut parity is not required. Clients use the same mediated operations, with no waiver of scope, permission, recording, privacy or recovery requirements. This is not runtime implementation approval. |
## 3. The objects in plain language
```text
Shared agent definition: darkwing
Identity, type, Pi configuration, SOUL, permission limits
Project: mosaic-stack-v2
Registered agents: darkwing, code-be-01, rev-code-01
Project decisions and shared project state
|
+-- Workspace: sessions
| Registered agents and assignments
| Workspace mission, tasks, files, state
| Darkwing conversations and execution records
| Code-be-01 conversations and execution records
|
+-- Workspace: skills
Separate assignments, files, state, and conversations
Project: personal
Workspace: journal
Darkwing registered here too, with separate work context
```
Agents are referenced by projects and workspaces; they are not copied into
new identities for each registration. A session belongs to one
agent/project/workspace combination. A running instance is one execution
attempt using that session. Resuming a conversation creates another execution
attempt, not another agent identity.
Owner Q5 distinguishes containment from dependencies: one project owns each
workspace; a workspace mission may contribute to at most one project mission.
A standalone workspace mission still belongs to its workspace and project.
Dependency references do not create another parent or confer its permissions.
Proposed ownership rule: project-wide decisions have one authoritative home.
Workspace records reference the relevant project revision rather than copy it
into an independently editable project state. Individual assignments stay
separate even when several agents share a workspace.
## 4. Resume, Fresh, and the work decision
| Conversation operation | Work decision | Intended result |
|---|---|---|
| Resume | Continue | Reopen the selected conversation and reconcile it with current authorized work state. |
| Fresh | Continue | New conversation; load checked mission, task status, and relevant evidence. Do not import the old chat. |
| Fresh | Abandon | End this agent's selected assignments and start a new conversation. Underlying missions/tasks remain for reassignment unless separately cancelled with authority. Preserve history and files. |
| Fresh | Select | Start with explicitly selected work and relevant dependency information. An unfinished prerequisite needs an authorized assignment change before execution. Unselected work is not silently cancelled. |
Owner Q4 ruling: first use creates the initial conversation automatically and
announces "Starting initial conversation" without a confirmation offer. This
is not replacement of existing context or a third public launch command.
Subsequent default launches resume. Owner Q11: if that conversation is already
running, report the conflict and offer to connect rather than automatically
connecting or launching another process. Owner Q15: a service receives an
already-active result with the execution identity and chooses any next
connection or other operation explicitly under its authority.
A missing/damaged established conversation is an error, not permission to
create a replacement. Ambiguity still refuses.
Owner Q10: initial conversation and permitted inspection do not require a
mission/task. Requests to make changes become recorded assignments within the
user's or service's authority. No mission setup ceremony is required for chat.
Owner Q6 approved Fresh/Continue loading the assigned mission and success
criteria, assigned tasks/status, relevant approved decisions, dependencies,
blockers, and verified-result references. A proposed next step is labelled as
a proposal, not an authorized assignment. Old chat/automatic summaries stay
out; unverified notes remain labelled as unverified.
Owner Q7/Q9: an explicit assignment change need not require Jason personally.
An authorized coordinator may approve prerequisite work within delegated plan
limits and record the change. The worker never silently expands its assignment.
This autonomy does not waive the owner-controlled phase boundaries below.
Owner Q16/Q17: an existing execution has one controlling interface connection;
authorized observers do not become additional controllers. A Fresh request
initiates controlled replacement, stopping new actions and preserving checked
state. Unfinished actions must be resolved or explicitly identified before
replacement; if safety is uncertain, stop and report. Do not copy old chat
into the new session as a substitute for checked work state.
Proposals for review:
- Abandon and Select are not valid with Resume in the first version. Keeping
the old conversation would retain the context the user meant to set aside.
- If the caller lacks access to a required dependency, the system refuses
rather than silently omitting it.
- A relaunch first resolves the old execution's status. Starting Fresh is
not permission to leave two sessions claiming the same assignment.
- A fresh launch does not delete files or cleanse incorrect files. Retained
artifacts must be distinguished from active work records.
These rules need lifecycle and failure details before implementation. See
[open decisions](2026-09-06_workspace-schema-and-audit.md#7-open-decisions).
## 5. What the owner has tested so far
On 2026-09-05 Jason launched the existing `researcher` definition with
workspace and session name `owner-checkpoint-1`, allowing only `read,ls`.
The supplied terminal output showed Pi 0.84.4, a successful model response,
and an empty `/var/lib/mosaic/workspaces/owner-checkpoint-1` listing through
`ls`. After exiting and relaunching, the agent recalled a phrase from the
conversation without a tool call.
This demonstrates the observed launch/list/resume path. It does not prove
cross-workspace access controls, long-conversation correctness, execution
auditing, Fresh mission recovery, or any project model. Fresh was discussed
but not demonstrated. This plan does not infer broad user acceptance from
the earlier test or mark unfinished tests as passed.
## 6. Current code references, not future guarantees
All source references below are at the baseline commit named above. These
are starting points for the later independent analysis, not its verdict.
| Current behavior | Source |
|---|---|
| On every launch, agent defaults and role are read from a definition and current seat SOUL is copied to a shared per-agent runtime location. This is not a one-time copy or a live update to already-running instructions. | `scripts/agent.sh:78-130` |
| Session defaults to `agent-<name>` under the global sessions directory, independently of workspace selection. | `scripts/agent.sh:132-138` |
| Role tools narrow requested tools; enabled skill definitions are resolved and passed as container paths. | `scripts/agent.sh:140-179` |
| Interactive mission is copied to `agent-missions/<agent>.json`; workspace is a separately selected folder; Compose starts the container. | `scripts/agent.sh:181-199` |
| Pi changes working directory, uses the named session directory, and adds `-c` when it is nonempty. | `adapters/pi/adapter.sh:23-50` |
| The whole data root is mounted at `/var/lib/mosaic`, not just the selected workspace. | `compose.yaml:39-43` |
| Dispatcher generates one shared system-prompt path; the loader also uses one `.partial` path and injects global user Markdown. | `src/run-agent.sh:37-41`; `src/load-contracts.sh:40-41,68-77,97` |
| Headless tasks get exclusive input snapshots and a final result containing workspace, session, tools, model, and timing. | `scripts/mosaic-task.mjs:295-325,442-466` |
The per-agent mission/SOUL paths and shared prompt paths require explicit
concurrency analysis. Do not claim that independent workspace names already
prevent launch-time context mix-ups. No race reproduction has been performed
in this documentation phase.
## 7. Scope and existing plans
Authorized now: these two planning documents, CURRENT.md routing, issue
intake, and append-only build/session history. No runtime schema files,
implementation, new agent definitions, credential changes, migration,
release activation, map generation, or skill integration.
The [auth/provider plan](2026-09-03_auth-provider-harness-registry.md), issue
#50, stays paused. Its seat-global selection and generated per-seat files
must be reconciled with concurrent workspace sessions before implementation.
OAuth refresh gate 7 is still unresolved; this plan does not approve it.
[ROADMAP.md](ROADMAP.md) supplies earlier direction, not authority to skip
this exercise. The old registry-first maps are historical candidates, not a
complete plan for this object model. Issue #51 and untracked skills remain
separate work. No source is moved into `packages/` in this phase.
Keep the repository canon: sole system config, no new root files, no secrets
in documents/images, reviewed role authority, and write-once run evidence
under `<dataRoot>/runs/`. Runtime project/workspace records must not become a
second system configuration or a way to grant themselves wider authority.
## 8. Phases, each stopped for owner review
| Phase | Output | Required stop |
|---|---|---|
| 1. Document, behavior confirmed | Agreed requirements, proposed record shapes, initial source references, audit limitations, and open decisions. | Jason confirmed the behavior summary on 2026-09-06 and subsequently authorized phase 2. |
| 2. Resolve details, current | Exact object relationships, schemas, permissions, command behavior, state ownership, and action-record guarantees, with read-only investigation. | Q27 selects invocation-level command evidence with enforced limits. Complete the remaining details, then Jason approves a specific plan revision for mapping. |
| 3. Map with Archify | Separate current/planned diagrams and a ledger tracing launch, state, permissions, messaging, and execution receipts. | Independent map review plus Jason's visual acceptance. |
| 4. Independent gap analysis | A distinct non-authoring agent compares the pinned implementation and agreed plan; reports missing, conflicting, or unsupported connections. | A different agent reviews the report; Jason decides each finding and whether another plan/map revision is needed. |
| 5. Plan one implementation increment | Named author/reviewer, allowed paths, acceptance test, rollback, and protected operations. | Jason authorizes that one increment. |
| 6. Implement and user-test | Local suites, independent candidate review, verified release if needed, and a short test Jason performs. | Jason explicitly accepts before another increment begins. |
Documentation, the decision interview, and phase-2 detailed design/read-only
investigation are authorized. Individual behavior rulings and documentation
completion are not full schema approval. A diagram passing rendering checks is not evidence
that its claims are correct. No review request is implied by naming a future
reviewer.
Roles: Jason owns scope, decisions, and acceptance. Darkwing authors this
plan and coordinates. Proposed later map author: rocko. Proposed later
independent reviewer/gap analyst: filbert, if available and not a design or
map author. Proposed peer reviewer of the gap report: ms-test, if independent
of its authorship. Confirm assignments at the phase boundary. If an independent
seat is unavailable, report blocked; the author does not take its place.
## 9. Evidence required in later phases
Archify work must cite code and plan lines at named commits, separate facts
from proposals and external measurements, and bind spec/ledger/HTML hashes.
Use the approved lane preview and browser checks; preserve old verdicts.
A reviewer verifies the exact candidate. The later gap analyst must check
both code and plan, not infer correctness from the diagram or its author.
Unspecified paths and missing enforcement are findings, never invented edges.
Candidate user tests, to agree before implementation:
- Same agent in two workspaces retains different files, tasks, and chats.
- Fresh/Continue remembers an approved task but not an unrecorded chat phrase.
- Resume reopens only the intended workspace's conversation. If already
running, it reports the conflict and offers connection without auto-attach.
- Unassigned conversation and permitted inspection work; a change request
creates an authorized recorded assignment, not untracked mutations.
- Agents can read shared work records but cannot read a colleague's transcript
without a separate grant. Granting access does not auto-load the transcript.
- Change canonical SOUL while two workspaces run: both show a configuration
mismatch, retain their recorded launch inputs, and recommend Fresh. Relaunch
one workspace with the new revision; only that execution becomes current.
Verify each interface uses the same status, and task progress alone does
not produce a base-configuration warning. See R16-R17 and D16.
- Abandon preserves history and does not cancel another agent's work.
- Select includes dependency information, obtains an explicit authorized
assignment change before undertaking unfinished prerequisites, and leaves
unselected work explicit.
- An authorized reviewer accepts a routine task without a user prompt, with
criteria and evidence recorded. A plan deviation or protected action does
not inherit that permission.
- Unauthorized registration, ambiguous addressing, and stale task ownership
refuse without acting in another workspace.
- Removing one workspace membership stops affected work and blocks further
actions there while leaving independently authorized work elsewhere running.
In-flight effects are reconciled rather than declared undone.
- Only one authorized interface controls a session; observers cannot issue
controls and explicit transfer prevents the old controller from continuing.
- Fresh during active work does not overlap unsafe executions. Unknown effects
block dependent work until authorized, non-destructive investigation resolves
them or escalation occurs; no blind replay.
- Audit failure blocks affected work without unnecessarily stopping executions
that can still meet their recording requirements.
- Configuration notices cover shared behavior-affecting inputs, appear without
blocking work, and can be requested on demand without changing execution.
- Synthetic personal-journal context is absent from a coding launch unless
explicitly authorized/relevant; designated shared preferences remain usable.
- Closing a workspace preserves records and blocks new launches until reopening.
Adopting legacy sessions requires explicit reviewed assignment, not name-based
inference, and does not rewrite their historical evidence.
- A changed approved plan pauses affected actions/dependents until reconciled;
delegated reassignment and continued unaffected work retain authority evidence.
- Standard scope roles are narrowed by registration and parent/agent ceilings.
A role label alone cannot grant access to other workspaces or self-acceptance.
- Each managed action can be traced to a session, execution attempt, task,
authority decision, and evidence-backed outcome, including interrupted work.
Later tests must cover strict schemas, cross-project references, path escape,
concurrent launch, shared-file races, stale messages, audit write failures,
and partial external effects. Terminal/desktop/web clients need situational
and accessibility tests when implemented; none are claimed by this draft.
CI runners remain owner-deferred. Local suites plus `verify.sh` are the repo
publication gate, with exact-candidate review and owner phase approval.
No deployment or migration occurs now, so existing operation is unchanged.
Any later migration must preserve old sessions/run records, be explicit about
reset/prune effects, and have an owner-reviewed recovery and rollback test.
## 10. First owner review
Review R1-R33 for faithful intent. The owner authorized the grill-me interview
on 2026-09-06. Rounds 1-6, Q1-Q26, are recorded in the linked discussion.
Q25/Q26 add affected-work reconciliation after approved plan changes and
standard scope permission roles with registration-specific narrowing.
Jason confirmed the behavior summary with "That looks correct" on 2026-09-06,
after Q1-Q26. Shared understanding of intended behavior is confirmed. This is
not whole-schema approval or a claim that every technical branch is resolved.
Jason subsequently answered yes to phase 2: detailed records, permissions,
commands, and audit guarantees with read-only technical investigation. The
linked phase-2 candidate records the first source/document findings and proposed
common types. Jason answered Q27 A: invocation-level command evidence with
enforced limits, without a promise to enumerate every internal effect.
The explicit /goal request resumed phase 2 after reboot recovery. The schema
package now includes checked record/command shapes and semantic-rule proposals.
Jason answered Q28 A, recorded as R34. The owner-review package now reconciles
D1-D16 against the candidate schemas/rules and explicit implementation proof gates.
Phase-2 acceptance remains Jason's decision. No mapping, independent gap review,
implementation, publication, or migration follows automatically.
@@ -0,0 +1,57 @@
# Fleet goal ownership
## Authority and acceptance
Jason confirmed Resume's NG footer and Alt+G, authorized fleet-wide correction, and authorized scoped commits after green suites. Joe/Huey quiet waits are "looking good so far", not final acceptance. Dewey owns this bounded work in mosaic-stack-dev-test. Independent read-only Pi review must approve exact source and deployment pins. CURRENT and the foundation work remain darkwing's.
Outcome: one shared NG implementation at ~/.mosaic/.pi/extensions/goal for existing fleet launch configurations, without duplicate registration. Retain wrapper-guard or mosaic-core role enforcement as configured, and the launcher's unslop hook. Remove the Resume-specific no-discovery override once ordinary discovery works. No automatic restarts, credentials/configuration edits, private-state reads, state migration, policy changes, or push. Local-first deployment remains the operator-authorized workflow; repository CI runners remain deferred, not reported green.
## Measured ownership
41 agent settings paths and 14 role settings paths reference fleet/extensions/goal. They resolve to 24 ordinary settings files; many agent settings are symlinks to role settings. All declared packages lists are empty. Most seat workdirs are the shared brain; Topher and Velma use their seat directories. Topher's extension directory and fifteen role extension links already resolve to shared extensions. Velma still has an ordinary standalone goal tree. The settings template also references fleet/extensions/goal.
Pi 0.85.1 package-manager toResolvedPaths canonicalizes resource paths before deduplication. We will verify this mechanism in the native loader with normal discovery enabled. Replacing settings would needlessly touch 24 files and still leave compatibility entrypoints. Instead, preserve every settings file/link and make the legacy fleet goal path and Velma standalone goal path compatibility symlinks to the shared NG goal. Keep their old ordinary source trees in verified backups. No recursive library forwarding and no separately auto-discovered core entrypoint are introduced.
## State boundary
The shared implementation resolves its NG state directory relative to the selected import path. Native fixture writes proved Pi deduplicates real source paths but Jiti retains the selected alias when resolving imports. At the brain cwd, state is ~/.mosaic/.pi/state/goal. At Topher or Velma's cwd, their project alias selects their own .pi/state/goal. A role cwd behaves the same way. With no project discovery, the legacy compatibility alias selects ~/.mosaic/fleet/state/goal. Every filename remains goal-state.<incarnation>.json. Do not claim all aliases use one state directory.
Legacy agent-home goal state remains untouched. Velma and Topher's existing NG stores remain in place. Source unification does not adopt, resume, delete or migrate existing goals. Existing processes keep their loaded code until the operator safely restarts them. Old goal evidence stays in its original location; do not assume an active goal will transfer. Native fixtures use fresh owned incarnation IDs and clean only their exact fixture files. Live canaries never invoke goal commands or read private state.
The first Velma synthetic fixture failed because it omitted Velma's existing neighboring mosaic-core libraries. The corrected fixture includes those libraries. All shared supporting modules match both installed legacy and Velma libraries byte-for-byte. Pin these dependency trees, wrapper and unslop entrypoints as well as the goal sources, since an alias does not redirect neighboring imports.
## Delivery
1. Pin all three source trees, the current Resume-hotfix launcher, and settings references/link targets. Pin launch.env filesystem identity/change metadata only. Environment files may contain credentials, so the deployment tool must never read or copy their contents. Register an issue and append session/build records.
2. Add an incident-scoped, fail-closed deployment tool and synthetic tests. Default is preflight. Stage two compatibility symlinks outside discovery, then atomically exchange them with the two ordinary trees. Restore the exact original shared launcher as the third change. Check pins before every effect. On failure reverse verified exchanges; on drift refuse rollback. Retain backup receipts and support explicit verified rollback. Never rewrite a run record.
3. Native red control must reproduce the duplicate with ordinary separate trees. Green fixture must load one shared goal through ambient plus explicit aliases, including root, role-linked, Topher-linked, Velma-linked and isolated cwd cases. Preserve configured wrapper/core tool interception plus unslop. Verify NG footer/full recall against identical installed runtime sources. Test hostile drift, symlink/special-file refusals and partial-failure rollback.
4. Independently review exact candidate, hashes, tests, state limits and rollback. Deploy locally only after approval, then rerun actual fleet resource combinations with normal discovery. Do not invoke auth-seeding launchers or restart sessions for testing.
5. Run goal, native, package and five repository suites. Stage only our explicit files, inspect the index, commit with Dewey identity, verify the commit. Do not stage foundation/skill/task dirt or overwrite CURRENT. No push authorized by this commit request.
6. Record Resume acceptance and fleet deployment distinctly. Close #57 for accepted Resume correction when evidence is recorded. Quiet-wait #56 and fleet user acceptance remain open until explicitly accepted.
The first independent review requested removal of launch.env content hashing because environment files can contain credentials. The corrected candidate pins only filesystem metadata and includes tests that reject any attempted environment-content read and detect metadata drift. The rejected manifest remains historical evidence and was never deployed.
## Deployment checkpoint, 2026-09-06 09:02 UTC
Corrected independent review APPROVE: .pi/evidence/goal58/review-v2.log. Deployed the reviewed three-path transaction and verified all after-pins and backups. Legacy fleet goal and Velma goal are now compatibility symlinks to shared NG. The shared launcher is byte-identical to its original pre-workaround version, SHA256 9352feed0acf9d449c26c0556ba00aba1c65ded43d376930a39ff6b5cee21986. No Resume-specific no-discovery override remains.
Reviewed plan SHA256: 43eb7821a484796850e5e9352f51fe8a6cffb2fb9084769070f449b529a47220. Backup and recovery receipt: /home/jwoltje/.mosaic/.pi/goal-backups/goal58-5baaf9ff598644c6ae364b297e3c982a. Its planned.json contains the complete source, dependency and configuration pins. The former plan-v2.json was rejected and never deployed.
Native negative control and six unified fixture cases pass, including owned fixture writes proving the state paths above. Seventeen transaction controls cover rollback, partial interruption, source/config/dependency/mode drift, symlink/special-file refusal, concurrent-writer lock refusal and the environment-content no-read boundary. Post-install native loading passed all 55 actual agent/role settings combinations with their configured wrapper or core tool interception and unslop. No live goal command or model turn ran. Evidence: .pi/evidence/goal58/live-matrix.log. This verifies registration/discovery, not every role-policy decision at runtime.
All 71 goal tests, 18 package controls, native footer/full recall and timed/untimed waiting checks pass. Repository suites pass: config 24, task 90, release 14, conductor 17, auth 15. Whitespace and prose checks pass. Static TypeScript checking and remote CI remain unavailable/deferred, not certified by these tests.
Explicit rollback, only if current source, dependency, configuration and backup pins still match:
```sh
python3 scripts/unify-fleet-goal.py rollback \
--plan /home/jwoltje/.mosaic/.pi/goal-backups/goal58-5baaf9ff598644c6ae364b297e3c982a/planned.json \
--sha256 43eb7821a484796850e5e9352f51fe8a6cffb2fb9084769070f449b529a47220 \
--backup /home/jwoltje/.mosaic/.pi/goal-backups/goal58-5baaf9ff598644c6ae364b297e3c982a
```
Rollback restores both old source trees and the previously accepted Resume workaround. Do not overwrite later concurrent changes to force rollback.
User test: at a safe stopping point, restart another seat through its normal launch command. Expect one NG footer, full /goal and Alt+G recall, no duplicate-tool startup error, and the same configured safeguards. Do not overwrite an existing assignment with a test goal. Old legacy state does not transfer automatically. No session was restarted by Dewey. Resume UX is accepted; fleet rollout is ready for user test and #56 quiet-wait feedback is still preliminary.
Commit scope excludes CURRENT, foundation planning/reviews, unrelated skills/tasks, and the shared logs because they contain other-owner uncommitted entries. Our evidence/acceptance checkpoints are in these owned plans; shared append-only records remain on disk. MS58-DW-1 informational notice to darkwing returned rc=2, submission unconfirmed; no blind resend and no reply requested. Last verified index release remains MS55-DW-3; inspect the index and commit only explicit owned paths. No push authorized. Issue #58. Evidence directory: .pi/evidence/goal58/.
@@ -0,0 +1,46 @@
# Owner-reported durability failure and runtime WAL direction
Date: 2026-09-06. Recorded by darkwing.
Status: owner-reported context and direction for later runtime design; not an
independently investigated incident or an implementation/test receipt.
Jason reports that the separate ~/.mosaic Stack v1 deployment is fragile under
load; agents retain work in memory rather than reliably tracking it in the system,
and agent state does not reliably survive restarts. He directs that agent actions
use WAL for durability and expects tasks, missions, goals, workspaces, projects and
kanban to provide stronger control over system flow.
No investigation/intervention in that deployment is requested or performed.
## Required follow-through in the later runtime charter
- Trusted admission/publisher code enforces write-ahead intent for governed effects.
Compliance must not depend on a model voluntarily writing a note or remembering
to update a task. This builds on the accepted pre-effect recording requirement.
- Specify the durability boundary: flush/sync and acknowledgement ordering, atomic
publication, recovery of incomplete/torn records, writer ownership and failure
behavior. Merely appending to a file or calling something WAL is insufficient.
- Record stable action/request identity, exact scope/assignment/intent revisions,
and recovery evidence without secrets. Keep work records distinct from private
transcripts and enforce their visibility rules.
- Reconstruct selected work and outstanding obligations from authoritative records
after restart. Chat memory and UI caches cannot be the only source of truth.
Kanban and other surfaces must view the same work state, not competing task lists.
- Reconcile uncertain external effects using evidence; WAL does not make arbitrary
external commands exactly-once or justify blind replay after a crash.
- Test crash/restart points around intent, acknowledgement, execution and result;
concurrent writers and load; disk-full/write/sync failures; and loss of a required
audit path. Verify both preserved work and fail-closed admission. Preserve the
separately preauthorized fail-safe stopping path.
## Scope boundary
The accepted map already identifies legacy writeOnce's lack of fsync and the need
for a trusted durable publisher/recovery boundary. This observation sharpens later
runtime acceptance requirements; it does not prove a specific v1 root cause.
The approved inspector charter remains frozen at
19b6721128a627a2032ffdb95ece2d50abe69a8f6d521e9eff8bbdaff22798b6.
FI-ROCKO-3 implements only that offline synthetic inspector. WAL/runtime persistence,
load remediation and live migration are not added to its scope. A synthetic preview
cannot establish these durability guarantees. No worker is retasked by this note.
@@ -0,0 +1,52 @@
# Owner-relayed Jarvis stall: credential scope and evidence handoff
Date: 2026-09-06. Recorded by darkwing.
Status: owner-relayed incident account, not independently verified. No investigation
or remediation in the separate deployment is requested or authorized by this note.
## Report preserved
Jason relayed Jarvis's account of three causes:
1. code-be-01 lacked read:package. Repository access did not update the existing
token's scopes, and registry token exchange returned 401.
2. After re-minting the credential, code-be-01 generated evidence inside the
infrastructure worktree rather than its agent work/ directory.
3. Jarvis's watch monitored only the agent work/ directory and missed the completed
evidence. The monitoring mismatch extended the delay.
Reported state at handoff: full digest evidence exists; all three manifests are
pinned in the development infrastructure worktree; the delta remains uncommitted
and unreviewed. Jarvis reports the next gate as validation, commit and independent
review. This is context for that other assignment, not a task or commit/review
authorization for this session.
## Later design and acceptance questions
- Distinguish repository permission, package-registry permission and the scopes
actually held by an already issued credential. Permission changes must not be
assumed to update existing tokens. Use explicit capability checks without
revealing secrets or reflexively widening grants. A 401 alone is not a universal
diagnosis of this specific cause; the above cause is the relayed account.
- Before dispatch, bind evidence publication to an explicit, authorized artifact
location and completion contract. Do not infer the path from agent identity or cwd.
- Producers should return a structured receipt containing assignment/run identity,
canonical artifact locator, digest and completion state. Artifact presence alone
does not mean validation, commit, independent review or owner acceptance occurred.
- Watchers should bind to the same agreed receipt/location, not maintain a separate
path assumption. A location change needs an explicit authorized handoff update;
do not compensate by blindly scanning other workspaces or broadening visibility.
- Distinguish producer failure from observer failure. A missing watched file may
mean wrong scope/location, delivery failure or monitoring error—not stalled work.
Reconcile declared publication evidence before retrying or retasking the producer.
- Persist the receipt, watch binding and remaining gates so observer/orchestrator
restarts can recover the obligation without relying on memory.
Suggested later tests: stale token scopes after an access change; package versus
repository capability refusal; publication at an unexpected location; authorized
location-change handoff; missed/duplicate completion events; watcher restart; and
artifact-present but validation/review still incomplete. Keep all tests synthetic
or separately authorized; never use credential contents as evidence.
Related: foundation mechanical-workflow topics and durability observations dated
2026-09-06. No existing inspector requirement is changed, no worker is retasked,
and no credential, watch, repository or live-fleet operation follows from this note.
@@ -0,0 +1,95 @@
# Federation, master registry and mosaic comms — owner direction
Date: 2026-09-06. Recorded by darkwing.
Status: captured forward architecture/API proposal, not an installed command or an
implementation/migration charter. Current offline inspector scope stays unchanged.
## Owner direction
Plan for federation and cross-site/cross-instance clustering from the foundation.
Maintain a master registry covering site, instance, project, workspace and agent.
The mosaic CLI registers those entities and their valid combinations.
Agent communications should use the public `mosaic comms` boundary. tmux must not
remain an agent-facing addressing/communication API; if retained, it is an internal
transport behind Mosaic. Other configurable transports may include durable Git
communications and a Mosaic backend using Matrix or another platform, self-hosted
or hosted services such as Discord/Slack. No backend is selected by this note.
## Proposed CLI, preserved from the owner
```text
mosaic comms
--site -S {uuid|name}
--instance -I {uuid|name}
--project -P {uuid|name}
--workspace -W {uuid|name}
--source -s {uuid|name}
--target -t {uuid|name}
--help
--message -m <text>
--list -l opt:<filter_text>
```
List returns available entries; exact filtering/output semantics remain to specify.
Addressing supports either a UUID of a registered agent instance bound into
site+instance+project+workspace+agent hierarchy, or combinations of UUIDs and names.
Owner wording: names resolve with the agent seat "little-endian"; the precise
qualified-name grammar/order needs specification rather than guessing.
A UUID at any level identifies its bound parent hierarchy, so ancestors need not
be redundantly supplied. The lowest-level UUID can anchor the remaining resolution.
UUID-to-UUID messaging is recommended for brevity and lack of ambiguity:
```sh
mosaic comms -S homelab -I main -P journal -W health -s <uuid> -t <name>
mosaic comms -s <uuid> -t <uuid>
```
These are proposed examples, not commands verified or invoked in this session.
## Design follow-through and open questions
- Separate reusable agent definitions from registered/scoped agent-instance or
communication-endpoint identities. The accepted foundation allows one reusable
agent across scopes; a scoped UUID must not silently turn that definition into
a single-project identity. Define which entity each registry UUID identifies.
- Define logical master-registry authority and replication: single authoritative
service, delegated site authorities or another consistency model. "Master registry"
does not by itself decide deployment topology or make federation available.
- Resolve UUIDs to recorded parents without granting access. Conflicting explicit
ancestors must refuse, not override a binding; names must resolve uniquely within
an authorized namespace or refuse. No filename/cwd/seat-name membership inference.
- Clarify whether shared -S/-I/-P/-W flags constrain both endpoints or qualify only
named endpoints when one endpoint UUID belongs elsewhere. Define the little-endian
name syntax, escaping, aliases/renames, stale registrations and retirement.
- Authenticate the actual caller and check authority to use -s; a supplied source
UUID/name is not proof of sender identity. Independently check destination scope,
message type, visibility and cross-site trust. Listing/filtering must reveal only
authorized entries. Transport delivery never grants reassignment authority.
- Keep addressing, authorization, message/receipt semantics and work truth independent
of the transport. Different backends must not widen permissions or reinterpret
messages as owner instructions, task acceptance or permission to change goals.
- Define durable enqueue/acknowledgement, stable message/request IDs, deduplication,
ordering where required, retention/access controls and restart reconciliation.
Git/tmux/backend presence alone does not establish delivery or exactly-once effects.
- Specify partition/stale-registry behavior, federation trust/key lifecycle, revocation
propagation, site isolation and recovery. Continue unrelated authorized work where
safe; fail closed rather than guess through uncertain authority.
Related: accepted technical map at 7345f330fc6bfae5aa1d896c78cfb7cbe62efbae;
2026-09-06_foundation-mechanical-workflow-topics.md;
2026-09-06_foundation-durability-observations.md;
2026-09-06_foundation-evidence-handoff-observations.md.
## Scope and migration boundary
This records the future platform boundary, not permission to implement federation,
create a registry, select/install a backend or modify the frozen inspector. No
worker is retasked. The proposed mosaic comms command is not claimed available.
The existing approved agent-send wrapper remains the current project transport
until an authorized replacement exists; no raw tmux workaround is introduced.
No external platform communication, credential handling or ~/.mosaic intervention
follows from this note. Registry semantics and transport migration need their own
reviewed charter before activation.
@@ -0,0 +1,416 @@
# Foundation synthetic inspector — frozen charter candidate 3
Issue: #53. Author/integrator: darkwing. Feasibility contributor: rocko.
Independent reviewer: filbert, pending exact-candidate review.
Status: proposed implementation contract, NOT permission to implement.
Jason authorized continuation of planning after accepting the reviewed map.
No source changes, migration, publication, push or live fleet activity authorized.
## 1. Bound inputs and interpretation
Accepted map/handoff: commit 7345f330fc6bfae5aa1d896c78cfb7cbe62efbae.
Source/plan baseline: d4696d09eb1b5dcf1028f30db2cd63735f51cb16.
Map verdict: FM-FILBERT-1, SHA-256
6b08c6fac0718d3db527cf9ffbfab49407e7b289d09782f5d1d0e26493eaabb3.
That approval does not cover this new charter.
Detailed incorporated specification: sections 513 of
reviews/2026-09-06_foundation-inspector-rocko-feasibility-r2.md,
SHA-256 f2f47fcfe22dca79f10f885b83d87a2f846fdb560425a4e20705c40ce4a123e1.
The explicit corrections/decisions below override that note. Its other sections
are contributor rationale/history, not normative repository facts. Original r1
and the FI-ROCKO-2 corrections remain history, not alternate specifications.
Any ambiguity or contradiction discovered at implementation is a charter defect
to resolve, not permission for a coder to guess or weaken refusal rules.
Candidate records.schema.json SHA-256:
05774aaf6943cb69c113e39ff1c29676a2a230ca7bf665c50dbcaa8049672af6.
Candidate check.py SHA-256:
82564a7d3200afcdda0850a9454cac6e6cd6a76687d2162c13cf214d7eac4607.
Both resolve under docs/plans/foundation-v1-candidate at d4696d09.
## 2. Deliverable and non-goals
One local CLI over an explicitly supplied synthetic bundle, with deterministic
text and JSON previews of selected agent/project/workspace permission. No cwd
inference of scope, live registry, config bootstrap, account lookup, engine launch,
assignment mutation or real permission grant. Every result, including parser/I/O
failures, says SYNTHETIC PREVIEW — NO LIVE EFFECTS and authentication: not-modelled.
Implemented operations: work.read (null target), file.read/file.change (logical
workspace-relative target), and assignment.change as a negative/unresolved check
only. The other 25 known catalog names return unsupported-capability, exit 2.
Unknown names return invalid-request, exit 2. No assignment-change success path.
No execution.stop operation is invented; the catalog contains execution.control.
No general RPC, sandbox, authentication, command audit, stopping, durability,
legacy adoption, OAuth refresh or extension integration is delivered.
Operator supplies synthetic files only. The CLI reads just that explicit input,
never discovers credentials/config. It cannot know whether arbitrary operator-
selected file bytes contain secrets before reading them; this is not a general
credential-read prevention boundary. Never pass credentials or live records to it.
Tests and agents are not authorized to access such files.
## 3. Input, identity and graph rules
Use the closed bundle/selection/registry/artifact/authorization shapes in r2 §5,
subject to the proposal and output overrides below. All fields shown there are
required except message. proposal is explicitly null outside assignment.change;
assignment.change requires non-null proposal and null operation.target. The
subjectAssignmentRef must exactly equal selection.assignmentRef; selection agent,
project and workspace must match that assignment. Never infer a different subject.
Eight supported record kinds: agent-definition, project, workspace, registration,
mission, task, assignment and decision. Other candidate kinds refuse unsupported-
kind. Candidate record shapes remain unchanged; simulation-specific fields exist
only in the enclosing bundle, never appended to accepted records.
Record references are kind/id/scope/revision, WITHOUT digest. Registry references
include registry/id/revision/digest. Artifact references and authorization IDs
match declared simulation inputs; their presence proves neither publication
permission, authentication, authentic evidence nor current real-world authority.
Decision approvals are evaluated as synthetic fixture facts, not verified signatures.
Missing declarations refuse; no flag can turn them into actual authenticated facts.
Reject duplicate record identities, duplicate registry identities even with different
digests, duplicate artifact identities (runId/artifactId) even with different digests,
and duplicate authorization IDs. Do not choose one of ambiguous registrations:
more than one current active registration for the same agent/scope refuses ambiguity.
Revision rules: complete contiguous history from 1, or a single revision-1 record.
Structural references on every revision must resolve. Apply head/current-admission
checks only to head records used for this calculation and their consulted chain;
historical records do not fail merely because their then-current refs are now old.
Explicit historical supersedes/decision subject references may name existing old
revisions. Current selected intent/task/mission/registration/delegation/proposal
refs must name heads; no invented predecessor, repair or fallback.
Cycle checks cover task.dependencies, mission.parentMissionRef and supersedes as
separate edge relations. Before tests are counted, show that each fixture reaches
the intended rule rather than failing an earlier shape error. Workspace ownership,
mission parent scope and registration parent match the accepted schema/README.
Dependencies grant no ownership/access. A selected task's same-project dependencies
must be accepted to permit file.change; another project's dependency never lends
permission. Broader dependency execution semantics are unsupported in this slice.
## 4. Mandatory policy and assignment calculation
Adopt r2 §7 L1L9: enabled agent ceiling, both active registrations each bounded by
its mandatory scope-role ceiling, required project and workspace policy, exact
assignment/task/intent checks, applicable task/mission narrowing and an explicit
simulation execution layer. Workspace policy never falls back to a scope role.
Null optional restrictions mean no additional narrowing; missing required ceiling
never means unrestricted. Empty operations/path grants mean none. Exactly one
selected assignment contributes; no union across assignments.
Selected project/workspace must be active. Assignment is selected, belongs to the
selected agent and workspace, and binds the selected task. Task is ready/active,
with approved current plan-approval intent whose subjects include that exact task.
Assignment intent is an approved current plan-approval, delegation or assignment-
change decision covering the task/assignment. An unrelated approved acceptance
or owner-checkpoint decision is not a substitute. Mission chain must be active.
Taskless work.read/file.read skips assignment/task/mission only when assignmentRef
is explicitly null. Supplying an invalid assignment never falls back to taskless.
Paths use logical workspace roots and component-prefix intersection, never raw
string prefixes or physical filesystem traversal. Network restrictions are parsed
and intersected but no network operation is implemented or performed.
## 5. Reassignment negative check — tightened requester context
Extend r2 §5 proposal with required requesterContexts, a closed object:
- original: {assignmentRef, execution}
- target: {assignmentRef, execution}
Each assignmentRef is non-null recordRef; execution uses the same mandatory
selection.execution shape. No extra keys. Original scope derives from the subject
assignment; target scope derives from targetTaskRef. Both requester contexts belong
to requesterAgentId and the corresponding scope, not the subject agent.
Apply L1L9 to the requester in BOTH scopes for assignment.change, using those
contexts. Role membership alone is insufficient; task/mission/intent and execution
limits also narrow the request. Require original subject and target task current
intent/status checks. Apply r2 §8 recipient/subject-bounded delegation and recorded
change checks, with approved outcomes and exact current references. Artifact basis
is evidence only. Delegations are direct declared simulation assumptions as specified in §10;
this inspector cannot detect chains hidden in artifacts or omitted from the bundle.
No message authorizes a change. Missing delegation with a message yields the
message-is-not-authority rule once structural validation succeeds. Other failures
use fixed deterministic original-scope, target-scope, delegation, change, intent
ordering. If all simulation checks pass, return unresolved/unknown-effects with
runtime-reconciliation-required, NOT allowed. No supplied runtime observations
field exists. Echo the original selection unchanged on every admitted proposal.
This is a negative preview, not a real assignment engine or proof of stopping.
## 6. Canonicalization and precise limits
Adopt separately named inspector-content-digest/1 for mock registry content only:
sorted keys, compact encoding, ASCII string domain, safe integer values, array order
preserved. No launch fingerprint or full JCS claim. V1V3 in r2 were independently
recomputed and match. Use 1.5 (not 1.0) for the nonintegral-number V4 test: JavaScript
cannot distinguish numeric 1.0 from 1 after parsing. Reject negative zero and unsafe
integer values. Strict bundle number tokens use integer notation only: reject
fraction/exponent forms and negative zero, bound integer tokens exactly before
Number conversion. This prevents precision loss from hiding unsupported values.
This lexical restriction is an inspector profile rule, not a change to candidate
JSON Schema integer semantics; the differential record validator tests shapes
separately from this strict-input profile.
Non-ASCII mock content refuses unsupported-capability; candidate record and target
paths still use the candidate Unicode/path formats. Validation tests separate schema
validity from the stricter inspector input/canonicalization domain.
Bounds: file <=1 MiB; each top-level inventory <=256; arrays <=1024; nesting <=32;
strings <=4096 UTF-8 bytes subject to stricter candidate limits. Duplicate JSON keys,
malformed UTF-8, invalid calendar dates, forbidden path characters/segments and
unknown fields refuse. Parser object construction must not invoke prototype setters;
__proto__/constructor keys are ordinary unknown keys, never executable behavior.
Never normalize an invalid path into a permitted one.
Linux input open: require available O_RDONLY, O_NOFOLLOW and O_NONBLOCK; open the
final component without following its symlink, fstat regular type and bounded size,
read within limit with an extra-byte/truncation check, close in finally. Parent-path
containment is NOT claimed. Missing/symlink/FIFO/nonregular input refuses exit 4.
Do not OR an undefined O_CLOEXEC constant: host Node v26.8.1 exposes none (measured).
No child spawning or close-on-exec guarantee is claimed. The CLI itself never spawns;
verification harness processes are separate test effects. Same-size concurrent file
mutation is not ruled out by size checks: operator/test inputs must remain quiescent.
## 7. Output and exits — replaces r2 §9 shape
Closed result fields, emitted on every outcome:
- disclaimer: fixed SYNTHETIC PREVIEW — NO LIVE EFFECTS
- preview: fixed preview: no live registrations or permission grants
- bundleVersion: 1
- authentication: fixed not-modelled
- declarations: fixed unverified-simulation
- result: allowed | refused | unresolved | invalid
- reason: allowed | invalid-request | missing-state | stale-revision |
not-authorized | unsupported-capability | unsupported-kind | unknown-effects | io-failure
- rule: fixed documented rule identifier or null; never input-derived prose
- selection: null before safe structural validation, otherwise exactly the selected
{agentId, projectId, workspaceId, assignmentRef} supplied by the caller
- operation: null before safe validation, otherwise the validated {name, target}
- proposal: null outside a valid assignment.change input, otherwise
{result, reason, rule, selectedAssignmentRef}, with no allowed result
- diagnostic: null or {byteOffset, inputPath}, each member nullable; offset only for
parse failure, original argv path only for I/O failure, never file contents
No consulted inventory, global counts, unrelated identities/payloads or raw parser
messages. Text and JSON derive from the same result. JSON is ordinary valid JSON
with the disclaimer field, not a non-JSON preamble. Text escapes control characters
in echoed arguments/targets so diagnostics cannot inject terminal controls.
No time/random data. No output claims authentic publication or real grants.
Exits: 0 permitted permission preview; 2 malformed/structurally incoherent or
unsupported input; 3 simulated permission refusal or unresolved admission; 4 I/O.
Missing required JSON keys (including execution) are structural: 2 invalid-request.
Absent referenced ordinary records are incoherent: 2 missing-state. Absent declared
mandatory policy/artifact/authorization inputs: 3 missing-state. Existing historical
reference used for current admission: 3 stale-revision. Cycles/duplicates: 2 invalid-
request. These override inconsistent example exits in r2. Refusal is never repaired.
## 8. Physical paths, toolchain and verification
Proposed implementation allowlist only:
- scripts/foundation-inspect.mjs (CLI; eventual packages/mosaic)
- scripts/foundation/{strict-json,canonical,resolve}.mjs (pure; eventual packages/config)
- scripts/foundation/validate-record.mjs (test-only differential bridge)
- scripts/foundation/verify-schema.py (verification only)
- scripts/foundation/*.test.mjs and scripts/foundation/fixtures/**
- scripts/test-foundation.sh
This is not yet a code-write authorization. No root/package-manager files, packages/
scaffolds, duplicate runtime sources, .pi/extensions, Dewey scripts, roles or config
changes. Pure modules never import live config, filesystem/process/network APIs or
read environment/clock. CLI handles input/output only, with no ambient config lookup.
Node ESM, zero new runtime dependencies. Record exact tested Node version; proposed
minimum 22 is not evidence of testing every supported version. Host measured 26.8.1;
container source uses node:24-bookworm-slim, not the first CLI's test environment.
No new repository engines pin is inferred. No automatic installation.
Mandatory verification uses existing explicitly selected Python/jsonschema 4.26.0,
checking pinned schema/checker hashes first. Missing dependency fails, never skips.
The differential bridge compares field-level validation over supported candidate
fixtures plus deterministic mutations, not invocation of check.py as a library.
Keep record-schema verdict distinct from unsupported-kind/profile/graph refusal.
Use calendar/path format semantics from the pinned checker, not string-only tests.
Unit/CLI tests cover r2 §12 with these overrides. Separate observed fixture/home/cwd/
data-root roots from harness output. Compare before/after path/type/size/mode/owner/
inode/content inventories; canaries and static import checks are bounded evidence,
not a sandbox or proof of no reads. No native synchronization or live fleet tests.
Required five repository suites and foundation author checks must remain green.
## 9. Acceptance and gates
A1. Positive read and assigned file.change permission previews with coherent records;
deterministic text/JSON, explicit disclaimers and no effects.
A2. Unregistered second workspace/cross-project selection refuses without fallback
or unrelated payload disclosure.
A3. Ownership/reference/duplicate/revision/cycle negatives hit the intended rules.
A4. Required role ceilings and all applicable layers narrow; empty/missing authority,
cross-assignment union and unassigned change refuse.
A5. Encoding, duplicate keys, calendar, path, bounds and unknown-field tests refuse
safely with the documented schema/profile distinction.
A6. Input/observed roots unchanged; no bootstrap, ambient config/credential lookup,
engine, network, installation or migration action.
A7. Cross-lane proposal preserves original selection; requester limits in both
scopes enforced; even fully supplied mock evidence cannot yield allowed.
A8. All output/exit combinations match the closed result contract, including parse
and I/O failure. No raw contents, unrelated metadata or terminal injection.
A9. Jason runs the positive and negative demo and understands the limitation before
any dependent live-registry/runtime increment begins.
First: Filbert independently reviews the frozen charter and incorporated note at
exact hashes without authorship or edits. Resolve substantive findings as a new
candidate. Then present the reviewed build scope to Jason for implementation
approval; prior map acceptance does not supply it. After authorized implementation,
retain separate author/reviewer roles, serialize shared index use, run all required
checks and obtain owner demo acceptance before dependent work. No automatic push,
issue closure, implementation or next-phase advancement follows from this charter.
## 10. Candidate 3 corrections — normative overrides for FI-FILBERT-2
These rules resolve findings 15 and override any remaining inconsistent shorthand
in this charter or the incorporated note. Candidate 2 and its NOT APPROVED verdict
are preserved separately; approval of this revision is not yet established.
### 10.1 Registration delegation and explicit mock issuer bounds (finding 1)
Add required top-level `delegationInputs`, an array of at most 256 closed objects:
`{decisionRef, mode, issuerCeiling}`. decisionRef is a candidate recordRef of kind
decision; mode is exactly `direct-declared`; issuerCeiling is candidate restrictions.
No extra fields. Duplicate exact decisionRef entries are structural invalid-request,
exit 2. This is another explicit simulation input, not an accepted record-schema
change or a runtime authority record. Empty array is allowed when none is consulted.
Its ceiling is an unverified stand-in for the referenced decision author's reviewed
delegation ceiling; it proves neither authorship nor actual permission to delegate.
For each consulted current L2/L3 registration:
1. Check current registration identity, active status, correct agent/owning scope
and required parent registration. Revoked registration: exit 3 not-authorized,
rule registration-revoked. Resolve mandatory scope-role and policy ceilings.
2. delegationRef null explicitly models owner-origin registration under the
declared authorization ID. That is a fixture assumption, not verified owner
issuance. Other mandatory checks/ceilings still apply; null is not a grant.
3. Non-null delegationRef must name a current head decision. Old decision reference:
exit 3 stale-revision, registration-delegation-stale. Its decisionKind must be
delegation and outcome approved; otherwise exit 3 not-authorized,
registration-delegation-not-approved. No new revoked field is invented: changing
the decision head to rejected blocks admission, as does using its old revision.
4. Supported scope is exactly the registration's scope. A broader/other scope is
exit 3 unsupported-capability, registration-delegation-scope-not-modelled.
subjectRefs must be exactly the current agent-definition reference for that
registration's agent and the exact current registration reference. A wrong
recipient/registration is exit 3 not-authorized, registration-delegation-bounds;
additional subject forms are exit 3 unsupported-capability,
registration-delegation-subject-form. No ambiguous pairing of multiple recipients.
5. Require the exact decision's delegationInputs entry. Missing: exit 3 missing-state,
delegation-input-missing. Every delegatedOperation must be in issuerCeiling's
operations; otherwise exit 3 not-authorized, delegation-exceeds-issuer-ceiling.
6. Intersect the role ceiling, optional registration restrictions, delegated
operation set AND the issuerCeiling restrictions (operations, paths, network and
endpoints), then all other L1L9 layers. No union or skipping the role ceiling.
Current consulted scope-role/agent-policy/project-policy references must name the
highest declared revision of that registry/id; an older ceiling is exit 3
stale-revision, policy-ceiling-stale. Historical records may retain exact old refs.
This is currentness within the explicit mock inventory, never live-current proof.
Apply the same exact delegationInputs/issuer bound check to the proposal's explicit
delegation after its approved kind/outcome/recipient/subject tests. Its issuer
restrictions additionally narrow the requester's calculations in both scopes.
No source of declared authority overrides any required role, policy or assignment.
Required coherent tests: owner-origin null; approved applicable direct delegation
with file.read permitted; rejected delegation; unrelated plan-approval/acceptance;
wrong recipient; wrong registration; unsupported scope; role allows file.change
but delegated operations/issuer paths do not; missing issuer input; active registration
pointing to an old approved decision whose newer head rejects it; reference updated
to that rejected head; and revoked registration. Each must reach its named gate.
### 10.2 Structural ownership versus consulted work access (finding 2)
Structural graph validation uses EACH record's own scope, never the global selected
project. Every task mission must belong to the task's project (project-level mission
or a workspace mission in that project). A mismatch is exit 2 invalid-request,
mission-owning-project-mismatch. Workspace mission parents must be project missions
in their own project; project missions have no parent. Dependencies may structurally
reference tasks in other scopes/projects; they confer no permission or ownership.
Unselected P2 tasks with P2 missions remain valid when the caller selects P1.
For each actual calculation context (main selection, or requester original/target):
- Consulted tasks/mission/dependency references may target the SAME workspace or
the parent project only. A different workspace of the same project is exit 3
unsupported-capability, cross-workspace-work-reference-not-modelled. Another
project is exit 3 unsupported-capability, cross-project-work-reference-not-modelled.
Apply these refusals even if that agent has another registration there. The first
slice deliberately does not model additional cross-scope visibility contexts.
Do not inspect that referenced work's status/restrictions for admission or silently
omit the reference from readiness checks. Structural existence/type checks are
distinct and operate on the supplied synthetic inventory, not live private data.
- References within the supported relation require work.read in the caller's full
applicable L1L9 intersection, as well as the requested operation. Otherwise exit 3
not-authorized, consulted-work-not-readable. The parent-project registration and
policy are already mandatory layers, as are the acting workspace restrictions.
The calculator may combine supplied mock restrictions to derive this intersection;
it returns no referenced bodies and does not claim authentic private-record access.
- Dependencies of a task used for file.change or proposal requester authority must
be accepted. Otherwise exit 3 not-authorized, dependency-not-accepted. A reference
rejected by the scope guard cannot be skipped because its status says accepted.
Taskless work.read/file.read does not traverse unrelated tasks or their dependencies.
- Apply the same reference-access rules when the proposal checks its subject/target
work: original requester context for the subject; target requester context for
the target. Dependency refs never become extra assignments or execution authority.
Tests: local workspace mission/dependency permitted with work.read; parent project
mission permitted; work.read removed while file.change remains; W1 task referring
to W2 mission or accepted dependency while agent registered only W1; same case even
with W2 registration (still unsupported in this slice); cross-project dependency;
and independent P2 task/mission inventory under a P1 selection, which must not fail
merely because it is unselected. All outputs remain body-free.
### 10.3 Direct assumptions, not hidden-chain detection (finding 3)
The model supports only the direct-declared delegationInputs form in §10.1. There is
NO claim to detect a chain concealed behind an artifact, a declaration or an omitted
record. A decision subject reference to another decision is not automatically a
parent-delegation edge. Historical subject references retain their defined meaning.
No artifact body is read and artifact presence is not authentic authority.
An input adding parentDelegationRef or another undocumented field fails the closed
shape rule: exit 2 invalid-request. A mode other than direct-declared likewise fails
shape; it is not a successfully detected hidden chain. A valid direct declaration
with opaque artifact pointers is checked as a declared assumption and cannot prove
absence of chaining. Test these three distinctions explicitly. Proposal success
remains unreachable, even when all direct simulation checks pass.
### 10.4 Fixed graph sub-order and fixture reachability (finding 4)
Global order: input bounds/lexical parsing; closed shapes/profile/supported kinds;
identity index and duplicate checks; structural reference existence/type; cycles
(task.dependencies, then mission.parentMissionRef, then supersedes); revision-chain
continuity; per-record ownership/parent-scope checks; supported operation selection;
then context binding/current-head, work-scope guards and ordered admission checks.
Within a stage use stable kind/id/scope/revision and field ordering, not input order.
Use the documented missing-declaration/current-admission exit exceptions in §7.
Schema-valid workspace mission self-parent and revision-2 self-supersedes fixtures
must hit their cycle rule BEFORE parent-scope or predecessor-continuity refusal.
Use separate detector unit tests and CLI-precedence tests. Never count an earlier
schema/ownership rejection as demonstrated cycle-detector coverage. Missing required
policy/artifact/authorization declarations are admission failures at their documented
stage, not swallowed by the ordinary-record structural missing-reference rule.
### 10.5 Differential corpus and platform boundary additions (finding 5)
Add scalar type substitution; every null-versus-absent branch; integer min/max,
boolean-versus-integer and numeric 1.0 schema cases; nested object-order-insensitive
uniqueItems comparisons; BMP/non-BMP code-point length boundaries; UTF-8 byte-path
boundaries; and escaped surrogate cases. Schema verdict and strict bundle/profile
verdict are separate expected columns. Strict bundle strings reject unpaired escaped
surrogates as profile-invalid; this is not an assertion about unformatted candidate
JSON Schema strings. A passing finite corpus is compatibility evidence, not proof
of complete schema equivalence. Actual Node versions tested must be reported.
O_RDONLY availability checks use presence/type, not truthiness: its valid value is 0.
@@ -0,0 +1,160 @@
# Installation, onboarding and reconfiguration — owner requirements
Date: 2026-09-06. Recorded by darkwing.
Status: captured future deployment/onboarding requirements and proposed CLI, not an
implemented installer or permission to modify current bootstrap/config/auth behavior.
No current install.sh implementation or compatibility is claimed.
## Installation experience
- Update the Mosaic installer for the new deployment structure and support switches
for automation.
- Basic or advanced installation must be selectable without an initial interactive
"basic or advanced?" question. Define a mode switch/default in the later CLI charter;
the owner has not specified its exact spelling or default.
- Provide a step-by-step guide distinguishing required and optional steps. Optional
steps offer Skip. Once required configuration is complete, allow Skip to finish
without completing the optional sections.
- Provide configuration mode for customization at any later time. Define resumable,
validated reconfiguration rather than rerunning destructive installation.
- Define unattended behavior for missing required input explicitly: report the
missing requirements and fail, rather than unexpectedly prompt or invent defaults.
## Required initial configuration
| Area | Owner requirement |
|---|---|
| Deployment | Containerized or bare-metal |
| System authentication | SSO, OIDC, LDAP or internal authentication |
| Break-glass administrator | Always internal/basic authentication; username, password, email, independent of chosen external identity provider |
| Internal initial user | Username, first name, password, email |
| External-auth initial user | Initial user login through selected SSO/OIDC/LDAP path |
| Site | Site name |
| Instance | Name; default main |
| Initial agent name | System-generated or user-selected |
| Agent gender | male, female, neutral |
| Agent personality | Compatible traits selected with multiple-choice checkboxes; examples technical, professional, friendly, flirty |
| Agent communication style | Compatible traits selected with multiple-choice checkboxes; examples direct, teacher, terse, supportive |
| Default harness | Mosaic/Pi recommended; Claude or Codex alternatives |
| Initial harness account | OAuth or API account configuration for Claude, Codex or Pi; Pi provider choices include OpenRouter, Z.ai and others |
| First project | Name; default general |
| First workspace | Name; default chat |
| Routing | System-selected or user-selected task routing/agent mapping |
System login authentication and model-provider/harness account authentication are
separate configuration domains. Agent personality/gender/style do not grant roles,
permissions or access. Define trait compatibility rather than silently combining
contradictory choices. Register scoped entities through the future Mosaic registry,
not through filename inference or ad-hoc tmux identities.
## Optional initial configuration
System/use-case presets, potentially combined subject to a compatibility model:
- Software factory
- Personal assistant
- Executive assistant
- Enterprise / personal
- Journal
- Health tracker
- Writing assistant
- Social-media management
- Business operations
- Job-application tracker
- Additional future presets
The owner also specifies multi-user by default; do not interpret basic mode or a
personal preset as permission to remove identity separation or access controls.
Advanced user profile:
- Last name, gender, profession, hobbies, skills
- Food preferences
- Family, marital status, children, pets
- Automobiles and work history
- Writing-style capture for the user's communication voice
- Social-media account linking
- Gmail / Google-services linking
- Plugin add-ons, system skills and system extensions
Other optional setup:
- Custom agent-to-task mapping
- Additional agent registration
- Additional user registration
Optional profile/account data is opt-in, classified and access-controlled. Sensitive
health/family/work data must not automatically become globally shared agent context.
Account linking needs explicit scopes/consent; selecting a preset is not permission
to send messages, post publicly or take other external actions.
## Proposed CLI — owner example preserved
```sh
./install.sh \
--type container \
--site homelab \
--instance main \
--auth internal \
--admin_user <username> \
--admin_pass <password> \
--admin_email <email> \
--user_username <username> \
--user_name <name> \
--user_pass <password> \
--user_email <email> \
--agent_name {-g|<name>} \
--agent_gender {male|female|neutral} \
--agent_persona {technical|professional|friendly|flirty|...} \
--agent_comms {direct|teacher|terse|supportive|...} \
--agent_harness {mosaic|pi|codex|claude} \
--agent_auth_name <registry_name> \
--agent_auth_type {oauth|api} \
--agent_api_prov {openrouter|zai|...} \
--agent_api_key <key> \
--project general \
--workspace chat \
--routing {system|<custom_routing>}
```
This is proposed syntax with placeholders, not a runnable verified command. Exact
bare-metal spelling, mode switch, generated-name option, repeatable trait syntax,
routing input format and reconfiguration command remain to define. Clarify whether
mosaic and pi are aliases or distinct public harness identifiers.
## Security, durability and workflow design obligations
1. Revise raw --admin_pass/--user_pass/--agent_api_key inputs before implementation:
literal argv secrets can leak via shell history, process inspection and logs.
Prefer protected file-descriptor/stdin or permission-checked secret-file inputs,
masked interactive entry and supported secret-manager references. Never echo
secrets, put them in ordinary config, Git, images, evidence or diagnostic receipts.
2. Break-glass authentication must remain available through an identity-provider
outage without being an unrestricted bypass. Specify protected password storage,
account recovery/rotation, rate limiting, audit, privilege boundaries and testing.
Clarify coexistence of the break-glass account and the everyday initial user.
3. External user login and OAuth may require interactive consent; automation must
support provider-authorized preconfiguration or explicitly pause at that gate.
Do not claim a usable account before its required capability checks succeed.
4. Publish resumable installation/reconfiguration checkpoints with secrets excluded.
Record required-step completion, optional skips and recovery state. Restarting
setup must not duplicate users/agents/projects or lose already configured work.
5. Preserve fail-closed configuration and immutable evidence. Current repository
canon permits system config creation only through scripts/bootstrap.sh and no
overwrite. A future installer/reconfiguration authority model needs an explicit
reviewed migration design; this note does not override that rule.
6. Installation should establish the registered site/instance/project/workspace/agent
bindings and supported routing policies. UI presets cannot widen reviewed role
ceilings, bypass assignment requirements or silently retask existing agents.
7. Compare bare-metal/container dependency, storage, service ownership, networking,
upgrades and recovery workflows explicitly. Pin dependencies; never improvise
unattended privilege escalation or installation after a failed precondition.
8. Test basic/advanced and unattended modes; each auth/harness branch; external login
failure; provider outage; missing required inputs; optional skips; interruption/
resume; repeated install; protected secret handling; and later configuration.
## Scope boundary and related work
Related: foundation federation/comms, mechanical-workflow, durability and evidence-
handoff topic notes dated 2026-09-06; accepted map at 7345f33; #50 auth/provider work.
No installer, config mutation, account creation/linking, transport migration, package
installation or external-deployment investigation follows from this capture.
The current inspector and its schema/profile review stay unchanged. No worker is
retasked. These requirements need a later deployment/onboarding charter and review.
@@ -0,0 +1,136 @@
# Foundation map handoff — MAP-HANDOFF-2
Status: commit-pinned source/plan baseline; ready for owner review and a separately
approved non-author review. No implementation or push authorized.
## Baseline
Source/plan commit: `d4696d09eb1b5dcf1028f30db2cd63735f51cb16`.
Foundation parent: `44f257cb06484feda3412d9382e3587393796353`.
Mapping revision: the separate commit containing this document and the
[technical map](2026-09-06_foundation-technical-map.md).
All file:line source citations in the technical map refer to the source/plan
commit above unless explicitly historical. The map/handoff are not claimed to
exist at that earlier commit. MAP-HANDOFF-1's provisional baseline and delivery
waits were superseded after Jason authorized local commits and Dewey supplied
MS55-DW-3. Shared BUILD-LOG/SESSIONS retain the chronological receipts.
## What is ready
- All R1-R34 mapped to responsibilities; 20 source-linked findings and nine
inspected legacy source files, unchanged from 69d1bb3.
- packages/* succession target reconciled with current extensions/** source and
generated .pi installation. No source move or package-manager change proposed now.
- Dewey's shim, single adapter ownership and package-versus-process-privilege
qualifications adopted in the technical map.
- One small increment: read-only synthetic scope/permission inspector, seven core
acceptance cases plus the owner-reported cross-lane retasking negative scenario.
No live registration, authentication, sandbox or runtime guarantee is claimed.
Jason's ~/.mosaic incident report is context only. No investigation/intervention
there occurred or is authorized. A coordinator message must not itself reassign
an agent, redefine its role or displace an owner-authorized goal.
## Collaboration and verification boundary
MS55-DW-1/2 coordination is complete. MS55-DW-3 reports Dewey's 43-path baseline
commit and index release; parent, path allowlist and empty index were independently
checked locally before staging mapping work. Dewey's native extension test/review
receipts establish that separate baseline, not managed-foundation acceptance.
No mapping review has yet been dispatched or approved by a non-author.
## Non-author review checklist, when authorized
1. Resolve the mapping revision and source/plan baseline independently.
2. Open every cited source location and validate all R1-R34 mappings, not just counts.
3. Challenge reuse/new classifications and the limited inspected inventory.
4. Check packages/* sequencing, single source ownership and trust-boundary separation.
5. Examine broad mounts, shared context, authorization gaps, uncertain retry and
deletion-before-receipt findings and their dependency ordering.
6. Confirm the inspector cannot imply real permission grants or sandbox proof.
7. Return independent findings; do not substitute author fixtures or #55 native
acceptance for runtime enforcement tests.
## Remaining gate
Owner review and separate authorization for non-author review. Implementation,
source moves, migration, push and issue closure remain outside this handoff.
## Source/plan SHA-256 inventory at d4696d09
Every listed file was compared byte-for-byte against the named commit. This
inventory excludes credentials, generated installations and runtime state. The
mapping documents are versioned by their own containing commit, not this input list.
```text
d4c4e08f56c299106133b57e58a78ddfcb015f131036d243d54f42e0fbdc741b .pi/.gitignore
ea5856b5d93811b43b1c1f278eab3283f7f909396dd74464f49dfe1717b51e0e .pi/README.md
3e4498c8066d5a796a8cfe6e07fc1aad118dac2827c771d954b4ae0c38dae84c .pi/SOURCE-SNAPSHOT.json
73823786b54acae627ddefe4b8a258f0554330e49213a118d32e1ffe59c40415 .pi/goal-dev.sh
d213c167d319dbbb42326f68c9c76ec01dbdd42e8f4f226d3232cc5b355bfebc adapters/pi/adapter.sh
a21dc87079d255261ae7318845073ec06bf9df7bac6874012ea0c84b0d1ec12d compose.yaml
ce58408289fceea4b99d8a77c69523a1c04e683f5084f31c7c4199ae0b9934f3 docs/plans/2026-09-06_agent-project-workspace-foundation.md
a49533e1a06e7610583141e7dbf5f529569b9fb3765bf0f4742ff287905f4dfb docs/plans/2026-09-06_foundation-phase2-contract.md
6e2a6b4e0d323940ee5139f5b01f3ecedb23522b9cfa7674fc3d817508cb152f docs/plans/2026-09-06_monorepo-source-layout.md
b746e8c9963dce430b83a7a5fd0973b789a753cc5edd8288291ecdc8f906b2af docs/plans/2026-09-06_ng-goal-footer-dev.md
6e854e83f2a2b26cb93473611b0a47264fb64dd08b6c272c6b18257238915c82 docs/plans/2026-09-06_workspace-schema-and-audit.md
9e0a95865f9479c5f2d06c485513326891727e5295b62c085efe41dddd7dbc0a docs/plans/ROADMAP.md
27fd60f68d30ddc8c1a0cef96714308b8e526f60a17852f7ec21957c1870ec81 docs/plans/foundation-v1-candidate/README.md
7883fde367a3c4377585a5d2f74f09e8f662651b479abb652779c3ed6c7a4c32 docs/plans/foundation-v1-candidate/REVIEW.md
b1a2b4d0df88ba6f7b197252807f3a3925ffff9375f4e70d4ff28593337c3438 docs/plans/foundation-v1-candidate/RUNTIME.md
82564a7d3200afcdda0850a9454cac6e6cd6a76687d2162c13cf214d7eac4607 docs/plans/foundation-v1-candidate/check.py
7806e42cd792935ddba1c8bcac853f049d79eab227fb7191fe622685e699203a docs/plans/foundation-v1-candidate/command-events.fixtures.json
19e9e50359790ec9a4de5b8c817026ac075e254968de1411867222646743d31f docs/plans/foundation-v1-candidate/command-events.schema.json
cbfcb88531838c8c3dd290257e5d9a657e552bb7dd0f67102b49a921a249da45 docs/plans/foundation-v1-candidate/fingerprint-vectors.json
d433d06da5cd38baf9e51c8857244ee70375db3b68e02a5325a6d1c2cc47da85 docs/plans/foundation-v1-candidate/records.fixtures.json
05774aaf6943cb69c113e39ff1c29676a2a230ca7bf665c50dbcaa8049672af6 docs/plans/foundation-v1-candidate/records.schema.json
02a611925923b2592d9e0e67741a542e6c2e8bec06d69ae5700f25c51d720a7a docs/plans/foundation-v1-candidate/runtime.fixtures.json
74deeb4cd6d87ff9306ed088b9641e51f9c41db71589b5364424908842ee51bc docs/plans/foundation-v1-candidate/runtime.schema.json
93d16f38738bb0610d5250ee54c271934e4f2201660becaa72982363d0711fda docs/plans/foundation-v1-candidate/semantic-model.fixtures.json
c89c3bb19624826c1a84658595c71694a1fd371b9155ef50ffa4c265c099daa8 docs/plans/foundation-v1-candidate/semantic-model.py
1551ee0bb11d4a16181186f5091ddbcfcb0ea1e4718997639542beb797442308 extensions/README.md
0850b651309b57a50f2933182376531c8bbcc75ab92d83c1798dfd1637fe8b8b extensions/goal/README.md
9bfcf60097ec83209f5acae443ce48dde55d39343426de63f89997e3275e67ed extensions/goal/index.ts
5db32424e7376f85f22b1055addf3784125da09c3ea23b86a3d0d338f852babd extensions/goal/lib/display.ts
57f1d89c998099b2a9b0c860e70a35db61261a5d1ae7f07cc9acff29fe21042e extensions/goal/lib/executive-update.ts
3837d31fad6481a3c69132ec7fb19849498d5d4c90b0e41eeb683daec19b50a1 extensions/goal/lib/parse.ts
6614b228b3d10252751bc4b68f62ada58f04dfb3a77a51086bb2d7ae42f39c8b extensions/goal/lib/settle.ts
ade39fa3c7a99295712dac41fdfe366384e2cfd695a967c62f0e3f424876ffae extensions/goal/lib/state.ts
78185bb61cb85ae8f2940a89e18063a1d90361d8a869582fff6f4787ec7430dd extensions/goal/lib/store.ts
cad6670a9e8206166b710cd464347aedee6401be9307248b10f9fcfc295585f0 extensions/goal/test/communication-closeout-contract.test.ts
8b28e3c63607dfc321e6b74cd967e92ae6f72ed3e31e2286fcf9a5964394bb02 extensions/goal/test/display.test.ts
66d9437111c4574ea255affe539b78c6ad896d7f43de191d154873b9ef500501 extensions/goal/test/executive-update.test.ts
dfb172ddd228205d3e72a47dfe11372b9cc39e4584017461efe037aa3dc8c08a extensions/goal/test/fencing.test.ts
ac5a69536ee07cc31c6f3a738ca2d5c446689bbfdae05dafffd9c6302864b96c extensions/goal/test/fixtures/.gitattributes
bbea48a46b1f8da7bc759f86856fb52830b7dde456b826317163c6dc6ccab319 extensions/goal/test/fixtures/skills-local/ms-executive-update/SKILL.md
c797776e992c4b1187d786ccb0cce57ca817ebd91ec56caebd4bc220d660ff9b extensions/goal/test/fixtures/skills-local/ms-honesty/SKILL.md
0132ad6508df2bcba0df7fb417e06cbe90c7ea663e334f2176c8973757bb4809 extensions/goal/test/fixtures/skills-local/ms-proactive-agent/SKILL.md
be377aa1e3128efddc00c651771e145acc6cb9d5c6f2599260d1e00ef617ac7d extensions/goal/test/goal.test.ts
3a03b44aea1a9f316ee1138092e3cb55507cdff34ca2cbf828d45ed90a46b699 extensions/goal/test/progress.test.ts
2a33f36881d38656e26ca7580907cf2b22df69d94243ebdfee7adef140d77040 extensions/goal/test/quiet-wait.test.ts
38df8499a64dd465ff5011b06fce80c0793bb53eb7c33bb685001e56451647f3 extensions/goal/test/runtime.test.ts
ea7e9e86782a8716a5e33a27d653227d162c595ba4aa02d1ee691fd4ddead081 extensions/mosaic-core/lib/adapter.ts
4604ae28cd16966d0e447a4e8075bbe320c3119d0a4c8fe870b98e2bb4fb6d03 extensions/mosaic-core/lib/enforce.ts
f291b11818ba01567c1f42bbd36fae63a2398a347426d863ecc6bdb7820358c7 extensions/mosaic-core/lib/gate-record.ts
8313837181a1ac07e7ca58f99873e66b305cd5b5e8d125c189b14d231bdc4620 extensions/mosaic-core/lib/goal-policy.ts
cea0165ab9b323c083ce2fcb1d9fa835e1ee534045cd67536aec17c339457d45 extensions/mosaic-core/lib/incarnation.ts
b52a129c97b822209acdf96ddae49c970bbb9a4dd74ac5bbe7440418e9cb2af1 extensions/mosaic-core/lib/journal.ts
b0c12f2cb9e974ad72d28773617a862e5e880612d63d1edc513c4e057d6f21d9 extensions/mosaic-core/lib/loader.ts
aabc1046dca38f03cfbe111259a21521b24b250736be42f548a57a0e315cf70b extensions/mosaic-core/lib/policy.ts
f174864dc499d0a39e7786e1220c54df3e9af2cd8523476c3d4bf2e2d5928264 extensions/mosaic-core/lib/proposal.ts
00448f6f00f72163bcde3e0a6fc9b87023c46a225316df7530bc14d9953b5e12 extensions/mosaic-core/lib/reconcile.ts
1ad2270a02e5668ef126c2af71a7b70771c2da09842313bdbf6abba84e95b555 scripts/agent.sh
fe5d3e89272d3b04db687eabed30a95dde480a2f7bc784cd27e43fa9321f15a6 scripts/auth.sh
a8a41274c06ab6fe38c42d1e96db115b8d65b03a87ed1796c4d37288258a1d12 scripts/goal-dev.sh
430ee6bc4fcfbe4b9ac030aaa19cdb6fdc7407e1b17253b80178b9b0523b5a3a scripts/mosaic-config.mjs
525bab31453ab84afa379421c619405632b9c85d639e2f4a6188dcf7ae825b83 scripts/mosaic-task.mjs
957ef76f949c2eb2e472182261bf2d1619e0cde44c506ab2bbb5c25dc063864d scripts/reset.sh
eee8c0a9c5708cbc9d0922ea733f09b34d50da8816f95a43e5afe3e3dfc24dbb scripts/sync-dev-extensions.sh
5d7b687f16fd1a9414b229501177d530ba3af4eadd3cac319b33f38fdba9bf99 scripts/test-extension-package.sh
4c5db63da5194a937ee4a488b796f60876a780482812d949e3337bf0d274a974 scripts/test-goal-native.py
4b210d5d785d06d699ceaa902ccb0451c09cb19397e861521f825c09e9e33ae5 src/load-contracts.sh
6749ebe3d0433b3dfefb40a44d58c4ca2606ab9f4ce01457dd52767059b7b13b src/run-agent.sh
ece76e2690012b53087357d467edf3f2c3f872d5db28bd97eaa1bbb217f5ed57 tasks/ng-goal-footer-implementation.json
d50fd5dbbd08a39b2a91424392d2b31bc2f9b4a1e3c3d09ee8061d5776989459 tasks/ng-goal-footer-review.json
```
@@ -0,0 +1,63 @@
# Mechanical coordination and recovery — owner discussion backlog
Date: 2026-09-06. Recorded by darkwing.
Status: captured owner direction and open design questions, not an implementation
charter, product selection or authorization to change the current inspector build.
Jason wants a continuously operating software factory with minimal user babysitting.
Mechanical status checks, action timeouts, stalled-session detection and other
failure indicators should support bounded recovery and escalation: restart an
orchestrator when appropriate, escalate agent stalls to an orchestrator, and involve
the user only when remediation requires owner authority or cannot safely proceed.
These workflows need attention during low-level development, not as a late UI layer.
## Questions to resolve in later design
1. n8n versus a custom mechanical workflow implementation: compare durable execution,
restart recovery, authority boundaries, idempotency/reconciliation, operational
complexity, observability and maintenance. No technology has been selected.
2. Kanban triggering: identify the authoritative work-state transition and component
allowed to admit/trigger work. Boards should project the same recorded work truth;
UI events, duplicate notifications and card movement cannot independently grant
execution authority or create competing task state.
3. Stall/failure definition: distinguish quiet but healthy work, long-running actions,
missing telemetry, blocked external dependencies, lost controller connections,
provider failures and actual dead processes. Define observations, thresholds,
ownership and false-positive controls for each state.
4. Continuity without state loss: bind recovery to durable goals, assignments,
action/WAL evidence and exact session/execution identities, not chat memory alone.
5. Recovery ladder: specify which checks, retries, restarts, Resume/Fresh operations
and escalations are preauthorized, by whom, within which scope and attempt limit.
Preserve other valid work and avoid restart storms or recovery loops.
6. Minimal owner remediation: distinguish routine recoverable faults from genuine
authority/safety decisions. Escalations should include diagnosis, attempted bounded
recovery, preserved evidence and a small actionable choice—not an unexplained
request for the user to investigate the whole system.
## Constraints to preserve
- A timeout or missing heartbeat is a signal, not proof of process death or absence
of effects. Fence stale controllers/workers and reconcile uncertain effects before
replacement; no blind replay or duplicate active execution.
- Restarting an orchestrator must not lose its outstanding obligations or confer
extra permissions. Mechanical coordination and model-based orchestration need
explicit ownership and independent recovery paths; neither is an unlimited role.
- Resume/Fresh may improve recoverability, but neither alone proves safe recovery.
Resume needs exact identity/history checks. Fresh must rebuild authorized work
context from durable records without silently importing old chat or abandoning work.
- Automatic recovery remains bounded by current intent, permissions, audit availability
and the accepted fail-safe stopping exception. Minimal babysitting is not permission
to guess around a safety refusal or silently change owner goals.
- Test orchestrator/worker crashes, delayed/lost/duplicate events, external outages,
restart during uncertain actions, sustained load and escalation/recovery loops.
Demonstrate both continued authorized work and preserved state/evidence.
Related: 2026-09-06_foundation-durability-observations.md and the accepted technical
map at 7345f330fc6bfae5aa1d896c78cfb7cbe62efbae.
## Current work boundary
This note preserves ideas for later discussion and workflow planning. It does not
select n8n, implement a daemon/watch, arm a timer, restart anything, investigate the
separate ~/.mosaic environment or retask Rocko/Filbert. FI-ROCKO-3 remains the frozen
offline synthetic inspector build. No new phase or independent goal loop is created.
@@ -0,0 +1,386 @@
# Foundation phase 2: record and operation contract candidate
Status: phase-2 planning baseline accepted by Jason on 2026-09-06 ("accept phase 2"). Not approved for mapping
or implementation. Owner authorized this phase on 2026-09-06 after confirming
the behavior summary. Tracking: `mosaicstack/stack-v2#53`.
Inputs: [agreed behavior R1-R33](2026-09-06_agent-project-workspace-foundation.md)
and [decision/interview record](2026-09-06_workspace-schema-and-audit.md).
This file proposes common types and operation rules. The linked
[Schema and semantic-rule package](foundation-v1-candidate/README.md) now contains
checked command/event and eleven-kind record schemas, permission bundles,
reference/lifecycle/storage rules, hash vectors, and #50 compatibility notes.
The [runtime contract](foundation-v1-candidate/RUNTIME.md) and runtime schema now
cover mediated execution/control, requests, messages and artifact bodies after
Q28 A. The [owner-review package](foundation-v1-candidate/REVIEW.md) reconciles
D1-D16 and lists unproved implementation gates. None of this is implemented runtime behavior. No new owner requirement is inferred from a technical proposal.
## 1. Evidence boundary
Repository baseline: `69d1bb3aa4b826218aa4cca3710f2d98c0b9d7ba`.
The inspected source files have no working-tree difference from that commit.
Existing runtime image: `mosaic-poc-agent:0.84.4-r0.0.12`, image ID
`sha256:a72aa79f98e54c3c974f5ad08b82e1643a4aa6eff259ac891b738120ba963ca1`.
A tool-free worker examined supplied, line-numbered source excerpts through
`scripts/run-task.sh`. Task specification:
[`tasks/foundation-phase2-source-facts.json`](../../tasks/foundation-phase2-source-facts.json).
Run: `r-20260906T024609Z-68ee7f`, under `<dataRoot>/runs/`.
`result.json` SHA-256:
`e81ac68a369a9315d827e6fc7117033f794ab7b214d2610ca07081a668045875`.
The run reports success, exit 0, no tools or persistent session, and 1490
whitespace-delimited response words. This is a source-analysis response, not
an independent review verdict or a runtime test of the proposed controls.
Author checks qualify the worker's suggestions:
- One supplied excerpt header says `adapters/pi/adapter.sh:65-106`, although
the file ends at line 96. The supplied numbered text and worker's executable
citations end at 96. Preserve that input as recorded; no lines 97-106 exist.
- A recorded stdout response is not a trusted account of arbitrary side effects.
- The exclusive active-work key must include agent/project/workspace, not a
session ID that lets two different Fresh sessions evade the same-scope limit.
- A session transcript grows. Verify identity, supported format, and accepted
checkpoints; do not mistake its changing content hash for corruption.
- `--no-extensions` disables discovery, not explicitly supplied `-e` extensions.
The current adapter supplies no `-e`; future reviewed extensions are possible,
but their enforcement and failure behavior need investigation.
- Command receipts and complete internal file/network tracing are different
guarantees. The worker's suggestions do not settle their required granularity.
### 1.1 Pinned Pi documentation
Host Pi is 0.85.1. Its documentation was not used as proof of 0.84.4 behavior.
The author copied the 0.84.4 package's README, package metadata, docs, and
examples from the existing image using `docker create` and `docker cp`.
The extraction container was never started and was removed afterward. No
credentials, runtime sessions, or user-context files were copied. Temporary
extraction: `/tmp/mosaic-foundation-pi-0844.poVO9d`.
To reproduce, create an unstarted container from the exact image ID above and
copy the named files under
`/opt/app/node_modules/@earendil-works/pi-coding-agent/`. Do not substitute a
newly installed host version. The temporary directory is not canonical evidence;
the image ID, file paths, and hashes identify the documentation used.
| Package file | SHA-256 |
|---|---|
| `package.json` | `db9fead11bd2ddf7a327d2c2d11b535f30d059241c251d376837d5ab638a5576` |
| `README.md` | `f2ffe4e825ea457ecb728cabbb15b545655cda2e3bab9b5b1fc28b950a3abd08` |
| `docs/rpc.md` | `010811d3526435b4fff5b03ee3a40f5bcb47fa17d5034fbd6b598955f56020d7` |
| `docs/json.md` | `094b8d1b25aebd949334b695b18e8e7e73d934b82337439af12f6936d73aca84` |
| `docs/session-format.md` | `c0f25475ab08fb96e244e6bf433aeaa41b69bfb5a52de5c7b01b81c900a84a54` |
| `docs/security.md` | `5df4b27e7b0db49f50807f0e5d8a77e6dc099c76adab6edf21c5b705ede6efab` |
| `docs/containerization.md` | `29ac379f744052adf301e744559cb43d0e6e84d1b04f4757f1710680991be10b` |
README and these five documentation files were read completely. They establish
documented capabilities and cautions, not measured implementation behavior.
No SDK, extension, RPC client, or isolation mechanism was implemented or tested.
### 1.2 Findings that constrain the contract
| Evidence | Supported statement | Design consequence, still proposed |
|---|---|---|
| `scripts/agent.sh:132-138`; `adapters/pi/adapter.sh:29-44` | Session default is per agent; nonempty session directory causes `-c`. | Replace latest-file inference with a scoped conversation index and exact session binding. |
| Pinned README, Session options; `docs/session-format.md:386-438` | Pi documents exact session selection and session-manager APIs. | Mosaic can investigate using an exact validated path; no need to expose partial UUID search to clients. |
| Pinned `docs/session-format.md:19-27` | Older session formats are automatically migrated when loaded. | Preserve a legacy source before any reviewed adoption/conversion; do not load original historical files merely to inspect them. |
| `scripts/agent.sh:121-123,181-185`; `src/run-agent.sh:37-41`; `src/load-contracts.sh:40-41,97` | SOUL, mission, and generated prompt locations are shared by agent or data root. | Resolve stable execution-specific inputs and avoid shared temporary-file names. No race reproduction is claimed. |
| `scripts/agent.sh:144-162`; `scripts/mosaic-task.mjs:364-378` | Interactive path intersects role tools; headless path intersects mission/task tools. | One future authorization resolver must apply all applicable ceilings on both paths. Current checks are not a project/workspace RBAC system. |
| `compose.yaml:39-43`; pinned `docs/security.md:31-37` | Broad data-root mount; Pi explicitly has no built-in sandbox. | Tool names and working directory cannot establish cross-workspace privacy or evidence protection. |
| `scripts/mosaic-task.mjs:295-302,408-466` | Exclusive-create snapshots/results and Compose process outcome, not a complete trusted action ledger. | Separate invocation outcome, external effect, acceptance, and confirmed execution termination. |
| Pinned `docs/rpc.md:43-80,905-911,1012-1060` | Prompt acceptance, settled processing, and tool events are documented and distinct. | Acceptance of an RPC prompt is not task acceptance; events alone are not durable pre-action authorization. |
| Pinned `docs/rpc.md:124-184,479-550,620-642` | Abort, queue clearing, direct bash, and session switching are separate commands. A session switch can be cancelled while the response says success. | Do not pass arbitrary raw RPC commands through a Mosaic connection. Do not equate abort acknowledgement or idle processing with process death. |
| `adapters/pi/adapter.sh:81-96` | Mosaic currently runs plain print or native interactive mode, with no explicit extension or RPC invocation. | Documented Pi integration options are not already implemented Mosaic attachment, control-transfer, or audit guarantees. |
## 2. Proposed common record types
Plain-language rule: an ID names an object, a revision names the exact approved
copy, and a scope names where it belongs. A display name is only a label.
These proposed types are shared across records; they are not fields accepted
by today's task runner.
| Type | Exact proposed representation and validation |
|---|---|
| `Id` | String matching `^[a-z0-9][a-z0-9._-]{0,63}$`. New runtime IDs use lowercase UUID v4; reject collisions with exclusive allocation. Existing definition IDs require explicit resolution, not a guessed alias. |
| `Revision` | Integer from 1 through 9007199254740991. A create request expects revision 0 to mean absent; 0 is never a persisted revision. |
| `UtcTime` | A valid UTC instant encoded as `YYYY-MM-DDTHH:mm:ss.sssZ`. Validate calendar/time values as well as shape. Wall-clock time is not proof of causal order. |
| `Digest` | String `sha256:` followed by exactly 64 lowercase hexadecimal characters. A digest detects content change; it is not a signature or access grant. |
| `Scope` | Discriminated object: `{kind:"system"}`, `{kind:"project",projectId:Id}`, or `{kind:"workspace",projectId:Id,workspaceId:Id}`. No omitted or extra fields; workspace ownership must match its project. |
| `RecordRef` | `{kind:RecordKind,id:Id,scope:Scope,revision:Revision}`. Resolve exactly; a reference never means latest or grants access. `RecordKind` is the closed catalog in section 3. |
| `ActorRef` | `{kind:"human"|"service"|"agent",principalId:Id,executionId:Id|null}`. An agent requires its authenticated execution ID; human/service actors use null. Producer supplies this from verified invocation context, never from a caller's identity claim. |
| `FileTarget` | `{workspace:Scope,relativePath:string}` where scope must be workspace kind. Nonempty slash-separated relative path, at most 4096 UTF-8 bytes; reject NUL, control characters, empty components, `.`/`..`, backslashes and absolute paths. Validate filesystem containment and symlinks at use time, not merely through a string prefix check. |
Unknown fields/versions fail validation. Required fields cannot be omitted.
Use explicit null only where the type permits it; an empty list means none,
not unrestricted. Permissions and source-reference lists contain no duplicates.
Proposed generic list limit is 1024 items and record size limit is 1 MiB of
UTF-8 JSON, with detailed evidence stored separately. Limits need workload
review before becoming an implementation contract. Reject oversize input;
never silently truncate authority, dependencies, or recovery context.
Common envelope for declarative records: `schemaVersion:1`, `kind:RecordKind`,
`id:Id`, `scope:Scope`, `revision:Revision`, `createdAt:UtcTime`,
`createdBy:ActorRef`, `supersedes:RecordRef|null`, and `authorizationRef:Id`.
`authorizationRef` resolves a trusted authorization event, not a recursive grant
from the record being created. System bootstrap authority resolves to reviewed
policy and the authenticated owner; no record may authorize its own creation.
The first revision has null `supersedes`. Later revisions reference the exact
previous revision of the same object and immutable owning scope. A mutable
current index is a rebuildable view, not the authoritative history.
## 3. Record catalog and ownership
The catalog names proposed types, not approved role or directory additions.
Fields below supplement the common envelope. Complete per-kind field types,
conditional requirements, and transition validation are still being drafted.
There is deliberately no unrestricted `metadata` or `permissions:any` field.
| RecordKind | Payload to define | Authorized writer |
|---|---|---|
| `agent-definition` | Display name, reusable type, harness reference, canonical SOUL/instruction/skill references, reviewed role ceiling and configuration references. | Reviewed configuration author, not a workspace worker. |
| `project` | Display name, policy reference and lifecycle. | Authorized project administration. |
| `workspace` | Display name, parent project via scope, policy reference, lifecycle and logical working-file root. | Authorized workspace administration. |
| `registration` | Agent ID, scope role reference, narrowing restriction set, active/revoked status and delegation reference. Project and workspace registrations are separate records. | Actor with bounded registration authority. |
| `mission` | Objective, criteria, lifecycle, optional parent project-mission reference and policy restrictions. Project missions have no workspace parent. | Authorized work owner. |
| `task` | Purpose, criteria, optional mission reference, approved intent reference, dependencies and lifecycle. An ad-hoc task still needs purpose/criteria and explicit authorization. | Authorized work owner/coordinator. |
| `assignment` | Exact task reference, agent ID, lifecycle, assigned policy/work revisions and coordinator decision. | Authorized coordinator; progress does not grant assignment authority. |
| `decision` | Decision kind, approved intent/evidence references, affected records, delegation bounds and decision outcome. | Authorized decision maker. Acceptance checks authorship independence separately. |
| `session` | Agent ID, immutable project/workspace scope, creation mode, predecessor reference and exact harness identity binding. | Launch resolver; lifecycle observations are separate events. |
| `context-source` | Content reference, revision/digest, owner, permitted scopes, context category and explicit sharing designation. | Authorized context publisher. Relevance cannot invent a grant. |
| `adapter-capability` | Adapter/package identity, supported operations, observable and enforceable action classes, tested coverage references and limitations. | Reviewed adapter authority. A worker cannot self-certify coverage. |
Execution, active claim, connection, message, context manifest, and audit event
are runtime evidence types, not mutable declarative records. Their complete
envelopes remain a dependent drafting task. Do not force them into a common
mutable `status` record or rewrite a run to update its progress.
## 4. Permission resolution
Proposed operations are distinct from tool names. A `read` tool grant does not
imply permission to read another conversation, credential, or audit payload.
An action must satisfy both the requester's authority to request it and the
performer's authority to execute it. The performer is bounded by reviewed
agent policy, project registration, explicit workspace registration, applicable
parent/workspace mission policy, task policy, and launch restrictions. Resolve
all required references before taking the intersection. Missing/invalid required
policy is an error, not an empty policy to skip. An optional absent restriction
adds no grant and does not remove any ancestor ceiling.
Each layer checks operations and their targets. Project participation alone
never admits a workspace action. Multiple assignments do not produce a union
that lets an action borrow another task's wider grant. Bind a changing action
to one explicit assignment and current intent revision. Tool availability is
only a coarse filter before per-action authorization.
Proposed standard role catalog for later review:
- Reader: permitted work records and scoped file inspection.
- Contributor: permitted inspection plus assigned file changes and result
submission, with command execution subject to Q27's invocation-audit ruling
and verified permission, containment, and evidence checks.
- Reviewer: permitted evidence inspection and independent routine acceptance.
- Coordinator: permitted within-plan decomposition and assignment changes.
These roles are not a rank hierarchy. Coordinator does not automatically mean
file writer, reviewer, membership administrator, or credential administrator.
Separate explicit grants govern registration, conversation reading/observation,
control transfer, retirement/reopening, and protected operations. Exact operation
sets and any combined standard roles need review. Shared role authority still
changes only through reviewed commits under `roles/`; no live role-policy file
was added or modified during this phase.
## 5. Proposed command contract
Use the target `mosaic` command family already proposed by #50. These spellings
are candidate interface decisions, not commands to run today. Terminal,
desktop, web, and service clients submit the same authorized operations.
| Candidate command | Operation/result rule |
|---|---|
| `mosaic agent resume <agent> --project <id> --workspace <id> [--session <id>]` | Default launch operation. Use exact session if provided; otherwise use the authoritative current scoped session. Genuine first use creates and announces initial conversation. Missing established history refuses. |
| `mosaic agent fresh <agent> --project <id> --workspace <id>` | New conversation, no old chat/automatic summary. During active work, request controlled replacement; do not overlap executions. |
| Work flags for Resume/Fresh: `--work continue|select|abandon|none`, repeatable `--assignment <id>` | Omitted work flag means continue selected assignments. No selected work means taskless conversation/inspection, not arbitrary changes. Select/Abandon require explicit assignment IDs. Abandon ends only those assignments. Validate referenced revisions before launch. |
| `mosaic execution connect <id> --mode control|observe` | Exact active execution, explicit authorization. Control is exclusive; observe needs conversation access. No automatic connection from a conflict response. |
| `mosaic execution transfer <id> --to-connection <id> --expected-generation <n>` | Explicit authorized transfer; old generation stops admitting control input. Being an observer is not permission to take control. |
| `mosaic execution config-check <id>` | Return matching/changed/unknown, launch/current references and check time. Automatic non-blocking notices use the same comparison record. |
| `mosaic workspace retire <id> --project <id>` | Stop admitting new work, safely stop/reconcile active work, retain files/history. Report retirement pending if safety is unresolved. |
| `mosaic workspace reopen <id> --project <id>` | Revalidate current policy and membership; no automatic restoration of revoked access or launch. Exact assignment reactivation rules need review. |
The runtime schema now fixes `requestVersion:1`, `requestId:RuntimeId`,
`operation`, `scope`, `expectedRefs:RecordRef[]`, `assignmentRef` and closed
`parameters`. Input and command requests have their own narrower shapes. The
service supplies authenticated actor and authorization; clients cannot submit
trusted producer/outcome records.
The runtime schema defines the common response: version, request ID, outcome,
reason code, optional execution ID and classified receipt/data references.
Accepted means durable admission, not completion. Success/acceptance cannot have
null receipts. Invalid/unauthorized pre-admission refusals and audit failures are
not success receipts. Already-active identity requires authorization; wrappers
never silently connect or Fresh.
The linked runtime contract supplies all 29 operation names, request coverage,
error codes, JSON stdin/file semantics, message routing and #50 boundaries. CLI
presentation/aliases in a later charter must map to these same operations.
## 6. Ordering and recovery obligations
1. Authenticate and validate before resolving private state for a caller.
2. Resolve exact record revisions, scope, permission, and expected audit coverage.
3. Serialize active-work admission by agent/project/workspace. Default limit is
one, independent of session ID. Validate history before claiming first use.
4. Commit authorization and intent to trusted durable storage before effects.
Proposed durability requires file flush plus parent-directory flush and
atomic publication on a supported local filesystem; unsupported storage
refuses. Crash-order and multi-record commit mechanics still need design.
5. Resolve exact immutable launch inputs and record the manifest. A successful
spawn is not a confirmed session binding; require adapter identity evidence.
6. Serialize controller changes separately by execution ID and generation.
Reject stale generations for new input even if an old connection still lives.
7. Before managed actions, recheck policy, assignment/plan revisions, active
claim generation and required recording. A changed approved plan pauses
affected work; a shared base-configuration notice alone does not.
8. For replacement/revocation/retirement, close admission first, account for
queued requests, settle or identify outstanding effects, and confirm the
execution can no longer act before admitting conflicting new work.
9. Append observations and recovery decisions. A timeout, abort acknowledgement,
or Pi `agent_settled` event alone does not prove container/process termination.
A scoped conversation index must retain initial-creation history and missing/
pruned-session tombstones. A missing index is not proof of first use. Retrying
a request must resolve its prior durable decision; a timeout or missing response
must not replay an external action. Request identity binds authenticated actor,
operation, scope, and normalized arguments. Reuse with different arguments is
an error. Tombstone retention and post-prune retry behavior remain unresolved.
The enforcing runtime component is not an autonomous conductor agent. It has
only its reviewed operation authority; changing a task's state cannot change
policy. Required audit failure blocks affected actions, including recovery
checks that cannot meet their own recording requirements.
## 7. Storage and evidence
Keep immutable execution evidence under `<dataRoot>/runs/`. A proposed new
execution record uses its run directory as the evidence owner; projects and
workspaces reference it instead of maintaining competing result copies.
Existing v1 records are never rewritten or silently reinterpreted as v2 evidence.
Keep conversation bytes under `<dataRoot>/sessions/`, but resolve them through
explicit scoped metadata rather than directory naming. Do not mount all sessions,
all runs, all user context, or the whole data root into a worker's tool-accessible
filesystem. Only the necessary approved inputs and working paths are exposed.
A trusted recorder must be outside worker write permissions. OS ownership and
mount tests must verify these boundaries before any isolation claim.
Proposed metadata ownership: project/workspace records in their dedicated
runtime metadata namespace; current indexes under a derived system state
namespace. No actual directory choice or second configuration file is installed.
Exact paths, per-directory writers/readers, export, retention, prune/reset
references, and rollback must be specified before schema approval.
Legacy adoption makes a new mapping receipt. Preserve the original source and
its identity before adapter conversion. Pi's documented automatic format
migration is a reason to inspect metadata without opening a legacy session in
Pi. Fresh does not load an adopted transcript just because it is accessible.
## 8. Configuration fingerprints
Proposed algorithm: SHA-256 over a versioned canonical JSON manifest using RFC
8785 serialization. Hash exact instruction/SOUL bytes as referenced content;
normalize structured settings through their strict schema first. Preserve
instruction order; sort set-valued permissions and source inventories by their
explicit stable keys. Publish test vectors before implementing the comparator.
Comparable base configuration covers canonical SOUL/shared instructions,
applicable enabled skills and their dependency content, canonical harness/model
settings, and reviewed role configuration. Exclude timestamps, display labels,
chat, task progress, private user context, and credentials. Content changes must
be detected even if a path stays the same. Do not hash unrelated skills merely
because they are installed.
The full launch manifest separately binds execution identity, selected approved
context, effective restrictions, session choice, adapter/image identity, and
non-secret credential-account reference where authorized. Its hash naturally
differs between launches. Never compare full launch hashes as the base-config
change test. Private context source references/digests remain access-controlled.
Exact included JSON keys, skill dependency inventory rules, configuration
revision races, approved launch overrides, and notification transport still
need D10/D16 design and #50 reconciliation. A launch fingerprint is not continuous
proof of live process state, and a changed role grant may require immediate
revocation rather than waiting for Fresh.
## 9. Owner decision after investigation
### Q27: command-level evidence or internal-effect evidence
A command such as `npm test` can invoke other processes, read/write many files,
and attempt network access. Pinned Pi documents tool invocation/result events;
it explicitly does not provide a sandbox. Mosaic's current adapter does not
capture those events as a trusted per-action ledger.
Choices presented to the owner; Jason selected A on 2026-09-06:
- A: for the initial managed command facility, record the authorized command
invocation, actor/scope/assignment, enforced filesystem/network limits,
start/end, outcome, and controlled evidence references. Be explicit that this
does not separately enumerate every internal file read/write or network call.
- B: require separately attributable internal file/network effects before
enabling command execution. Keep commands unavailable until that stronger
coverage is designed and verified.
Owner ruling: A, with real sandbox enforcement and honest coverage labels.
This keeps test/build commands practical while preserving accountable invocation
and fail-closed admission. Use typed, allowlisted command metadata. Neither
events nor detailed evidence may contain credentials; unfiltered argument,
environment, and output capture is not approved. The ruling does not waive
workspace isolation, credential protection, action recording, revocation,
or uncertainty recovery. A command
whose effects cannot stay within the authorized boundary must still refuse.
This ruling settles D11's command-audit granularity, not a specific sandbox,
RPC/SDK choice, product UI replacement, or implementation increment. It does
not grant shell access to any current research worker.
## 10. Phase-2 exit gate remains open
Q27 and Q28 are answered; R34 records the managed-terminal ruling. Runtime,
control/input, command artifacts, permission/routing/recovery rules and the
D1-D16 reconciliation now form an owner-review candidate. Jason's specific
phase-2 acceptance is now recorded. Implementation must prove the listed mechanisms;
any change to owner behavior returns for a new decision.
Candidate acceptance evidence must include negative schema fixtures, reference
and permission checks, representative lifecycle/crash traces, hash test vectors,
and a capability matrix that distinguishes documented, code-inspected, and
runtime-tested support. Current evidence is source/document inspection and author
schema/hash/synthetic-model checks only. No
runtime feature or security guarantee, independent verdict, whole-schema approval,
or readiness for mapping follows from this draft.
## 11. Continuing goal task record
Goal: issue-53-phase2. Owner: Jason. Assigned author: darkwing in the explicit
repository working directory. The repeated /goal request resumes this existing
assignment; it does not create a second authority record. CURRENT owns live
control state; BUILD-LOG and SESSIONS hold append-only history.
| Task | Criterion and dependencies | Status and evidence |
|---|---|---|
| P2-1 | Recover Q1-Q27 requirements and pre-reboot work. | Done for author recovery; R1-R33 and preserved source/run evidence. |
| P2-2 | Repair command/record/path schemas and fixtures after P2-1. | Author checks pass: 38 command cases, 38 record cases, 16 path cases, plus 5 deliberately shape-valid forgeries. Runtime semantics remain separate. |
| P2-3 | Define permission/reference/lifecycle/storage rules after P2-2. | Review proposals in schema package sections 2-6; no live policy installed. |
| P2-4 | Define comparable fingerprints and reconcile #50 boundaries. | Seven restricted-domain vectors pass; package sections 6.1-6.2 retain the unresolved OAuth gate. |
| P2-5 | Choose managed terminal requirement and finalize affected execution/control/artifact schemas. | Author checkpoint complete after Jason's Q28 A. Runtime schema, fixtures and RUNTIME.md provide the review candidate; no runtime capability is claimed. |
| P2-6 | Assemble exact-candidate review package, trace D1-D16, and recommend one testable increment. Depends on P2-5. | Owner package in REVIEW.md; 155 runtime cases and 35 synthetic rule-model cases supplement earlier checks. First recommendation refined to a read-only synthetic scope/permission inspector, not live registration. |
| P2-7 | Obtain Jason's specific phase-2 acceptance after P2-6. | Done: Jason said "accept phase 2" after clarification that this accepts the planning baseline, not security certification or implementation. Goal satisfied; later mapping/implementation still require separate authority. |
Jason clarified that /goal belongs to the separate ~/.mosaic deployment, not
this environment. Continue through existing file-based records. No extension
installation, active --wait-timeout 60, or automatic follow-up was requested for
this phase-2 session. Separate #54 native-extension work is not part of this goal;
this session has not migrated its goal state or configured that extension.
Owner waits are manual and never expire into approval. No numeric work budget was supplied; aggregate usage is
unavailable, not reset on resumption.
@@ -0,0 +1,352 @@
# Foundation technical map — #53 / #55
Status: source/plan baseline pinned; prepared for owner review. Author: darkwing.
Scope: documentation/read-only investigation and explicitly authorized local
baseline/mapping commits; no implementation, migration, push or issue closure. Prepare for independent review, not self-approval.
## Authority and coordination
The active operator goal authorizes mapping the accepted phase-2 foundation with
Dewey, aligning canonical directories, identifying reuse/change and recommending
one small user-testable increment. This supersedes CURRENT's earlier wait for
mapping authorization; it does not reopen phase-2 acceptance.
MS55-DW-1: Dewey requested scope/path coordination. The attempted direct tagged
reply returned exit 2: submission could not be confirmed. Jason identified a known
tmux-tool bug. Delivery remains unknown; no blind resend or private-pane polling.
Dewey subsequently directly acknowledged MS55-DW-1 and recorded the agreement in
his #55 layout plan. Receipt is now settled; no resend is needed. He is waiting
for Jason's mapping-goal authorization in his session; this session's active goal
already authorizes its own mapping work. Do not assume his work has started.
Acknowledged division:
- Darkwing: this map, foundation requirement-to-code trace and CURRENT integration.
- Dewey: #55 canonical source, package/install boundaries and phased inventory.
- Shared BUILD-LOG/SESSIONS: append-only; no exclusive claim.
- Darkwing will not edit extensions/**, .pi/**, #54/#55 plans or Dewey's packaging
and goal test scripts. Accepted foundation documents remain stable inputs.
## Initial measured directory alignment
Repository HEAD measured at this checkpoint: 69d1bb3aa4b826218aa4cca3710f2d98c0b9d7ba.
The working tree includes uncommitted foundation and separate #54/#55 work;
HEAD alone does not identify that newer source. Exact content hashes are required
for the eventual review handoff.
| Boundary | Existing location | Mapping disposition |
|---|---|---|
| Immutable worker instructions | contracts/ | Retain dedicated contract ownership; draft schemas are not installed contracts. |
| Reviewed role authority | roles/ | Retain; scope registration must narrow, not replace this authority. |
| Harness integration | adapters/ | Retain adapter boundary; trace mediated-runtime changes before proposing placement. |
| Skills | skills/ | Retain declarative resources; not automatically JavaScript workspace packages. |
| Goal extension/support source | extensions/goal/, extensions/mosaic-core/lib/ | #55 canonical-source input; do not duplicate or relocate in this mapping. |
| Generated native installation | .pi/extensions/ | #55 installation output, not canonical source. |
| Launch/build/test utilities | scripts/ | Distinguish tooling from future service logic; no moves proposed yet. |
| Existing runtime code | src/ | Inventory responsibilities before proposing apps/ or packages/. |
| Plans and evidence summaries | docs/ | Keep planning separate from runtime authority/evidence storage. |
Directory existence was checked locally. The goal/source-install boundary comes
from Dewey's #55 layout record; its source/package verification is not foundation
runtime admission evidence. No empty apps/packages scaffolding is proposed.
## Initial workplan (historical)
1. Trace actual source entrypoints and state/authority paths against R1-R34.
2. Classify reuse unchanged, reuse with changes, replacement, and new component;
identify dependencies and proposed canonical placement without moving files.
3. Reconcile package/source boundaries with Dewey, settling MS55-DW-1 receipt.
4. Recommend one bounded user-testable increment and produce a hashed independent-
review handoff with unresolved risks. Owner review remains the completion gate.
## Source trace checkpoint: launcher, adapter, policy and evidence
Read directly on 2026-09-06 at 06:59 UTC. These classifications concern the
inspected paths, not a claim that no similar capability exists anywhere else.
| Source locator | As built | Reuse/change classification and requirement impact |
|---|---|---|
| scripts/agent.sh:78-130 | Reads reusable seat definition; copies SOUL into a shared per-agent data-root path; writes a seat record if absent. | Reuse identity source concept (R1). Change materialization to immutable execution-specific inputs (R16/R17); preserve canonical source ownership rather than duplicating definitions per workspace. |
| scripts/agent.sh:132-170 | Default session is agent-NAME; declared role resolves tools, intersected with requested tools. | Replace global default with explicit project/workspace/session resolution (R3/R4/R12). Reuse narrowing principle, not this as a full scope authorizer. |
| scripts/agent.sh:181-199 | Mission copied to shared per-agent path; workspace defaults to agent name; native Compose launch. | Change launch orchestration and mission snapshots. Existing directory names cannot establish membership. R34 requires a mediated client path, not merely native launch with another flag. |
| adapters/pi/adapter.sh:23-50 | Changes cwd, supports ephemeral/fork/persistent modes; a nonempty session directory adds -c; tools are explicit or disabled. | Retain adapter separation and explicit tool/discovery controls. Replace directory-nonempty/latest selection with exact binding and genuine-first-use checks. Preserve fork/history behavior only after explicit compatible adoption. |
| adapters/pi/adapter.sh:63-96 | Native TUI or print; ambient extensions/context/templates disabled; explicit provider/model and prompt. | Reuse explicit configuration/discovery suppression where verified. New mediated transport/tool gateway required for R34/R33. Do not enable the native #55 extension in managed workers as an implicit shortcut. |
| scripts/mosaic-task.mjs:252-273,670-676 | Closed role fields, filename identity, known unique tools and network enum; resolve-role emits tools and network. | Reuse validation principles and tests with changes. This role format does not express the new project/workspace registrations or 29-operation catalog. Network metadata emission is not evidence of network enforcement (R13/R33). |
| scripts/mosaic-task.mjs:362-378 | Task tools intersect mission tools; empty intersection yields tool-free. | Reuse least-privilege operation, expand to all required ceilings, targets and current revisions. Do not infer authorization from tool presence or combine assignments. |
| scripts/mosaic-task.mjs:295-325 | Exclusive wx creates input snapshots; writeOnce writes then closes, without fsync in this helper. | Reuse exclusive-create intent and snapshot conventions. Change publisher for durable commit ordering, classifications, trusted origins and recovery. Exclusive creation alone is not crash durability. |
| scripts/mosaic-task.mjs:442-466 | Result stores prompt/response, task/session/tools, exit/signal/model and times, then writeOnce. | Retain legacy run evidence and useful provenance fields. Do not treat it as the R14/R33 invocation ledger: new scope/assignment/authorization/limits/intent/observation records and controlled detailed evidence are required. |
### Proposed component boundaries, not source moves
- Scope/reference/policy resolution: a reusable domain module with no process,
credential or filesystem-effect authority. Existing validation/intersection code
is an input, not permission to copy its narrower semantics unchanged.
- Managed launch/control coordination: separate runtime responsibility above the
harness adapter; owns claims, current intent and authenticated control routing.
- Pi adapter: owns engine protocol translation and exact-session/config binding,
not canonical project membership or global policy decisions.
- Trusted evidence publisher/supervisor: distinct from worker output. Owns durable
intent/outcome publication and trustworthy stopping observations.
- Keep proposed modules unallocated to new apps/packages until their dependency
and build boundaries are reconciled with Dewey. Existing scripts remain untouched.
### Dependency implications
A read-only scope/permission inspector can precede managed execution: it needs
strict records, a coherent synthetic reference graph and a clearly labelled
permission calculation. It does not need provider credentials or Pi launch.
Live registration needs the trusted publisher and protection from legacy broad-
mount paths first. Managed launch then depends on scope resolution, publisher,
claim/control protocol, adapter admission and real isolation/stopping proof.
### Exact inspected file identities
- `scripts/agent.sh`: `1ad2270a02e5668ef126c2af71a7b70771c2da09842313bdbf6abba84e95b555`
- `adapters/pi/adapter.sh`: `d213c167d319dbbb42326f68c9c76ec01dbdd42e8f4f226d3232cc5b355bfebc`
- `scripts/mosaic-task.mjs`: `525bab31453ab84afa379421c619405632b9c85d639e2f4a6188dcf7ae825b83`
## Mount, context and lifecycle trace
Read-only source inspection, 2026-09-06 07:01 UTC. No reset, prune, retry,
container launch or credential-file read was performed.
| Source locator | Observed behavior | Classification / required boundary |
|---|---|---|
| compose.yaml:38-43 | Whole configured data root mounted at /var/lib/mosaic without :ro; auth file separately mounted read-only. | Replace mount design for managed admission. Read-only auth mounting does not establish credential separation from the engine, and whole-root exposure is not workspace isolation. Preserve runtime-only credential handling, not broad mounts. |
| src/run-agent.sh:20-41 | Adapter name/path checks; dispatch after generating one shared /var/lib/mosaic/system-prompt.md. | Reuse dispatch validation with changes. Context builder must publish execution-specific immutable inputs, not a shared prompt destination. |
| src/load-contracts.sh:18-58 | Governance files required; optional seat SOUL; OUT.partial is a fixed sibling staging name. | Reuse governance precedence and missing-source refusal. Replace shared staging/output with uniquely owned execution snapshots and verified publication; current concurrent same-output writers can contend. |
| src/load-contracts.sh:68-77,81-98 | Appends every user/*.md, then mission objective/directives. Header's stated layer order differs from actual user-before-mission order. | Replace blanket context discovery with classified, authorized selection and explicit ordering. Actual code, not header prose, defines this baseline. R6/R16/R17 and context privacy require recorded source revisions. |
| scripts/reset.sh:16-48 | Loads configured data root, rejects root symlink/resolution mismatch/missing marker, then recursively removes that root. | Retain useful refusal checks, change lifecycle integration before protected foundation state exists. Despite hard-coded-path commentary, implementation compares resolved path with configured TARGET, not a fixed literal. No active-claim/reference protection or reset receipt is present in this path. |
| scripts/mosaic-task.mjs:548-595 | Retry reads task snapshot, redirects relative mission to recorded snapshot, then runs it as a new run. | Reuse provenance and original-record preservation. Do not use as uncertain-effects recovery: no old process/effect reconciliation gate is visible in retryRun. New run identity alone does not make replay safe. |
| scripts/mosaic-task.mjs:598-641 | Prune sorts run directories, keeps a count, defaults to preview; --yes removes each directory before appending its receipt. Directory-read failures are caught as no entries. | Reuse preview/explicit-apply UX, not protected-retention semantics. Add live-reference/claim protection, distinguish unreadable state from empty state, and make deletion/receipt failure recoverable. Deletion-before-receipt exposes an uncertainty window. |
### Proposed authority and state ownership
| Responsibility | Proposed owner | Required separation |
|---|---|---|
| Approved context selection and snapshot construction | Trusted context builder under launch coordinator | Does not trust workspace file discovery or client classification; private inputs stay out of shared work metadata. |
| Actual mounts, egress and process cohorts | Sandbox supervisor/gateway | Separate command jobs from credential-bearing engine; no worker access to evidence/policy/control roots. |
| Canonical records and required evidence | Trusted publisher and retention coordinator | Writer/retention share a serialized protection boundary; a worker cannot prune its own audit trail. |
| Uncertain outcome recovery | Authorized recovery coordinator plus trustworthy observers | Distinct from replay; must establish stopping and reconcile effects before admitting replacement/retry. |
| Native extension development installation | Dewey's #55 tooling | .pi installation acceptance is not evidence that container mounts or managed lifecycle meet these requirements. |
### Ordering constraint for the rewrite
Before live foundation state is treated as protected, close legacy broad-mount
launch paths into that state and integrate reset/prune protection. Before managed
Resume/Fresh, provide exact scoped session/claim resolution and immutable context
snapshots. Before uncertain-effect retry, provide trustworthy stopping, evidence
availability and explicit reconciliation. These are dependencies, not source moves
or authorizations to repair the current scripts during mapping.
### Additional source identities
- `compose.yaml`: `a21dc87079d255261ae7318845073ec06bf9df7bac6874012ea0c84b0d1ec12d`
- `src/run-agent.sh`: `6749ebe3d0433b3dfefb40a44d58c4ca2606ab9f4ce01457dd52767059b7b13b`
- `src/load-contracts.sh`: `4b210d5d785d06d699ceaa902ccb0451c09cb19397e861521f825c09e9e33ae5`
- `scripts/reset.sh`: `957ef76f949c2eb2e472182261bf2d1619e0cde44c506ab2bbb5c25dc063864d`
- `scripts/mosaic-task.mjs`: `525bab31453ab84afa379421c619405632b9c85d639e2f4a6188dcf7ae825b83`
## Complete requirement responsibility index
This index covers every accepted requirement, not every implementation. “New”
means not provided by the inspected paths; repository-wide absence is not proven.
Source detail is in the two trace tables above; accepted behavior is in the
foundation plan R1-R34. Uninspected responsibilities remain explicit gaps.
| Requirement | Proposed responsibility | Mapping finding |
|---|---|---|
| R1 | Identity | Launcher identity reuse; scoped runtime binding changes |
| R2 | Scope/policy | New registration/delegation resolver; existing tools-only role validation is insufficient |
| R3 | Scope/policy | New single-parent project/workspace graph and explicit membership |
| R4 | Session/control | Replace global session default with scoped identity |
| R5 | Scope/policy | Explicit selection resolver replaces agent-name workspace default |
| R6 | Context | Replace shared/global work input paths with authorized snapshots |
| R7 | Session/control | Exact Resume/initial/Fresh state; replace nonempty-directory continuation |
| R8 | Work coordination | New assignment selection/Abandon/prerequisite transitions |
| R9 | Session/control | Authorized service launch and work-record recovery |
| R10 | Session/control | New exclusive scoped claim; scaling/budgets remain later-phase requirements |
| R11 | CLI/client | New shared operation interface; no client-owned task truth |
| R12 | Messaging | New explicit scoped addressing/delivery; not established by inspected launcher |
| R13 | Sandbox | Replace whole-root mount boundary and prove containment |
| R14 | Evidence | Expand run provenance into trusted classified action evidence |
| R15 | Governance | Retain explicit owner phase and user-test gates |
| R16 | Context | Replace shared SOUL materialization with immutable execution snapshot |
| R17 | Context | New comparable fingerprint and cross-interface notice flow |
| R18 | Scope/policy | New mission ownership/parent graph and reference checks |
| R19 | Work coordination | New bounded decomposition and non-author acceptance checks |
| R20 | Work coordination | Separate taskless read/chat from assigned changes |
| R21 | Session/control | New active-conflict response and explicit connection |
| R22 | Context | New transcript-specific visibility and handoff checks |
| R23 | Scope/policy | New revocation propagation linked to supervisor stopping |
| R24 | Session/control | New controller/observer generations and transfer |
| R25 | Session/control | New fenced, verified-safe replacement protocol |
| R26 | Recovery | Replace blind retry use for uncertainty with evidence-based reconciliation |
| R27 | Evidence | New fail-closed recording/admission and preauthorized fail-safe stop |
| R28 | Context | Replace blanket user Markdown inclusion with classification/selection |
| R29 | Retention | New retirement/reopen without deletion; protect required evidence |
| R30 | Session/control | New reviewed legacy adoption; preserve originals |
| R31 | Work coordination | New current-intent reconciliation and stale-action rejection |
| R32 | Scope/policy | Extend reviewed ceilings with standard scope roles and narrowing |
| R33 | Evidence/sandbox | New mediated invocation records plus actually enforced limits |
| R34 | CLI/client | New Mosaic-controlled terminal; retain Pi behind reviewed adapter |
## Canonical placement matrix for Dewey reconciliation
ROADMAP.md:127-166 records an owner decision, not just an optional legacy pattern:
post-M20 succession uses packages/*, with restructuring delayed until script
replacement to avoid two migrations. This corrects any reading of the initial
map as leaving the entire package target undecided. #55 extensions/** is current
canonical source, not an implicit repeal of that post-M20 destination.
| Responsibility | Current source/input | Post-M20 proposed destination | Packaging/ownership boundary |
|---|---|---|---|
| User CLI and managed terminal | scripts/agent.sh and other wrappers | packages/mosaic/ | CLI consumes domain/runtime APIs; wrappers retire only after replacement acceptance. |
| Engine/session/control/supervision | src/, adapters/, launcher orchestration | packages/agent/ | Runtime owns process/session protocol; adapter internals do not own global role authority. |
| Strict records, references and policy intersection | scripts/mosaic-config.mjs, parts of mosaic-task.mjs; candidate schemas | packages/config/ | Pure validation/resolution separated from effectful publication; first inspector can exercise this boundary. |
| Provider/account materialization | #50 plan/current auth tooling | packages/auth/ | Credential ownership stays here; no credential migration or refresh experiment in mapping. |
| Evidence/work/recovery coordination | mosaic-task.mjs portions plus new responsibilities | Initially packages/agent/ internal modules | Separate trusted writer and policy interfaces; do not invent extra top-level packages without independent build needs. |
| Contracts/roles/missions/tasks/skills | Existing dedicated directories | Retain declarative directories | Packages consume reviewed resources; no promotion of drafts or workspace-written authority. |
| Goal extension and imported support | extensions/goal/, extensions/mosaic-core/lib/ | Current source retained pending explicit M20 extension packaging decision | Dewey owns inventory/provenance; whether to stage a package artifact or move source later remains a specific unresolved boundary. |
| Native development installation | .pi/extensions/, sync/test scripts | Generated installation remains separate | Never package .pi/state or treat native acceptance as managed-runtime proof. |
ROADMAP's target also retains src/ while describing packages/agent as absorbing
src/adapters. The map must distinguish a retained container entry shim/build input
from absorbed runtime implementation; do not move both copies and create competing
sources. That exact shim boundary and extension distribution placement need Dewey's
reconciliation. No package-manager change or empty package scaffolding is authorized.
## Earlier review-baseline gate (resolved below)
Dewey's #55 reconciliation and ms-archify require commit-pinned code and plan
citations for a formal map. The accepted phase-2 plan and #55 work are currently
uncommitted. Current hashes make this preparatory inventory reproducible, but do
not satisfy the formal commit-pinned handoff gate. No commit/push is authorized
by this goal. Do not label this document an independently review-ready Archify map
yet or use HEAD to pretend it contains the dirty source.
Remaining ready work: inspect config/auth/interface inventories read-only and
specify the first inspector's exact acceptance boundary. External dependencies:
Dewey's package/shim reconciliation and an owner-authorized baseline strategy
before formal independent review. Review dispatch itself is a separate gate.
## Config/auth/interface inventory and first-increment boundary
Read source only; no configuration, credential contents, auth status, login or
refresh operation was accessed/executed during this checkpoint.
| Source | Finding | Disposition |
|---|---|---|
| scripts/mosaic-config.mjs:39-61,76-174 | Config path can be overridden by MOSAIC_CONFIG; regular-file/symlink and strict field checks; canonical data-root checks exclude root/home/config ancestors; lstat errors are treated as missing. | Reuse strict validation and protected-root principles; reconcile the override with sole-config canon rather than silently adopting a second config authority. Distinguish missing from unreadable/error where fail-closed diagnostics matter. |
| scripts/mosaic-config.mjs:194-239 | Bootstrap uses exclusive create, validates existing config without replacement; validate/env expose resolved non-secret fields and shell quoting. | Retain bootstrap-only creation and read-only resolution. Future domain validation must not bootstrap or load live config when running a synthetic inspector. |
| scripts/auth.sh:19-96 | Config-backed account directory and reporting of provider/type/permission metadata. Reads credential JSON when invoked; parse errors include parser text. | Preserve ownership separation, not a proven redaction guarantee. Do not reuse credential-reading report functions in the inspector. Error disclosure and account materialization belong to separate auth review. |
| scripts/agent.sh:28-64 | Per-launch named account checks readability, symlink and mode 0600, exports selected mount source; no project selection flag in this parser. | Reuse explicit refusal rather than account fallback. Replace flat account/path selection with #50 registry bindings at the later auth boundary; introduce full scope at the managed CLI, not by inferring it from cwd. |
### Recommended increment: synthetic scope/permission inspector
Purpose: let Jason see whether one agent's project/workspace membership and
permissions resolve as intended before any live state or worker can be affected.
This is a recommendation for a later charter, not an implementation task started.
Input: one explicit local synthetic bundle containing a coherent graph of agent,
project, two workspaces, registrations, mission/task/assignment and declared mock
policy sources. No live registry, credential, engine history or data-root lookup.
Independent shape fixtures cannot simply be concatenated into this graph.
Output: deterministic text plus structured result, identifying selected agent,
project and workspace, reference errors and the calculated least-privilege result.
Every successful output says preview only: no live registration or permission grant.
Unknown/missing policy is a refusal, never an empty layer skipped during intersection.
Acceptance cases for the implementation charter:
1. Valid bundle resolves the explicitly named first workspace and its permitted read.
2. Same agent, second workspace without registration: refuse without revealing that
workspace's private payload or selecting the first workspace instead.
3. Missing parent, multiple/incorrect ownership, dependency cycle and stale revision:
report the violated rule; do not repair or invent references.
4. Broader task grant cannot widen mission/registration/agent ceilings; another
assignment cannot supply missing permission. Explicit empty grants allow nothing.
5. Ambiguous name, duplicate ID/revision, unknown field and malformed UTF-8/path:
reject before producing a permission preview that appears valid.
6. Source bundle stays byte-identical; no writes to config, data root, .pi/state,
roles or installations; no child engine, network, credential or migration action.
7. Jason runs the positive and negative examples and understands both the scope
display and the preview disclaimer before any dependent increment is chartered.
Proposed exit classes (not installed): 0 valid preview, 2 malformed/invalid graph,
3 simulated permission refusal, 4 input/I/O failure. Final naming/packaging belongs
to the later charter, consistent with packages/config domain ownership and the
packages/mosaic CLI target. No npm/Turbo change is needed to approve this boundary.
Deferred: authoritative publication, real authentication, sandbox tests, process
control, native/managed goal integration, OAuth refresh, live registration, session
adoption, reset/prune changes and repository restructuring. These require their
own dependencies, implementation tests and owner acceptance.
### Config/auth/interface source identities
- `scripts/mosaic-config.mjs`: `430ee6bc4fcfbe4b9ac030aaa19cdb6fdc7407e1b17253b80178b9b0523b5a3a`
- `scripts/auth.sh`: `fe5d3e89272d3b04db687eabed30a95dde480a2f7bc784cd27e43fa9321f15a6`
- `scripts/agent.sh`: `1ad2270a02e5668ef126c2af71a7b70771c2da09842313bdbf6abba84e95b555`
## Owner-reported cross-lane retasking scenario
Jason reports that orch-01 in the separate ~/.mosaic environment redirected two
agents from their owner-set goals into supervisor work for another agent, outside
their lanes. This is owner-reported context, not an independently investigated
incident or a proven root-cause diagnosis. Jason explicitly prohibited involvement
in that environment's failure; no inspection, messaging or intervention there is
part of this goal.
Map to R8/R12/R19/R23/R31/R32: current goal/mission/assignment and scope authority
must be checked when reassignment is requested. A coordinator title, message or
new role description is not authorization. A goal reminder is not an enforcement
boundary. Another workspace's permission cannot be borrowed, and changing a scope
role cannot silently replace reusable identity or the owner's approved intent.
Add this adversarial case to the proposed inspector/implementation acceptance set:
- Agent A has an active owner-authorized assignment in workspace A. A coordinator
from workspace B requests reassignment into supervision for another goal.
- Without explicit applicable delegation and a valid recorded change within owner
intent, refuse the request; preserve A's goal/assignment and report the conflict.
- A message alone cannot mutate assignment, role, goal or acceptance state.
- If a properly authorized change is requested, account for underway effects and
follow reconciliation; never abandon old work merely because a new message arrived.
- Later runtime tests must prove original work remains selected and stale/cross-scope
actions are fenced. An offline preview alone cannot establish this behavior.
## Integrated baseline and reconciled ownership
Source and accepted-plan baseline: d4696d09eb1b5dcf1028f30db2cd63735f51cb16,
whose parent is foundation baseline 44f257cb06484feda3412d9382e3587393796353.
All source file:line citations in this map now refer to d4696d09 unless explicitly
labelled historical. Inspected legacy source bytes are unchanged from 69d1bb3.
The mapping revision is the separate commit containing this map and its handoff;
no circular claim that d4696d09 already contains these mapping documents is made.
Jason authorized scoped local baseline commits. Dewey's MS55-DW-3 receipt was
verified locally: exact parent, 43 allowed paths and an empty released index.
The former baseline-authorization and coordination waits are resolved. No push,
implementation, migration or independent review is authorized by that resolution.
Dewey's MS55-DW-2 qualifications are adopted:
- extensions/** remains current canonical source until explicitly chartered M20
packaging; mosaic-core/lib remains internal support, not another entrypoint.
- Post-M20 src/ retains only unavoidable bootstrap/exec shims. Current material
context behavior migrates/replaces, never duplicates packages/agent logic.
Adapters also have one canonical post-M20 owner under packages/agent.
- packages/mosaic handles presentation/routing, not policy truth. packages/config
owns pure validation and deterministic policy calculation, not publication.
- packages/agent contains runtime coordination and separated writer/recovery
interfaces. A package is not a process trust boundary: engines must not inherit
publisher/supervisor privileges simply by importing the same package.
- packages/auth contains code/metadata, never packaged secrets. Declarative
top-level directories remain authoritative inputs, not generated installations.
The initial inspector remains the recommended bounded increment. No independently
built packages, empty scaffold or package-manager migration is needed now. Exact
extension distribution packaging and any retained shim are implementation-charter
choices constrained by the reconciled ownership rules, not unresolved permission
to create duplicate sources.
Next gate: owner review of the mapped baseline and separate authorization of a
non-author review. This mapping prepares that handoff; it does not supply the
reviewer's verdict or authorize the inspector implementation.
+49
View File
@@ -0,0 +1,49 @@
# Goal quiet waits
## Latest checkpoint, 2026-09-06 09:02 UTC
Jason reports Joe/Huey are "looking good so far" after reloads. This is preliminary positive quiet-wait feedback, not final acceptance. He accepted Resume's NG footer/full recall, authorized fleet ownership correction and scoped suite-gated local commits. #58 now makes the legacy fleet and Velma goal paths compatibility aliases to shared NG and removes the Resume-only workaround. See docs/plans/2026-09-06_fleet-goal-ownership.md for the explicit state-location and no-migration boundary. All 55 live settings/discovery combinations load one shared NG goal with configured guards. The seven-file quiet-wait patch still has its independently approved SHA256 d9caeb0c87e074196421a010e8a2de0968b3bb922010fa864ca2ec1f1902ef6b. Goal, native, package and five repository suites were rerun green. No existing session or private state was changed. #56 remains open for final quiet-wait acceptance; no polling or rearmed timer.
The deployment and authority descriptions below are historical checkpoints. #58 supersedes the three-independent-copy topology and the earlier absence of commit authority.
Owner request: fix the untimed goal-check loop observed in Joe on 2026-09-06. Author: Dewey. Baseline: 7345f330fc6bfae5aa1d896c78cfb7cbe62efbae. Darkwing owns foundation mapping; none of its files are in scope.
## Defect
An accepted untimed wait resets the check counter but does not suppress settle or startup injection. The old quiet-wait test explicitly expects that behavior. Joe's pane showed an accepted wait followed by a new check and a 190k-token cache-miss notice. This is a scheduling defect; a cooldown would merely reduce its frequency.
## Scope and acceptance
- Canonical extension source/tests under extensions/goal, plus this plan and append-only delivery logs.
- All accepted waits suspend automatic checks, independent of timeout configuration.
- Untimed waits create no timer. Unrelated incoming messages and reloads preserve the wait. The model reconciles relevance; the extension never evaluates nextCheck as code or treats incoming text as permission.
- Explicit resume may reconcile a waiting or paused goal once. It clears the old wait; another unresolved report returns to quiet waiting.
- Timed waits retain one deadline wake per goal/resume and their existing abort/readiness/delivery safeguards.
- Guard queued check dispatch against duplicate lifecycle events, without blocking genuine subsequent work or retaining stale dispatches across goal replacement.
- Preserve footer/full recall, completion, pause/blocked, fencing, report-policy and write-failure behavior.
- Verify red-before-green regressions, full extension and repository suites, native no-model checks, independent exact-candidate review, and safe local test installation.
## Delivery boundary
No live fleet files, private goal state, user goals, or sessions are modified during development. The active operator goal explicitly authorizes local-first deployment under ~/.mosaic after verification. Do not automatically resume Joe or reload other seats. Before deployment, compare the actual shared entrypoint and every independent active copy with the reviewed baseline, preserve their state location, pin the file set, save rollback copies and verify native loading. The legacy fleet/extensions copy is distinct from the current .mosaic/.pi/extensions target; do not replace it merely because it exists. No push, CI/merge claim or foundation implementation. This is the operator-requested local development/deployment path, not a published release.
## Tasks
1. Reproduce untimed settle/startup injection and record red tests.
2. Fix wait gating, explicit resume and dispatch deduplication; verify regressions.
3. Review and test the candidate, including fresh native installation.
4. Deploy the pinned candidate to verified current targets, verify symlink resolution and a no-model native canary using isolated fixture state. Report reload requirements without driving existing sessions. Owner acceptance cannot be inferred from tests.
Status: deployed on disk and native-verified; ready for operator reload and acceptance. Issue #56: https://git.mosaicstack.dev/mosaicstack/stack-v2/issues/56. Four red regressions reproduced the old behavior; 71 goal tests, 18 package controls, native Pi timed/untimed Waiting fixtures, and all five repository suites pass. Independent read-only Pi reviewer approved the exact seven-file source/test/documentation candidate at 2026-09-06 07:50 UTC; transcript /tmp/goal56-review.log. Static TypeScript compiler remains unavailable; no claim of static checking.
At 07:51 UTC, the shared .mosaic/.pi/extensions/goal entrypoint, state and settle files matched HEAD byte-for-byte before the fix. Fifteen role extension links resolve to the shared root; Topher links through interact. Velma has an ordinary independent copy whose three runtime files also match HEAD. Joe's running process has cwd ~/.mosaic and no explicit extension flags. A broad initial symlink scan encountered a denied backup subtree; the bounded active roles/agents extension scan completed. The scratch pi-goal-x experiment is not a current shared target. Initial symlink-only inspection was insufficient: a subsequent settings scan found 41 agent and 14 role settings files still referencing legacy fleet/extensions/goal, including Joe. That path therefore also requires the scheduling backport, preserving its distinct state location and widget UI.
Deployment gate, 2026-09-06 08:00 UTC: canonical 71 tests and staged legacy 67 tests pass. The first full legacy staging run had two ENOENT errors because its external-relative skill fixtures were absent; copying the existing canonical fixtures into the expected staging location resolved them without changing live skills. Shared/Velma have an older README and no fixture .gitattributes; runtime files match the baseline. These differences were diagnosed and the actual whole-file trees pinned rather than bypassing a drift refusal. Independent deployment/backport reviewer APPROVE: /tmp/goal56-deploy-review.log.
Pinned deployment input: .pi/evidence/goal56/deployment-pins.json, SHA256 08aec1a0ff7c70ce2a5e1b6831c8ec395a94a2bafe9ca1be9c7110a6b913935c. One-shot deployment script: .pi/evidence/goal56/deploy.py, SHA256 248ec09b534f067d296fc70f2ce50b11810c9927321a58205e860eb19b39684a. Preflight and an isolated atomic-exchange/rollback test passed. Deploy only these three ordinary goal directories; core, settings, symlinks and existing goal states remain untouched. Deployment completed by atomic exchange. Rollback trees and write-once receipts: /home/jwoltje/.mosaic/.pi/goal-backups/goal56-60590600f2c9420a881f1f4f0a5fc5cf. All three deployed trees match their approved after-pins; all three backups match before-pins. All fifteen role links and Topher's agent link resolve to the patched shared tree. Six native Pi RPC canaries loaded the three actual installed entrypoints, timed and untimed: exactly one explicitly selected /goal per isolated canary, zero automatic checks/model starts, and no fixture state changes. Provider credentials were not inherited. All owned fixture states were removed. This does not assert ambient multi-extension discovery is conflict-free, nor that already running sessions have reloaded.
At 2026-09-06 08:02:45 UTC the canonical source still matched the deployed shared/Velma candidate. Canonical working-tree patch: .pi/evidence/goal56/canonical.patch, SHA256 d9caeb0c87e074196421a010e8a2de0968b3bb922010fa864ca2ec1f1902ef6b. Source remains uncommitted; no new commit/push authority was inferred from the earlier baseline authorization. No existing process was interrupted, reloaded, resumed or given a replacement goal. The legacy fleet copy preserves its original store and UI; core, settings and links are unchanged.
Remaining user-facing gate: Jason chooses a safe idle session, runs /reload, and confirms an accepted untimed wait remains quiet. If the chosen goal is paused, resume only when its assignment is still authorized; /goal resume deliberately reconciles once. Do not set a test goal over another agent's assignment. A relevant input should permit verified progress; unrelated input should preserve waiting. This is a manual operator acceptance wait using incoming messages, not a polling watch or another timer. No reload or owner approval is inferred from the native canaries. #56 remains open until that confirmation. The extension's one bounded deadline wake arrived after the 08:06:21 UTC deadline with no operator confirmation or defect report in the conversation. Acceptance remains blocked; no timer was re-armed and no session was inspected, reloaded or resumed. Resume this task on explicit operator confirmation or a defect report.
Communication: darkwing closeout MS55-DW-3 confirms its baseline and released index; no reply requested. The separate fix notice returned rc=2 (delivery unconfirmed); no blind resend. The d4696d09-pinned map remains historical.
@@ -0,0 +1,94 @@
# Replacement repository source layout
Tracking issue: [#55](https://git.mosaicstack.dev/mosaicstack/stack-v2/issues/55)
## Authority and scope
Jason requested canonical codebase storage for extensions, skills, and other components, with phased replacement of mosaicstack/stack. Source organization does not authorize deployment, credential migration, commit, push, or changes to the live fleet. The accepted goal extension is the first migration candidate. This record is an investigation and recommendation, not an implemented move.
## Measured legacy structure
Read through the Gitea contents API on 2026-09-06 at 06:14 UTC. Requests returned HTTP 200.
- `apps/` contains appservice, gateway, and web.
- `packages/` contains shared domains including agent, auth, brain, comms, config, coord, db, forge, log, memory, queue, storage, and types.
- `plugins/` contains discord, macp, mosaic-framework, and telegram. These names alone do not establish that these plugins are Pi extensions.
- Root also has `skills/`, `tools/`, `scripts/`, `profiles/`, `infra/`, `docker/`, and documentation.
- Root package manifest is private, declares pnpm 10.6.2, and delegates build, lint, typecheck, and tests to Turbo.
- Workspace membership is explicit: apps/*, packages/*, plugins/*, and tools/matrix-presence-harness. Skills are not declared as JavaScript workspace packages.
- Turbo builds dependency packages before dependents and records dist/** as build output. Tests and typechecking depend on dependency builds.
Inspected manifest blobs:
- package.json: `5910f98225226274141a6da4c81098fa122f3f46`
- pnpm-workspace.yaml: `abb7a60e203c6d44dcf8dc9bf7c99cb3cb72cbd8`
- turbo.json: `cf8ef998ee52391cc581c16c865f6d0c0066b24f`
The recursive `main` tree response identified commit/tree `7102ccb93e007d98faab3994ea552d03f522ad2c` but was truncated at 1,000 entries. Follow-up contents reads supplied complete targeted directories. This is a targeted structural and packaging inspection, not a complete legacy inventory.
## Recommendation for stack-v2
Adopt the monorepo principle now, not the entire legacy directory tree or build system.
- `extensions/goal/` becomes canonical goal source and tests.
- `extensions/mosaic-core/lib/` initially holds its existing supporting dependencies. This does not activate Mosaic Core as a separate extension.
- Existing `skills/`, `contracts/`, `adapters/`, `roles/`, `scripts/`, and `docs/` retain their distinct responsibilities.
- `.pi/extensions/` becomes a generated native-test installation. Never edit it as canonical source. Populate it by deterministic ordinary-file copying, not symlinks.
- `.pi/state/` remains ignored runtime state and must never enter an extension artifact.
- Introduce apps/ or packages/ when an actual service or shared package requires them. Do not create empty placeholder packages or rename existing components merely to resemble the old repository.
- Package explicit source inventories and dependency versions. Record artifact hashes and fail rather than overwrite unknown local edits in the development installation.
The current private npm manifest and pinned Pi dependency do not need to become pnpm/Turbo merely to store one extension. Decide workspace tooling when independently built JavaScript packages justify it. Native Pi 0.85.1 testing versus the container's 0.84.4 pin is an unresolved compatibility gate before managed-runtime promotion.
## Measured legacy packaging and installation
Targeted Gitea reads returned HTTP 200 for the installer, release verifier, publish pipeline, publishing helper, and representative manifests.
- Shared packages such as `@mosaicstack/agent` build TypeScript into `dist/`, publish only `dist`, and use explicit workspace dependencies.
- The `@mosaicstack/mosaic` package publishes both `dist` and `framework`. This is the legacy bundle for CLI plus framework resources.
- `plugins/mosaic-framework` declares an OpenClaw extension in its own package manifest. It is not a Pi extension package.
- The main installer separates framework files under `~/.config/mosaic` from the globally installed npm CLI. Its development mode builds the monorepo, packs local tarballs, and installs those tarballs without registry writes.
- The publish pipeline installs from a frozen lockfile, verifies that CI's commit equals checkout HEAD, runs one terminal release verifier, builds, and only then publishes. Registry, authentication, network, and unknown publication failures fail the job. The `next` lane snapshots and restores modified manifests.
- A separate npmjs helper mutates manifests temporarily and restores them. It tolerates publish failures broadly, so it is not a model for stack-v2's fail-closed publication path.
The useful pattern is source, build output, installation, and publication as separate states tied to explicit manifests and immutable identity. The volume and complexity of the old framework package should not be copied into the first stack-v2 extension increment.
## Phases
1. Source ownership: move accepted goal source, dependencies, and tests into canonical directories; preserve source provenance and user acceptance evidence.
2. Native development packaging: deterministic install into .pi, drift checks, source/package tests, explicit runtime version compatibility, and repeat native acceptance if behavior changes.
3. Managed runtime integration: approved extension allowlist, image packaging, policy compatibility, isolation, and rollback checks. Docker remains deferred until authorized.
4. Component-by-component legacy replacement: inventory dependencies, contracts, state ownership, acceptance criteria, and rollback for each component before migration. No wholesale adoption of operational state.
## Implemented first increment
The accepted goal extension now follows the recommended ownership boundary.
- Canonical source and tests are under `extensions/goal/`.
- Imported support modules are under `extensions/mosaic-core/lib/`; no second entrypoint exists.
- `scripts/sync-dev-extensions.sh` stages and verifies ordinary-file copies in `.pi/extensions/`. It records the installed manifest, rejects source symlinks or extra entrypoints, detects installation drift, and refuses to overwrite it.
- `scripts/goal-dev.sh` synchronizes before launching isolated native Pi. `.pi/goal-dev.sh` remains a compatibility wrapper for the accepted test command.
- `scripts/test-extension-package.sh` runs 18 controls. It covers initial installation, full-tree equality, extra destination entries, source and destination symlinks, local drift, canonical updates, nested entrypoints, active and stale locks, and interruption rollback/recovery.
- `.pi/extensions/`, state, generated manifests, and local evidence are ignored. They are installations or runtime evidence, not canonical source.
Verification after the move passed: 67 goal tests, all 18 development-package controls, and native Pi PTY checks at 45 and 120 columns for Waiting, Paused, Blocked, Complete, bare `/goal`, Alt+G, and NO_COLOR. A separate read-only reviewer first requested stronger full-tree, nested-entrypoint, interruption, and lock controls. After correction, the reviewer returned APPROVE and identified each prior finding as closed. The exact canonical-source manifest is `.pi/evidence/canonical-source.sha256`, whose SHA-256 is `c2096c00fb505a53482ff6a94a0f293dbc1ca2058d7a1996961601af80c29946`.
No package-manager change, npm publication, Docker inclusion, live-fleet installation, commit, or push is part of this increment. Unrelated CURRENT.md work remains untouched.
## Replacement method
Use the repository as a component monorepo with explicit boundaries, not as one undifferentiated package. Keep declarative resources in their existing top-level directories. Add executable packages or apps only when they have independent build and dependency needs. Every migrated component gets canonical source, tests, a deterministic development installation, an explicit release inventory, exact runtime compatibility, independent review, and user acceptance before the old implementation can be retired.
For Pi resources, the Pi package format supports explicit `pi.extensions` and `pi.skills` paths. Do not publish the entire private repository by convention. When external distribution becomes necessary, create a bounded package manifest or staged artifact that names only approved resources and declares Pi APIs as peer dependencies. The current sync installation is deliberately narrower and precedes that release decision.
## Coordination request
`MS55-DW-1` asks darkwing, the current owner of CURRENT.md and issue #53 phase-2 planning, to reconcile the next foundation goal before Jason sets it. Requested return: darkwing's intended technical-mapping scope, paths this session must avoid, and a recommended division of work that incorporates #55 without overlapping writers. Destination is the default tmux session `darkwing`; follow-up owner is this assistant. `tools/tmux/agent-send.sh` returned rc=2 at 2026-09-06 06:55 UTC: it could not confirm submission after three attempts and reported that the message may be undelivered. Delivery is unknown, so this session will not resend without reconciliation. No source or shared-plan work starts from this request.
## Reconciliation finding
CURRENT.md says issue #53 phase 2 is accepted and awaits Jason's separate authorization for technical mapping. ROADMAP.md also records an earlier owner decision: the succession target uses `packages/*`, while broad restructuring waits for M20 because moving current bash entrypoints sooner would create two migrations. The #55 `extensions/` location is therefore a valid canonical source improvement now, but it cannot silently become the final post-M20 package layout. Jason's latest direction does not explicitly revoke the earlier sequencing decision.
The next safe step is a coordination and baseline goal, not another code move. It should reconcile the `extensions/` staging location with the `packages/*` target, assign non-overlapping writers for the current dirty tree, and define the exact committed revision that a later as-built/planned map will cite. Archify mapping requires a commit SHA and will not treat the current dirty working tree as evidence.
Darkwing replied directly to `MS55-DW-1` at 2026-09-06. The reply supports an evidence-backed current-versus-planned component/package map with #55 as input and no runtime implementation. Agreed ownership: darkwing owns requirement-to-code mapping and CURRENT integration; Dewey owns source, package, installation boundaries, and phased inventory in this #55 plan or a separately agreed file. Darkwing will avoid `extensions/**`, `.pi/**`, #54/#55 plans, and the four extension development scripts. Dewey will avoid CURRENT.md, the three foundation/phase-2/schema plans, and `foundation-v1-candidate/**`. Shared logs remain append-only. Both sides await Jason's explicit mapping authorization. A non-author must review the resulting candidate. The acknowledgement send returned rc=2 after three unconfirmed attempts; delivery is unknown and it was not resent.
+135
View File
@@ -0,0 +1,135 @@
# NG goal footer development plan
Issue: [#54](https://git.mosaicstack.dev/mosaicstack/stack-v2/issues/54)
## Outcome
Develop a repository-local copy of the Mosaic `/goal` extension for native Pi launches from this repository. Goal state appears in Pi's built-in footer as `Goal: <State>`. Bare `/goal` and a keyboard shortcut show the full stored goal text without truncation.
This is NG development. It does not change or reload the live extension under `~/.mosaic`, and it does not add the extension to the Docker launch path.
## Baseline and ownership
- Repository baseline at intake: `69d1bb3` with unrelated, preserved phase-2 planning changes in the working tree.
- Source snapshot: `~/.mosaic/fleet/extensions/goal/` plus the imported `mosaic-core/lib/` modules, copied as ordinary files.
- Initial development location: `<cwd>/.pi/extensions/`; canonical source moved to `extensions/` after acceptance.
- Author: this native coding session, `01a07506-f3c7-76ff-a725-b3c5e2086f31`. The operator directed completion after the setup-only checkpoint. The worker declaration remains undispatched.
- Independent reviewer: a non-authoring reviewer seat on the exact candidate.
- Product acceptance owner: Jason.
The source snapshot is not evidence that the external extension is safe to change. All changes remain inside this repository; nothing writes through to the external source.
## Requirements
1. Native Pi discovers the project extension through `.pi/extensions/*/index.ts` after project trust is granted.
2. No goal widget appears above the editor.
3. Pi's built-in footer displays a textual state. Color reinforces the text but never carries the meaning alone.
4. State presentation uses Pi theme roles:
- `Active`: `accent`
- `Waiting`: `warning`
- `Paused`: `warning`
- `Blocked`: `error`
- `Complete`: `success`
- `None`, if shown: `muted`
5. Bare `/goal` displays the complete stored goal text. It may format or wrap the text, but it must not shorten it.
6. A non-conflicting shortcut displays the same complete status. Tab remains Pi autocomplete because Pi's public status API does not expose focusable footer items.
7. Waiting, blocked, and ordinary pause are distinguishable. A completed goal remains recallable without reactivating its continuation loop.
8. Existing continuation, report, wait, abort, persistence, and incarnation-fencing behavior stays intact.
9. Extension failures must not alter the live `~/.mosaic` files or operator goal state used by other agents.
## State design
The current source has `active`, `paused`, `blocked`, and `none` in its type, but blocked reports currently become paused with a `blocked:` reason. Satisfaction clears the state immediately. The implementation must add an explicit display projection rather than infer every label in the renderer.
Preserve active-loop semantics. Store a bounded terminal outcome record for recall after completion, including the full goal text and terminal state. Do not treat `Complete` as an active goal. Existing version-1 state files must load without losing their active or paused goal.
Bare `/goal` must use the full state text. Short text remains acceptable in transient notifications that are not the recall command.
## Interaction design
Use `ctx.ui.setStatus("goal", ...)` so the goal indicator composes with Pi's default footer. Do not replace the entire footer. Remove the project copy's `setWidget` registration and clear any stale goal widget during startup and reload.
Register a direct recall shortcut. `Alt+G` is the initial candidate, subject to a native terminal conflict check. The shortcut invokes the same full-status formatter as bare `/goal`; it does not insert text into the editor or start a model turn.
## Tasks
| ID | Owner | Work | Exit evidence |
|---|---|---|---|
| NG-GF-1 | Conductor | Copy the goal directory and imported mosaic-core library into `.pi/extensions/` with no symlinks; record source and destination hashes. Do not copy `mosaic-core/index.ts`, because Pi would auto-discover and execute that separate policy extension. | File inventory, no-symlink check, matching snapshot hashes, and one discovered goal extension. |
| NG-GF-2 | Author worker | Implement display-state projection, terminal outcome recall, colored footer status, full `/goal` output, shortcut, and focused tests. | Clean patch plus focused test and type-check output. |
| NG-GF-3 | Independent reviewer | Review the exact candidate for state migration, loop regressions, extension isolation, keyboard behavior, and hostile state inputs. | Written verdict tied to the candidate hash. |
| NG-GF-4 | Conductor | Run repository suites and extension tests; verify no path resolves into `~/.mosaic`. | Green command output and path/hash checks. |
| NG-GF-5 | Jason | Exercise native Pi at narrow and wide widths, active/waiting/paused/blocked/complete states, bare `/goal`, shortcut, and color-disabled mode. | Explicit user acceptance or findings. |
Worker declarations are `tasks/ng-goal-footer-implementation.json` and `tasks/ng-goal-footer-review.json`. They are not dispatched until the conductor provides an isolated exact candidate workspace. The current dirty repository is not silently adopted as a worker workspace.
## Verification
Automated checks:
- strict task validation;
- extension unit and runtime tests;
- state migration fixtures for old version-1 records;
- footer text and theme-role assertions for every state;
- full-text equality for `/goal` and shortcut recall;
- no `setWidget` call for goal state;
- no symlinks or source paths resolving into `~/.mosaic`;
- existing repository suites;
- `git diff --check` and prose checks.
Native TUI checks:
- normal and narrow terminal widths;
- keyboard-only recall;
- project reload and session replacement;
- default theme and color-disabled output;
- stale widget removal;
- complete and blocked states remain textually identifiable.
The footer uses native terminal theme colors. Browser-specific WCAG contrast measurement is not applicable, but status meaning must remain available without color and in `NO_COLOR` mode.
## Security and rollback
Project-local extensions execute with the native Pi process's host permissions. The operator must explicitly trust this project. This test does not prove the managed execution, command recording, credential, or isolation requirements in #53.
Development-install rollback removes generated `.pi/extensions/` plus NG-only test state. Source rollback reverts the separately scoped canonical `extensions/` change; it never edits the external `~/.mosaic` extension.
## Setup checkpoint
NG-GF-1 completed on 2026-09-06. The copied goal tree hash is `8853f2b72dde3e87c4573648b9a931c1c75da87ccde995c3224e6d2e707a75f0`; the copied mosaic-core library hash is `d1194dce31209e5773c6cc5ce571cbca3c39b29d943a79dea06665e05d29f319`. `.pi/SOURCE-SNAPSHOT.json` records the snapshot. A no-symlink check passed, and only `.pi/extensions/goal/index.ts` is auto-discoverable.
An isolated native Pi RPC launch with `--approve`, a temporary `PI_CODING_AGENT_DIR`, no session, and no tools reported exactly one project-scoped `/goal` command from this repository's `.pi/extensions/goal/index.ts`. No extension error appeared.
The five self-contained goal suites pass 55 tests. The first broad copied-source run also exposed two repository-context tests that expect `skills-local/` files from the source brain. They remain failing in this repository and are not counted as green. Copying all of mosaic-core initially also exposed that its separate `index.ts` would be auto-discovered and that its tests require source-brain role manifests. Setup was corrected before the discovery smoke test: only the imported mosaic-core library remains. No live extension file was changed.
## Delivery gates
Implementation is not done at setup. It proceeds through author checks, independent exact-candidate review, repository checks, and Jason's native TUI acceptance. Docker promotion, release packaging, and edits to `~/.mosaic` are excluded from #54.
## Implementation checkpoint, 2026-09-06
The local implementation is available for hands-on testing. Start `bash .pi/goal-dev.sh` from this repository. It loads only the local goal extension, preserves native provider authentication, and puts goal state and conversation files under `.pi/state/`. It neither copies credentials nor loads the live fleet extension. Plain native Pi also discovers the project copy after trust, but the launcher prevents global extension collisions.
- NG-GF-2 implemented by the current native session. Footer status replaces widget content; stale widgets are cleared. Bare `/goal` and Alt+G share full-text recall. Complete retains full goal text and bounded evidence without an active continuation loop. Blocked has its own state and supports explicit resume. Old version-1 state remains readable.
- NG-GF-3 has an independent read-only native reviewer APPROVE. The reviewer inspected the entrypoint, display, state, store, and launcher but did not rerun tests. The first review attempt timed out without output; a second process with a ready configured provider returned the verdict. No worker container was dispatched.
- NG-GF-4 author checks passed: all 67 goal tests and repository suites config 24, task 90, release 14, conductor 17, auth 15. Native Pi 0.85.1 PTY checks passed at 45 and 120 columns for paused, blocked, complete, waiting, bare recall, shortcut recall, and one color-disabled case. Native discovery found exactly one local goal command. No symlinks. Live extension hashes still match the source snapshot.
- The source-brain test failures are resolved with ordinary local contract fixtures. The executive-update fixture matches its pinned SHA-256. Tests no longer need live brain reads.
- Static TypeScript checking was not run because no compiler is installed. Native loading and Node TypeScript execution passed, but they are not a substitute for static checking.
- NG-GF-5 remains open for Jason. Instructions and expected results are in `.pi/README.md`. No user acceptance, commit, push, Docker integration, or release completion is claimed.
Evidence is in `.pi/evidence/README.md` and its logs. The reviewed candidate file manifest hashes to `10e949db54149c6bed945fab086ad7a91461683ee5661c920f9ee6bddcf04aae`. No reviewed source file changed between review and manifest recording.
Goal state: waiting for manual user test. Next action: Jason launches `bash .pi/goal-dev.sh`, exercises full recall and the footer, and returns acceptance or findings. Issue #54 stays open. This checkpoint does not advance or change issue #53 or CURRENT.md. No automatic wake is registered.
## User acceptance, 2026-09-06 06:05 UTC
Jason replied "It works" to the native test handoff. NG-GF-5 is accepted for this local development test. This supersedes the manual user-test wait above; it does not establish separate measurements of every test case.
No further feature work is requested. Static type checking remains unperformed. Commit, push, Docker integration, and live fleet changes are not authorized by this acceptance. Issue #54 remains open for repository delivery disposition; CURRENT.md and unrelated work remain unchanged.
## Canonical source migration, 2026-09-06
At Jason's direction, the accepted implementation moved from development-only source ownership to the repository's component structure. Canonical files now live under `extensions/goal/` and `extensions/mosaic-core/lib/`. `.pi/extensions/` is generated and ignored.
`scripts/sync-dev-extensions.sh` performs staged ordinary-file installation with content verification and refuses local drift, symlinks, or an extra extension entrypoint. `scripts/goal-dev.sh` runs this sync before native Pi; `.pi/goal-dev.sh` remains a compatibility wrapper. The native test moved to `scripts/test-goal-native.py`. This source-only relocation does not change accepted behavior, package the extension into Docker, publish npm, or touch the live fleet.
@@ -0,0 +1,27 @@
# Resume goal discovery repair
## Accepted outcome, 2026-09-06 09:02 UTC
Jason explicitly confirmed the NG footer and Alt+G full recall. The Resume-specific workaround was then superseded by the authorized fleet fix in #58. Normal discovery now selects the shared NG source through compatibility aliases; the common launcher is restored to its original version with unslop retained, and wrapper/core enforcement stays as configured. All 55 live settings/discovery combinations pass. No settings, credentials or private state were migrated. See docs/plans/2026-09-06_fleet-goal-ownership.md. Resume's accepted defect correction is ready for #57 closure after the scoped commit. Fleet rollout and quiet-wait acceptance remain separate gates.
## Earlier correction — 2026-09-06 08:34 UTC
Jason rejected the legacy selection below and explicitly directed use of the shared NG extension. Resume now selects ~/.mosaic/.pi/extensions/goal with wrapper-guard and unslop retained, exactly once. No state migration or private-state writes. Independent correction review APPROVE; native selected-resource checks verify NG Alt+G and both guards; 71 goal tests and native footer/full recall pass. Installed NG runtime matches tested canonical source. Deployed launcher SHA256 dd9e5ece5f1a86cc286027668560198bb7a44d2ab891f7b1ab4f89d7379ca0e6. Backup: ~/.mosaic/.pi/goal-backups/goal57-shared-4d95a698f4bb4544a0cb90aed5e887a3/launch-seat.sh. Evidence: .pi/evidence/goal57-shared/. Other seats remain unchanged. Restart Resume through its normal launch command when safe; /reload alone in an already running legacy-selected process does not replace its CLI extension selection. Awaiting user retry.
## Earlier repair — legacy selection rejected by owner
Jason requested repair after Resume launch failed with duplicate goal_report registration. Dewey owns this bounded hotfix; foundation/CURRENT and existing agent sessions remain untouched.
The real native loader reproduces the collision when project discovery and Resume's explicit legacy extension are both enabled. A global-settings exclusion of the project entrypoint was tested and failed: Pi applies project auto-discovery exclusions in project scope. No live settings were changed for that experiment.
Chosen repair: Resume-only explicit extension selection in the shared launcher. Retain its declared legacy goal and wrapper-guard with explicit -e arguments while disabling automatic extension discovery for this seat. The launcher's existing explicit unslop hook remains enabled. This is not bare pi -ne: all three existing safeguards are explicitly retained. Refuse if Resume's declared extension list/packages drift from the reviewed two-resource configuration, or if a selected entrypoint is missing. Preserve the legacy state location, all settings/credentials, and all other seats' launch behavior.
Scope: one bounded block in ~/.mosaic/fleet/bin/launch-seat.sh, a canonical native discovery regression script, this plan, and append-only logs. Do not change per-seat shim conventions, mutate private goals, alter credential refusals, migrate state, restart seats, or disable safeguards. Broad fleet source/state unification is not part of this targeted repair; other seats are not silently redirected between the shared and legacy goal stores.
Acceptance: native negative control reproduces the original combined-discovery error; repaired selection loads exactly one goal_report and /goal, retains wrapper tool interception and unslop extension, and produces no loader errors. Validate shell syntax, exact candidate independent review, an atomic backup/replace with drift refusal, and post-install discovery. Do not launch the real auth-seeding pipeline merely to test resource selection. User retries the unchanged Resume launch command for final acceptance.
Status at 2026-09-06 08:29:51 UTC: deployed, ready for Resume retry. Issue #57: https://git.mosaicstack.dev/mosaicstack/stack-v2/issues/57. Independent reviewer APPROVE (.pi/evidence/goal57/review.log). Nine isolated selection controls passed (valid config plus refusals for extension/package drift, invalid JSON, additional project settings/resources/core entrypoint, automatic user extension directory, missing wrapper). Huey's extracted launcher arguments were unchanged. Native negative control reproduced the original collision; post-install native resource loading proved exactly one legacy goal_report and /goal, registered wrapper tool interception, unslop loaded, and no loader errors. A separate real Pi RPC CLI process using the repaired three-extension arguments started successfully without credentials, sessions or model turns. All 71 goal tests still pass; shell syntax and whitespace checks pass.
Launcher before SHA256: 9352feed0acf9d449c26c0556ba00aba1c65ded43d376930a39ff6b5cee21986. Reviewed/deployed SHA256: 9f5c987bb745b8edb6c2093f0dc4990f48229c0e429fbf388feda040cb2bfacb. Verified backup: /home/jwoltje/.mosaic/.pi/goal-backups/goal57-45d15b9891ff4ef392961615abefc8e7/launch-seat.sh. Atomic replacement preserved mode and refused concurrent drift. Rollback requires matching that deployed hash before atomically restoring the verified before-file; do not overwrite another owner's later launcher changes.
Only Resume's launch path changes. Other fleet seats may still have the duplicate-source configuration; do not describe this as fleet-wide consolidation. Existing running sessions were not touched. No goal state, settings, credential files, per-seat shims, source extensions, or foundation files were edited. No commit/push authority inferred. Evidence: .pi/evidence/goal57/. User acceptance: retry ~/.mosaic/fleet/agents/resume/launch.sh normally, without manually adding -ne. The existing credential warning remains correct and fail-closed. #57 stays open pending that retry.
@@ -0,0 +1,873 @@
# Workspace schema and execution audit discussion
Status: proposed details for owner review, not an implementation contract.
Date: 2026-09-06. Parent plan:
[Agent, project, and workspace foundation](2026-09-06_agent-project-workspace-foundation.md).
Tracking: `mosaicstack/stack-v2#53`.
Field names, enums, enforcement mechanisms, and storage suggestions in this
file are proposals. Behavior explicitly recorded as an owner ruling is agreed;
exact transition schemas remain proposed. JSON blocks are illustrative records,
not JSON Schemas. Requirements R1-R33 come from the owner discussion in the
parent plan.
Section 2.1 records the 2026-09-06 owner ruling; its field names and hash
mechanics are still proposals, not additional approved schema decisions.
Phase 2 now authorizes detailed drafting and read-only investigation. See the
[Record and operation contract candidate](2026-09-06_foundation-phase2-contract.md).
Adopting a schema or changing runtime behavior still requires later approval.
## 1. Identity and ownership
Proposed rule: stable IDs identify records; display names are editable labels.
Names such as `code-be-01` remain useful to people but are not sufficient to
identify an execution attempt. Use explicit composite references, not a
flattened name such as `agent-project-workspace` that can be misparsed.
| Record | Proposed required information | Owner and scope |
|---|---|---|
| Agent definition | Schema version, agent ID, name, type, harness reference, SOUL reference, role/policy reference, configuration revision | Reviewed shared identity/configuration. No mission progress or project-specific chat. |
| Project | Version, project ID, name, policy reference, revision | Authorized project administration. Contains workspaces; references registered agents. |
| Project registration | Project ID, agent ID, scope role reference and narrowing restrictions, status, revision, granting actor and decision reference | Authorized administrator. Explicit visibility and participation grant, not an agent self-enrollment claim. |
| Workspace | Version, project ID, workspace ID, name, revision, logical file/state references | Authorized workspace administration. Exactly one parent project, confirmed by owner Q5. |
| Workspace registration | Project ID, workspace ID, agent ID, scope role reference and narrowing restrictions, status, revision, granting actor and decision reference | Requires valid project registration. May narrow but not widen it. |
| Mission | Version, mission ID, owning scope, objective, acceptance criteria, status, revision | An authorized work owner. Scope and single-parent relationships are decided in D1; exact fields and lifecycle transitions remain to be specified. |
| Task | Version, task ID, optional mission reference, owning workspace reference, purpose/acceptance criteria, dependencies, status, revision | Work owner sets intent; Q10 permits lightweight assignments without lengthy mission setup. Exact ad-hoc task shape remains proposed. Execution results do not automatically imply acceptance. |
| Assignment | Version, assignment ID, task reference, agent ID, full scope, revision, active claim reference | Work coordinator. Distinct from the reusable agent definition and from one attempt to execute it. |
| Session | Version, session ID, agent/project/workspace IDs, harness session reference, creation mode, predecessor reference when applicable | Workspace-scoped conversation metadata. Scope is immutable. Does not store credentials. |
| Execution | Version, execution ID, full scope, session ID, launch decision, claim ID/generation, start evidence, resolved-context reference, launch/configuration hash references | Runtime-created attempt record. Resume retains session ID but creates a new execution ID. |
| Connection | Version, connection ID, authenticated client actor, session/execution references, observer/controller mode, control generation, admission decision, lifecycle | Proposed representation of Q16. One controlling interface; observation and transfer require permission. Connection is not another agent execution. |
| Context manifest | Version, immutable manifest ID, source references/revisions, selected assignments, resolved skills/policies, prior-session inclusion rule | Launch resolver. Records exactly which approved inputs were supplied. |
| Event | Version, event ID, event kind, subject references, actor/execution identity, observation source, outcome/evidence references | Trusted producer, append-only. An agent's narrative is not execution evidence. |
A field that is optional needs an explicit absence rule in the eventual
schema. Do not use a missing grant to mean unrestricted access, an absent
workspace to mean the last-used workspace, or a missing task to mean any task.
Owner Q10 permits conversation and allowed workspace inspection without a
mission/task, but scope, permissions, and session/execution identity still
apply. Requests to change things become explicit recorded assignments without
lengthy mission setup. Proposed task representation permits no mission reference
for such work, while requiring purpose and acceptance criteria. Missing mission
is not an unrestricted grant. Taskless read/conversation event fields and the
first guaranteed action-audit set remain D11.
Proposed formal-schema requirements:
- Every record has a version; reject unsupported versions and unknown fields.
- References resolve to the intended scope and permitted record version.
- Workspace ID alone is never a sufficient cross-project address.
- Parent and membership status are checked, not just file existence. Owner
Q5 requires single-parent hierarchy: each workspace has exactly one project;
each workspace mission has at most one parent project mission, with standalone
missions still owned by their workspace. Dependencies are separate typed
references, not parents, ownership duplication, or access grants.
- Display names never authorize access or select paths by themselves.
- IDs and relative paths reject traversal, separators where inappropriate,
symlink escape, and collisions. Missing targets refuse; do not auto-enroll.
- Changed declarative records create new revisions. Immutable snapshots retain
the exact revision used by a launch. Authority cannot change through state.
- Final schemas must specify timestamp format, ID generation, uniqueness,
array limits, status transitions, null/absence rules, and secret exclusions.
### 1.1 Scope permission roles, owner Q26
Use standard permission roles to simplify project/workspace registration, with
registration-specific restrictions that narrow them. Reader, contributor,
reviewer, and coordinator were examples, not an approved role catalog.
A scope role grants operations in the named scope; it does not change the
agent's identity/type or expand reviewed role authority. Grants remain bounded
by agent, project, workspace, mission, and task policy where applicable. Missing
scope membership still refuses access. A reviewer role does not authorize
accepting the holder's own delivery. Grant changes and role-definition changes
are different operations; shared authority changes still require reviewed
commits under repository policy.
Exact role names, permission sets, catalog representation, membership fields,
and enforcement remain D3. Proposed references in section 1 are not an approved
RBAC schema or a new configuration location.
## 2. Launch request and resolved context
Illustrative request for a new conversation continuing established work:
```json
{
"launchVersion": 1,
"requestId": "req-001",
"agentId": "code-be-01",
"projectId": "mosaic-stack-v2",
"workspaceId": "sessions",
"conversation": {
"operation": "fresh"
},
"work": {
"operation": "continue",
"assignmentIds": ["assignment-014"],
"expectedStateRevision": 7
}
}
```
Names are illustrative IDs. Authentication is not established by this JSON.
The service must attach the verified requesting user/service identity from
the authenticated invocation. A client-supplied actor field cannot grant
permission. `requestId` detects duplicate requests; its exact retry semantics
and storage lifetime remain D8.
Resume instead names `conversation.operation: resume` and an exact `sessionId`,
or uses a documented unambiguous current-session lookup in the same scope.
Owner Q4 ruling: if that scope has never had a conversation, default launch
creates the initial conversation and announces it without an offer. Record
initial creation as distinct from a successful resume. An established session
that is missing or damaged causes an error, not automatic replacement. How
first-use history is established and retained needs a storage/recovery design;
absence of a transcript file alone is insufficient proof of first use.
Fresh allocates a new session ID. Selecting work requires explicit assignment
or task IDs; the exact record fields remain to be specified. Owner Q8 limits
default Abandon to this agent's selected assignments; underlying work remains
unless separately cancelled with authority. Q9 requires an explicit authorized
assignment change before undertaking an unfinished prerequisite, not automatic
addition to the selected work. Authorization can be delegated within the
approved plan under Q7; it need not involve a human prompt for every change.
Proposed launch sequence:
1. Authenticate the requester and validate the entire request.
2. Resolve the agent definition, project, workspace, and both registrations.
3. Check project/workspace visibility and action rights. Derive effective
authority from the agent ceiling and applicable registrations, mission,
task, and explicit launch restriction. A lower layer never widens it.
4. Resolve work references and dependencies against the expected state
revision. State that changed during the decision causes reconciliation or
refusal, not an unnoticed switch to a different assignment.
5. Resolve Resume/Fresh and any active execution. An active Resume returns the
Q11/Q15 conflict result or user offer, not another launch. Q17 Fresh requests
controlled replacement: stop new actions, preserve checked state, resolve or
identify in-flight effects, and confirm safety before acquiring a new active
claim. Uncertain safety blocks replacement, never starts competing work.
6. Generate execution-specific inputs and a context manifest. Paths to work
state come from the selected workspace, never the agent's global seat dir.
7. Persist the launch decision and identity binding before starting work.
Record a start result separately. Refusal and failed start are not success.
8. Start the program through its reviewed adapter using the exact context.
Record actual startup and bind its session/process to the execution ID.
9. On exit or interruption, preserve evidence and reconcile active claims.
A missing completion record means unknown/interrupted, not successful.
These are semantic steps, not a claim that multiple file writes are one
transaction. Crash ordering, claim recovery, and adapter support require
explicit design and tests. They are D7-D9 and D12.
Context content approved in owner Q6, with field layout still proposed:
- Assigned mission and success criteria, current assigned tasks and status.
- Relevant approved project/workspace decisions, not all project history.
- Dependencies, blockers, and references to verified results.
- A proposed next step, visibly distinguished from authorized assignment.
- Unverified notes remain labelled as unverified.
- No old conversation or automatic chat summary for Fresh. Resume retains only
its authorized conversation; old session IDs may remain as audit references.
Shared governance, identity, and skills still apply through the launch contract.
Owner Q22: user context is not automatically the whole profile. Only explicitly
designated general preferences are shared by default; personal/project-specific
information is supplied where authorized and relevant. Record selected source
references/revisions without copying private content into public metadata.
The exact source/revision representation, context grants, and publishers of
checked state remain schema details. This decision does not change the current
user-context files, dispatch implementation, or install a filtering mechanism.
A generated summary of work can help reading, but cannot replace source
records or convert an agent claim into accepted state. Fresh must not quietly
follow a transcript reference and reload the discarded conversation. Any
intentional historical lookup needs an explicit policy and visible record.
The context manifest should record non-secret source identity/revision,
resolved skill versions, effective policy reference, release/image identity,
harness/provider/model selection, included work, and generation time. Hashes
may bind non-secret content, but do not hash credentials into public records.
Input manifests are execution-specific and write-once; shared prompt scratch
files or per-agent mutable mission files do not meet this proposal.
### 2.1 Canonical configuration and change notices
Owner ruling, 2026-09-06, R16-R17: keep canonical SOUL shared by agent identity,
load the current approved revision on each Resume or Fresh launch, and retain
execution-specific launch inputs. Running instructions are not silently
replaced. All TUI/GUI/WUI clients can reference the launch hash and display a
canonical-versus-running configuration mismatch with a Fresh recommendation.
A new configuration does not automatically restart sessions or abandon work.
Owner Q20: cover shared behavior-affecting agent configuration, not just SOUL.
Included categories are SOUL, shared instructions, applicable enabled skill
versions, harness/model settings, and role configuration. Exclude credentials,
conversation history, and changing task progress. Q21: display automatic,
non-blocking mismatch notices and support on-demand checks. Keep the mismatch
visible without repeated interruption, and make details available. Permission
removal is enforced separately, not delayed behind a Fresh recommendation.
Illustrative notice, wording still for review:
```text
Darkwing / Mosaic Stack v2 / sessions
Agent configuration changed since this session started.
Start Fresh with the current configuration to continue your saved work.
Details: running configuration, current configuration, launch record.
```
Proposed record and comparison mechanics:
- `launchManifestHash` binds the complete non-secret input manifest for one
execution. It identifies the recorded launch, not the latest desired state.
- `agentConfigHashAtLaunch` identifies the comparable canonical agent
configuration loaded by that execution, including SOUL content/version.
- A shared status operation returns the execution and launch references,
stored configuration hash, current canonical configuration hash/revision,
check time, comparison status, and Fresh recommendation. All clients use
this operation with the same access checks, not private copies of SOUL.
- Compute both configuration hashes using the same versioned input definition
and deterministic representation. Q20 establishes included categories; exact
field/dependency coverage, algorithm, and encoding remain D16. Hash applicable
shared content, not just its path or unrelated enabled resources.
- Do not compare whole launch manifests for configuration drift: execution
IDs, timestamps, and workspace/task selections legitimately differ between
launches. Ordinary work-state changes must not look like base-config edits.
- Do not include secret values or hashes of secrets. Credential refresh and
selection need their own lifecycle; this notice does not solve #50.
- Status is `matching`, `changed`, or `unknown`. Missing canonical data,
unsupported hash format, or unverified loaded inputs cannot report matching.
Never rewrite an old launch hash to make a running session look current.
- Pin a consistent input snapshot before starting the execution. If canonical
configuration changes during startup, compare against that exact snapshot
afterward; never label a mixture of two revisions as the loaded state.
- A launch fingerprint describes what was supplied and verified at startup.
It is not continuous proof that nothing in the process changed afterward.
Any supported in-process reconfiguration needs a new observed event/status
model; until then, such changes cannot be represented as verified matching.
- Record comparison observations with execution ID, compared revisions/hashes,
check time, source, and reason, without exposing private configuration.
Canonical changes need their own actor/revision record. Notification delivery
does not count as relaunch or adoption of the new configuration.
- Resume with changed configuration warns that the retained conversation can
still reflect old instructions. Fresh starts without that conversation;
adopting the new configuration does not clear the workspace or choose
Abandon on the user's behalf.
Q21 settles automatic non-blocking notices plus on-demand checking. Transport,
change-detection mechanism, acknowledgement behavior, and notification frequency
remain D16. No polling service is installed or implementation authorized by
this decision. Short hashes may be shown for readability; the full values and
source revisions must remain available through the authorized launch record.
Hashes provide comparison and evidence binding, not permission or proof of
safe behavior. Permission revocation still follows D3 and managed-action
checks; an old launch snapshot is not a right to keep revoked authority.
## 3. State is work data, not authority
Proposed separation:
| Information | How it changes |
|---|---|
| Desired mission, task, membership, and policy | Authorized, versioned changes. Workers cannot elevate grants through progress notes. |
| Observations and decisions | Append-only events with actor, scope, source, and evidence. Corrections reference the earlier event. |
| Current task/project/workspace status | A replaceable current view built from accepted changes, with revision checking and a named writer. |
| Chat transcript | Conversation history, not authoritative task completion or permission. |
| Files produced by work | Mutable working files plus immutable evidence references where needed. A file alone does not prove acceptance. |
Prefer one authoritative copy of project decisions. A launch records the
project revision it saw. Owner Q25 requires reconciliation of affected work
after approved plan changes; an old conversation cannot overrule a newer
approved decision. Revision delivery, impact detection, and stale-action
rejection still need design and tests, including on Resume.
Candidate work states, still for review: `proposed`, `ready`, `active`,
`blocked`, `ready-for-review`, `accepted`, `abandoned`. Completion reported by
an execution can move work toward review, never silently to acceptance by its
own author. Owner Q7 permits an authorized independent reviewer to accept
routine tasks against agreed criteria. Owner-controlled implementation
checkpoints remain separate. Task states, session lifecycle, and execution
outcomes are different fields.
Owner Q8: default Abandon ends this agent's selected assignments. It does not
cancel the underlying shared mission/task or another agent's involvement.
Underlying work remains available for reassignment unless separately cancelled
with authority. Selection preserves source IDs and dependency evidence;
unselected records remain present with explicit status. Owner Q9 requires an
explicit assignment change before undertaking unfinished prerequisite work.
Under Q7, an authorized coordinator can approve that change within the plan
without a human prompt. No silent expansion by the executing worker.
Cross-project selection remains excluded in the initial proposal unless
separately authorized and designed.
Two writers updating the same task need an expected revision and conflict
response. Avoid last-writer-wins. A registration change or permission
revocation during execution must be enforceable at subsequent managed actions,
not only checked once at launch. Owner Q14 requires stopping the affected
workspace executions and blocking further actions there while leaving other
independently authorized work running. Global agent disable is a separate
operation. In-flight effects require reconciliation; stop is not undo. Exact
revocation propagation, stopping, and enforcement mechanisms remain D3/D7.
### 3.1 Delegated autonomy, owner Q7
A software factory needs autonomous decisions within an established goal and
plan. Delegated agents may decompose work and make non-destructive decisions
that advance that goal without seeking user approval each time, provided they
stay within their authority and the plan. An authorized independent reviewer
can accept routine task results against agreed criteria. User approval still
controls declared phase boundaries, plan deviations, and protected operations.
Proposed evidence for each delegated decision: deciding actor/execution,
approved plan revision, delegation reference/limits, affected task/assignment,
reason, and observed result or follow-up evidence. Record the actual delegated
authorizer, not a fictitious owner approval. The acting agent's claim that an
action is harmless is not proof that it is authorized or within scope.
Exact permitted actions, risk checks, and delegation revocation are still D3
and D11. This ruling does not authorize implementation during this interview,
unbounded task expansion, or self-approval of a worker's delivery.
### 3.2 Approved plan changes, owner Q25
Pause affected work when an approved goal, plan, or acceptance criterion changes.
Account for actions already underway rather than assuming they can be undone.
An authorized coordinator may reconcile and adjust assignments within delegated
limits; independently valid, unaffected work may continue. Resume affected work
only after reconciliation. Escalate beyond delegated authority or when safe
resolution cannot be established. An unapproved proposal does not replace the
approved plan or grant authority to continue under new criteria.
Proposed transition evidence: old/new approved revision references, change
approval, affected assignments/dependencies, outstanding-action disposition,
coordinator authority, and resume/reassignment decision. Add evidence rather
than rewriting original launch inputs or historical action records. Exact
fields, publishing/notification paths, impact detection, and stale-revision
fencing remain to be specified under D2/D8/D15.
This differs from a non-blocking shared-configuration mismatch notice under
Q21. An affected assignment must not continue solely because a Fresh launch
has not been requested. Q25 does not require automatic Fresh for every plan
change; context reconciliation and controlled replacement remain distinct.
## 4. Accountability and action evidence
The agent identity remains `code-be-01`. Execution metadata answers which
of its sessions acted, on whose request, within which scope and authority.
This is useful even when no cross-workspace mistake occurred.
Distinguish five things:
1. Requester: the authenticated human/service asking for work.
2. Performer: the reusable agent and the particular execution/session.
3. Producer: the launcher, tool gateway, adapter, or external receipt source
that observed and recorded an event.
4. Subject: project/workspace, assignment/task, and target of the action.
5. Authority: policy and membership revisions plus any required approval.
### 4.1 Proposed event envelope
```json
{
"eventVersion": 1,
"eventId": "event-002",
"eventType": "action.finished",
"executionId": "exec-003",
"sessionId": "session-002",
"agentId": "code-be-01",
"projectId": "mosaic-stack-v2",
"workspaceId": "sessions",
"assignmentId": "assignment-014",
"taskId": "task-014",
"actionId": "action-001",
"causedByEventId": "event-001",
"sequence": 2,
"recordedAt": "2026-09-06T00:00:00Z",
"producer": "managed-file-tool",
"operation": "file.write",
"target": { "relativePath": "notes/result.md" },
"authorityRef": "decision-003",
"outcome": "succeeded",
"evidenceRefs": ["receipt-001"]
}
```
This is a shape example, not a real receipt. `decision-003` would bind the
requester's authenticated identity, full launch scope, current permission
check, claim generation, and any owner approval. The producer fills those
fields from trusted execution context; it does not accept agent-supplied
scope or success statements as evidence. Evidence references must resolve to
accessible immutable records with defined content and provenance.
### 4.2 What counts as an action
Proposed minimum classes to enumerate before implementation:
- Launch/resume/fresh/replacement, refusals, exits, and interrupted executions.
- Mission/task assignment, claim, state change, acceptance, and abandonment.
- Registration/permission changes and explicit owner approvals.
- Managed file/tool invocations, external requests, message send/delivery,
repository integration, and release operations when these are permitted.
- Usage observations with provider evidence or an explicit estimate label.
Not all of these exist as managed operations today. Adding an enum does not
create interception or enforcement. D11 decides the first guaranteed set.
### 4.3 Intent, effect, and result
Proposed behavior for managed actions with side effects:
1. Persist authorized intent with action ID, target scope, and expected state.
2. Perform the action through the enforcing component.
3. Persist the observed result and evidence, linked to the intent.
Owner Q19: if required intent cannot be persisted, refuse before the action.
If the action may have happened but its result cannot be recorded, block the
affected execution and treat the outcome as unknown. Other executions may
continue only if their own required recording still works. Do not retry
blindly, claim the action never happened, or invent a success receipt.
Owner Q18: an authorized agent/service may investigate using non-destructive
checks within delegated authority. Dependent work resumes only after evidence
resolves the result; otherwise escalate. The original uncertain action must
not be repeated merely as a test. Recovery checks must also satisfy their
applicable permissions and recording requirements, not bypass an audit outage.
Proposed mechanics: distinguish unknown state from observed failure; after
recording is restored, append recovered evidence with its actual observation
and recovery times. Do not overwrite prior events or claim that a record was
written during an outage when it was not. The exact out-of-band failure signal
and recovery receipt format remain D8/D9. External effects and local logging
are not one atomic write.
Use external request/receipt IDs and idempotency support where available.
Without them, an interrupted action may require human reconciliation. A zero
shell exit code only proves that the invoked process reported success; it
does not prove every intended external effect. Clocks are informational;
per-execution sequence and causal references establish local ordering without
claiming a global order across machines.
### 4.4 Coverage and trust limits
A launcher can record process lifecycle. It cannot automatically observe every
file write, child process, network request, or manually entered shell command
inside a running program. A transcript is useful evidence but is neither a
complete action audit nor a trusted authority source.
Each adapter must declare its observable/enforceable operation set. Record
coverage with the execution, for example lifecycle-only versus managed-tool
events. If a requested audit guarantee exceeds support, refuse that launch
mode or require a separately approved limited mode. Never report full coverage
just because the process has an execution ID.
No claim of tamper-proof logging follows from append-only application code.
A worker that can write the evidence directory could forge or remove records.
A trusted producer outside the worker's write permissions is a proposed
requirement for trustworthy managed-action receipts. Storage access and
failure tests must establish it; the current broad data-root mount does not.
Owner Q13 chose concise action records: actor and project/workspace/session/
task, operation, target, authorization, outcome, and evidence references.
Detailed evidence is separate and access-controlled; do not copy full request/
response content into every action event. Neither metadata nor evidence may
contain credentials. Exact safe field lists and evidence retention remain open.
Never put credentials, auth headers, raw environment contents, or arbitrary
unfiltered tool payloads in new audit metadata. Use operation-specific safe
fields and protected evidence references. Redaction failures must not leak
secrets through diagnostics. Define who may view sensitive task/file/message
content; workspace RBAC must apply to audit queries too. Existing transcripts
and run outputs need a separate privacy assessment, not retroactive claims.
## 5. Messages and concurrent execution
Proposed message envelope: version, message ID, sender identity/scope,
authenticated producer, destination agent/project/workspace, optional exact
session/execution/assignment, message kind, causal/reply reference, creation
and expiry times, and body reference with visibility rules.
User-addressed workspace messages may follow the workspace's current authorized
session under a stated policy. Execution results and approvals bind to their
exact execution/assignment revision. A late result must not become an
instruction or approval for replacement work. Ambiguous destinations refuse;
transport delivery and task acceptance are separate receipts.
Owner Q12 permits shared mission/task/decision records and authorized evidence
within a workspace, not blanket access to other agents' conversations. A
transcript needs a separate read permission or explicit authorized handoff.
Neither a shared workspace nor a read grant automatically injects that chat
into another session. Fresh's old-chat exclusion still applies.
Default active limit is one session for each agent/project/workspace tuple.
Owner Q11: an interactive Resume that finds the session already running must
state the conflict and offer to connect, never automatically connect, replace,
or create a duplicate. The owner may already be connected elsewhere. Q15:
a service receives a machine-readable already-active result with the existing
execution identity and must explicitly request connection or another authorized
operation; it does not receive an unanswered interactive prompt.
Owner Q16: one controlling interface connection at a time. Other permitted
connections may observe. Taking control is explicit and requires appropriate
authority; observation does not grant control. Proposed connection records
bind client identity and mode to the exact session/execution. Control-transfer
mechanics must prevent a stale controller from submitting commands after
transfer. Ordinary scoped inter-agent messages are not an implicit controller
connection; their admission/attribution rules remain distinct.
Owner Q17: Fresh requests controlled replacement rather than requiring a
separate stop command. Stop admitting new actions, preserve checked work state,
and settle or identify unfinished effects. Start Fresh only after safe
replacement is established; otherwise stop and report. This is not permission
to replay uncertain actions or import a conversation summary as checked state.
Adapter attachment, control transfer, stop/recovery, and durable claim behavior
must be measured and tested before claiming support.
User tuning changes a limit, not task ownership. Several agents can work in
one workspace but shared files/tasks still need coordination. Proposed active
claims have a unique ID and increasing generation. Managed writers reject
stale generations, so an old process cannot continue after its replacement.
Timeout alone is not proof that an old process has stopped, especially for
unmanaged external actions. Recovery policy is D7.
Budgets and automatic scaling are deferred requirements. Do not place invented
budget defaults into the first schema or imply that recorded usage alone
provides a hard spending limit. The limit scope, approval rights, usage delay,
and response to overruns need a later planning decision.
## 6. Storage constraints, not a migration plan
Keep `<dataRoot>/runs/` as canonical write-once execution evidence. Workspace
views reference evidence there rather than duplicating or rewriting it.
Existing `result.json` records remain untouched. A future version may extend
the record contract through new records and readers, never by rewriting old
runs. New events are append-only and terminal results are written once.
Proposed organization to discuss: projects own workspace metadata/work state;
workspace-and-agent scope owns session references; execution IDs own launch
inputs and receipts. Whether session bytes live in workspace subdirectories
or remain under `<dataRoot>/sessions/` is D6. Logical ownership does not force
a particular filesystem layout. No new storage path is approved by implication.
Every persistent file class needs an owner, writer, reader policy, retention
rule, and recovery rule. Shared agent settings are not a second system config.
Keep secrets in runtime credential storage, not project/workspace metadata.
Do not treat a project directory as permission to edit reviewed role authority.
Current `prune` receipts and data-root `reset` behavior still govern existing
records. The new design must specify dangling references after pruning,
metadata export, reset scope, and recovery before migration. It does not
silently protect new records from reset or authorize a new deletion path.
A safe rollback cannot depend on editing historical evidence.
### 6.1 Workspace retirement and legacy adoption
Owner Q23: closing a workspace retires it from active work. Stop its active
work safely, retain files/history, and prevent ordinary new launches until it
is reopened. This is not deletion or an implicit cancellation of every mission.
Outstanding actions and assignment dispositions need reconciliation under the
existing replacement/recovery rules. Reopening must check current authority;
it must not automatically restore a revoked membership. Those exact transition
records and reopening behavior are still to be designed and reviewed.
Owner Q24: preserve existing unscoped sessions as legacy records. Adopting one
requires explicit reviewed project/workspace assignment. Do not guess from
agent or directory names, place everything in a default project automatically,
or rewrite old execution evidence to pretend it originally had new scope.
Proposed adoption evidence: authorized requester/reviewer, legacy source ID and
non-secret content identity, destination agent/project/workspace, membership
checks, and a new mapping/adoption receipt. This is proposed metadata, not an
approved migration API. Ambiguous lineage or missing access refuses adoption.
Old conversation content remains historical and is not loaded by Fresh.
Actual storage paths, retention intervals, retirement state transitions,
reopening, recovery after prune/reset, and rollback remain D6. Closing does
not exempt records from separately authorized repository retention/reset rules,
and no new deletion tool is approved by the retirement decision.
## 7. Open decisions
| ID | Owner decisions or open question | Remaining proposal or engineering work |
|---|---|---|
| D1 | Scope/hierarchy resolved in Q1/Q5: project owns N workspaces, workspace has exactly one project; a workspace mission has at most one parent project mission or stands alone within its owning scope. Dependencies are not parents. | Formal field definitions and mission lifecycle/completion rules remain to be specified; no multiple-parent ownership. |
| D2 | Recovery content resolved in Q6: assigned mission/criteria, tasks/status, approved decisions, dependencies/blockers, verified-result references, labelled proposed next step and unverified notes; no old chat/automatic summary. | Exact record representation and publisher authority remain schema details; routine acceptance follows Q7/D15. |
| D3 | Q2/Q3/Q14 resolve bounded delegation, scoped visibility, and affected-execution revocation. Q26 chooses standard scope permission roles with per-registration narrowing; they do not redefine agent identity or expand ceilings. | Exact role catalog/grants, record fields, actor authentication, propagation, and stopping need schemas/enforcement. Role names were illustrative; shared role authority remains reviewed-commit controlled. |
| D4 | Resolved by Q4 clarification A: create and announce the initial conversation automatically on genuine first use, without an offer. Later default launches resume; missing/damaged established sessions and ambiguity are errors. | Record initial creation distinctly from resume. Specify reliable first-use history and recovery checks under D6/D8; do not infer first use from a missing transcript. |
| D5 | Default behavior resolved in Q8/Q9: end only this agent's selected assignments on Abandon; undertaking unfinished prerequisites needs an explicit authorized assignment change. | Preserve underlying work/history and other assignments. Q7 allows within-plan delegated authorization without asking the user each time. Fields and interruption sequencing remain open. |
| D6 | Q23 retires closed workspaces safely while retaining files/history and blocking ordinary launches until reopened. Q24 requires explicit reviewed legacy adoption, not inferred membership. | Exact layout, lifecycle/reopening, mapping records, retention intervals, prune/reset reference handling, and migration rollback remain engineering work. Preserve old evidence. |
| D7 | Q11/Q15 require conflict/offer for users and already-active result for services; connection is explicit. Q16 permits one controller plus authorized observers. Q17 requests controlled Fresh replacement, refusing unsafe overlap. | Connection/claim schemas, stale-controller rejection, adapter support, stopping, crash recovery, and concurrency tuning remain engineering work. Timeout alone is not proof an execution stopped. |
| D8 | Q18 permits delegated non-destructive investigation of unknown outcomes, evidence-based continuation, and escalation when unresolved; no blind replay. Exact atomicity/retry guarantees remain open. | Unique requests/actions, linked intent/result/recovery evidence, actual observation times, and explicit unknown status; no blanket exactly-once promise. |
| D9 | Q19 blocks affected executions when required audit recording fails; independently recordable work may continue. Effects with missing result evidence follow Q18 recovery. Storage integrity/trust and failure reporting details remain open. | Refuse before effects; never bypass audit requirements for recovery checks. A trusted writer and out-of-band outage signal need design and tests. |
| D10 | Partly resolved by R16-R17: current approved SOUL at launch, stable per-execution inputs, and cross-interface mismatch notice. Credential sharing and other configuration lifecycle details remain open. | Preserve the owner ruling in section 2.1; reconcile #50 before coding. Exact fingerprint and notification mechanics move to D16. |
| D11 | Q10 permits unassigned conversation/inspection and recorded change assignments. Q13 separates concise metadata from controlled evidence. Q27 A requires command-invocation records with enforced limits, not separate tracing of every internal file/network effect. | Complete taskless/read/chat fields, command/event schemas, evidence allowlists, and enforcement. No isolation, credential, recording, revocation, or recovery gate is waived. |
| D12 | Phase-2 inspection of documentation extracted from the pinned image establishes documented exact-session selection, RPC/JSON events, queue/abort distinctions, and no built-in sandbox. Current Mosaic adapter uses plain print/native TUI instead. | No live RPC, hook, controller, or isolation test yet. Investigate the enforcing integration and failure behavior; host Pi 0.85.1 is not evidence for runtime 0.84.4. |
| D13 | Q12/Q13 separate shared work records, transcript grants, and controlled audit evidence. Q22 shares only designated general preferences by default and supplies personal/project context only where authorized and relevant. | Exact context classification/grants and enforcement remain to be specified. Read permission does not imply automatic transcript loading; replace blanket global user-context injection only through approved implementation. |
| D14 | Jason answered Q28 A: a Mosaic-controlled terminal backed by Pi is acceptable. R34 records this; native UI parity is not an initial requirement. | Runtime/control/input schemas and shared operations now form an owner-review candidate. No UI implementation, engine replacement or safety waiver follows. |
| D15 | Q7 allows authorized independent routine acceptance and delegated non-destructive within-plan decisions. Q25 pauses affected work after approved plan changes, permits delegated reconciliation, and lets independently valid unaffected work continue. | Preserve owner checkpoints and plan-deviation/protected-operation approval. Readiness writers, impact detection, stale-action rejection, conflicts, and change evidence remain open; workers do not self-accept. |
| D16 | Q20 covers shared behavior-affecting config: SOUL, instructions, applicable enabled skills, harness/model settings, and role config, excluding credentials/chat/task progress. Q21 requires automatic non-blocking notices and on-demand checking. | Exact hash/dependency fields, encoding, snapshot verification, delivery/acknowledgement, and frequency remain technical details. Separate comparable config from whole launch identity; no silent refresh. |
The owner authorized a grill-me interview on 2026-09-06. Ask each round's
questions only after their prerequisites are settled. Record answers without
silently resolving qualifications. Independent analysis later may add questions
or reject recommendations, not overwrite owner rulings.
### 7.1 Owner interview record, round 1, 2026-09-06
Source: Jason's answers in pi session
`01a06e48-0718-71f2-a889-c263c4800fb9`. Interview Q numbers are separate from
D decision IDs. This records partial alignment, not full design approval.
- Q1, D1: "c. Glad you thought of that answer too." Option C permits both
project and workspace missions with explicit links, including standalone
workspace missions within project scope. Each has one authoritative owner.
- Q2, D3: "B". User delegates bounded authority for system registration and
assignments. Outside-limit actions require approval, not agent self-grants.
- Q3, D3: "B". Project membership gives shared project information plus
explicitly permitted workspaces, not visibility into all workspace content.
- Q4, D4, verbatim: "B, I'm not sure the offer is necessary. A declaration of
initial conversation instantiation is good, but an offer on a non-existent
item is unnecessary." The no-offer preference is clear. First-creation
behavior needs a focused clarification before the command rules are settled.
Q4 clarification, 2026-09-06: Jason answered "A" to automatic first creation
with an announcement and no offer. Later launches resume by default; Fresh
explicitly starts a new conversation. Missing or damaged previously established
conversations are errors rather than first-use cases. This resolves the
qualification above without rewriting the original answer.
Next: continue mission relationships, Fresh recovery, completion authority,
and work-selection decisions. Do not treat an unanswered question or an
interview recommendation as an owner decision.
### 7.2 Owner interview record, round 2, 2026-09-06
Source: Jason's answers in the same pi session as round 1. Previous answers
remain recorded above; this is not full plan/schema approval.
- Q5, D1: A, with the owner qualification that parent:child relationships
stay strictly one-to-many. A project has N workspaces; each workspace requires
exactly one parent project. A workspace mission has at most one parent
project mission as proposed in Q5. Dependencies are different from parents.
- Q6, D2: "agreed" to the full proposed Fresh/Continue information set:
mission/criteria, assigned tasks/status, approved decisions, dependencies,
blockers, verified-result references, labelled proposed next step, and
labelled unverified notes. Old chat and automatic summaries stay out.
- Q7, D15/D3: B. Authorized reviewers may accept routine tasks. Jason clarified
that a software factory needs autonomous goal-directed decisions, layered
decomposition, and delegated non-destructive actions within the established
plan without user interaction each time. Deviation does not inherit that
authority. The agreed user implementation checkpoints remain in place.
- Q8, D5: A. Default Abandon ends this agent's selected assignments; missions
and tasks remain for reassignment unless separately cancelled with authority.
- Q9, D5: B. Show an unfinished prerequisite and obtain an explicit assignment
change before doing it; include relevant dependency information, not silent
extra work. Read with Q7: the authorizer may be a delegated coordinator, not
necessarily the user. Record the assignment change and its actual authority.
Next questions concern review separation with reused agent identities,
unassigned interaction, revocation, concurrency conflicts, and audit behavior.
No downstream recommendation is settled by the answers above.
### 7.3 Owner interview record, round 3, 2026-09-06
Source: Jason's answers in the same pi session as rounds 1-2. No full schema
approval or implementation authorization is implied.
- Q10, D11: A. Permit conversation and authorized workspace inspection before
a mission/task exists. Requests to change things become explicit recorded
assignments without requiring a lengthy setup process.
- Q11, D7/D14: owner correction to the automatic-connect recommendation.
State the active-session conflict and offer connection because the user may
already be connected and have forgotten. Owner example, verbatim:
"I see that session is aalready active. Would you like to connect?"
No automatic connection or duplicate launch. Non-interactive service
response was not specified by this answer and remains open.
- Q12, D13: B. Shared work records and permitted evidence, but another agent's
conversation needs a separate permission or explicit handoff. Read access
does not imply automatic loading into the receiving conversation.
- Q13, D11/D13: A. Concise action metadata and evidence references; detailed
evidence separately controlled, not copied wholesale into each audit event.
Credentials must not appear in either.
- Q14, D3/D7: A. Workspace membership removal stops affected executions and
blocks further actions there; other independently authorized work continues.
Global agent disable is separate. Reconcile effects already in flight.
Next: clarify service-side conflict handling and decide connection ownership,
replacement, uncertain-action recovery, and audit-failure behavior. Technical
capability checks must supply facts, not be questions the owner must guess at.
### 7.4 Owner interview record, round 4, 2026-09-06
Source: Jason answered A to each of Q15-Q19 in the same pi session as prior
rounds. These settle behavior, not unmeasured runtime capabilities.
- Q15, D7/D14: service receives an already-active result with execution identity
and explicitly requests any authorized connection/next operation. No automatic
attachment or interactive offer waiting for a nonexistent human response.
- Q16, D7: one controlling interface connection; other authorized connections
may observe, with explicit control transfer and conversation access checks.
- Q17, D7: Fresh requests controlled replacement. Stop new actions, preserve
checked state, settle or identify unfinished actions, and only replace safely.
If safety cannot be established, stop/report rather than overlap executions.
- Q18, D8: delegated authorized agents/services may investigate unknown results
with non-destructive checks. Resume dependent work only on resolving evidence;
otherwise escalate. Repeating the uncertain action is not a recovery test.
- Q19, D9: audit-recording failure blocks affected executions. Other work may
continue only when its required recording works. Refuse before effects, and
reconcile effects already issued whose results could not be recorded.
Next: configuration fingerprint scope/notices and workspace retention/migration
choices. Exact schema and runtime mechanisms remain unapproved until examined;
no implementation or broader phase advancement follows from these answers.
### 7.5 Owner interview record, round 5, 2026-09-06
Source: Jason answered Q20 B, Q21 B, Q22 B, Q23 A, Q24 A in the same pi session.
These are behavior decisions, not permission to change runtime configuration.
- Q20, D16: shared behavior-affecting configuration fingerprint, not SOUL only.
Include SOUL, shared instructions, applicable enabled skill versions,
harness/model settings, and role config. Exclude credentials, chat, and task
progress; revocation remains an enforcement event, not merely a notice.
- Q21, D16: automatic non-blocking notices plus on-demand checks; keep mismatch
visible without repeated interruption or automatic restart.
- Q22, D13: designated general preferences may be shared; personal and project
information is scoped by authorization and relevance, not a blanket profile.
- Q23, D6: close means retire from active work, stop safely, retain files and
history, and block normal launches until reopened. Cleanup/deletion is separate.
- Q24, D6: preserve legacy sessions, then explicitly review and assign their
project/workspace on adoption. Do not infer membership or auto-place them.
Next behavioral questions concern changes to an approved plan during execution
and how scope permissions are expressed. Engineering questions remain open for
fact-finding and schema review, not guesses or silent implementation decisions.
### 7.6 Owner interview record, round 6, 2026-09-06
Source: Jason answered Q25 A and Q26 B in the same pi session.
- Q25, D15: pause affected work after an approved plan change and reconcile
underway effects and assignments. A delegated coordinator can adjust work
within its limits; unaffected work may continue. Escalate beyond authority
or when safe resolution is unavailable, rather than finish obsolete work.
- Q26, D3: standard scope roles with registration-specific narrowing. A scope
role describes permission there, not agent identity/type. Exact names and
grant lists remain subject to review; no expanded agent ceiling is implied.
### 7.7 Interview checkpoint, confirmation pending
Q1-Q26 have answers, with qualifications retained above, including the explicit
Q4 correction. No additional owner-behavior question is currently ready. Ask
Jason to confirm shared understanding of R1-R32; do not equate these individual
answers with interview completion or approval of the whole schema.
D1-D16 retain unresolved engineering questions: fields and record lifecycles,
authorization/enforcement, work-state revisions and recovery, adapter/session
capabilities, observable action coverage, storage/migration, configuration
hashing/notification, and concrete command/UI contracts. Detailed design and
fact-finding need next-phase authorization. Technical findings that introduce
new behavioral tradeoffs come back to the owner, not silent design decisions.
No mapping, independent gap analysis, implementation, migration, commit, push,
or issue closure follows automatically from this interview checkpoint.
### 7.8 Owner confirmation of intended behavior, 2026-09-06
Following round 6 and the assistant's behavior summary, Jason replied:
"That looks correct." This confirms shared understanding of intended behavior
represented by R1-R32. Section 7.7 records the preceding pending checkpoint;
its confirmation request is now satisfied.
This confirmation does not approve exact schemas, establish runtime support,
or resolve the remaining engineering branches. Phase 2, detailed design and
read-only technical investigation, still requires explicit authorization.
Mapping, independent gap analysis, implementation, migration, commit, push,
and issue closure remain outside current authority.
### 7.9 Phase-2 authorization and first investigation, 2026-09-06
Jason answered yes to detailed records, permissions, commands, and audit
guarantees with read-only technical investigation. This supersedes the pending
authorization recorded in section 7.8. Mapping, implementation, migration,
publication, and issue closure remain outside authority.
The linked phase-2 candidate records tool-free source-analysis run
`r-20260906T024609Z-68ee7f`, pinned image/document identities, author
qualifications, proposed common types, operation rules, and remaining work.
The run is investigative output, not independent gap analysis or approval.
Q27 is now ready for the owner: accountable command invocation with enforced
limits and controlled evidence, or separately attributable internal file/network
effects before command execution is enabled. Recommendation A is recorded in
the candidate; no answer or added approved requirement is inferred.
### 7.10 Owner Q27 ruling, 2026-09-06
Jason answered "Q27: A". Initial managed command auditing records the authorized
invocation, actor/scope/assignment, enforced filesystem/network limits,
start/end, outcome, and controlled evidence references. It does not require
separate enumeration of every internal file read/write or network operation.
This settles the question left pending in section 7.9 and adds R33. Workspace
isolation, credential protection, fail-closed recording, revocation, and
uncertainty recovery remain required. Exact schemas and enforcing mechanisms
are still phase-2 work; this is not implementation or mapping approval.
### 7.11 Resumed goal and Q28 frontier, 2026-09-06
After reboot recovery, Jason explicitly set the phase-2 goal and repeated it
with --wait-timeout 60. This resumed the existing planning assignment, not a
second goal or an implementation mandate. No exposed goal runtime API or
automatic timeout/wake has been verified; CURRENT records file-based state.
The linked phase-2 package now has checked command/record schemas, negative
fixtures, explicit calendar/path checks, restricted-domain fingerprint vectors,
and permission/reference/lifecycle/storage and #50 compatibility proposals.
These are author design checks, not independent review or runtime enforcement.
Q28 is ready: may the first managed release use a Mosaic-controlled terminal
client, or must it preserve the native Pi terminal with full control mediation?
Recommendation A is recorded in the package. No answer is inferred. Runtime
execution/control/input-routing and command artifact contracts depend on that
choice; manual owner response is the next resumption condition.
### 7.12 Owner Q28 ruling and goal clarification, 2026-09-06
Jason answered "Q28: A". R34 records the managed-terminal ruling. The author
continued P2-5/P2-6: closed runtime/control/artifact shapes, synthetic rule checks,
and the owner-review package. The prior Q28 wait in section 7.11 is historical.
Jason also clarified that /goal came from the separate ~/.mosaic deployment and
is not implemented in this development environment. Continue the same goal through
the existing repository records; no extension installation or active wait timeout
is requested. Goal completion still requires Jason's phase-2 verdict.
[Owner review and D1-D16 reconciliation](foundation-v1-candidate/REVIEW.md) is the
current candidate index. Runtime security/enforcement and later phase approvals
remain separate from the author checks. Earlier open-detail descriptions above
are preserved as the interview/design history, not claims of implemented behavior.
## 8. Trace checklist for the later independent analysis
The non-authoring agent must trace, at exact code and plan revisions:
- Request authentication -> registrations -> effective permissions.
- Workspace selection -> authoritative state -> context manifest -> program.
- Resume/Fresh choice -> exact session -> unique execution -> active claim.
- Existing-session conflict -> authorized connection -> sole controller or
observer -> explicit control transfer -> stale-controller refusal.
- Canonical configuration -> stable launch inputs/hash -> running execution
status -> shared comparison -> TUI/GUI/WUI notice -> authorized relaunch.
- Assignment -> action authorization -> side effect -> intent/result evidence.
- State update -> revision conflict handling -> visible task/project status.
- Message address -> permission check -> execution-aware delivery -> receipt.
- Exit/crash -> uncertain effects -> recovery -> replacement permission.
- Audit query -> scoped visibility -> retention/prune and recovery behavior.
For each connection: cite the actual implementation or explicitly record it
as missing; cite the approved plan requirement; name the writer/enforcer and
the receipt; identify unsupported guarantees and a reproducible test. Separate
implementation gaps from holes or contradictions in the plan. Do not fix the
candidate during review or substitute the map author's assurances for evidence.
The independent report and Archify maps are later deliverables. This file
contains no independent verdict and no assertion that these controls exist.
@@ -0,0 +1,272 @@
# Agent context, templates, and staged migration
Plan ID: ACT-1
Date: 2026-09-07
Recorder: Codex, at Jason's request
Status: owner decisions captured; execution sequence proposed; runtime migration deferred
Acceptance owner: Jason
Coordinator, implementers, and independent reviewers: unassigned
## Purpose and authority
`/home/jwoltje/src/mosaic-stack-dev-test` is the next-generation development leg
of `/mnt/storage/src/mosaic-stack`, being rewritten from the ground up to replace
that codebase. Earlier descriptions of this repository as only a standalone POC
do not fully describe its intended destination.
Jason requested a durable shared plan after agreeing to the context-file
responsibilities, personality improvements, template approach, and behavioral
evaluation described below. This request authorizes recording the plan. It does
not activate the implementation tasks or assign work to existing agents.
The current demo path must remain usable until Jason verifies it and the
transition is coordinated. Darkwing, Rocko, and Filbert have ongoing tmux work;
this plan must not change their sessions, launch inputs, assignments, or frozen
review candidates. Broad codebase organization for scaling comes AFTER demo
validation. Do not implement a monorepo move as preliminary cleanup.
At capture, [CURRENT.md](CURRENT.md) records a pending inspector correction and
independent review before owner demo acceptance. Read that live record on every
handoff; this document is not a competing next-action queue. Checkout HEAD was
`9a5fbdbda74b16adf488fe28138b2ba69ea5e669`, with substantial uncommitted work.
That SHA alone does not identify the working files or an accepted demo baseline.
## Agreed owner decisions
| ID | Decision |
|---|---|
| D1 | Each instruction file has a defined responsibility; avoid duplicating personality, governance, procedures, and runtime facts across layers. |
| D2 | Agent personality should produce concrete behavior: direct answers, evidence-backed recommendations, early challenges to flawed assumptions, useful brevity, and natural optional humor. Confidence must not erase uncertainty. |
| D3 | The agent's SOUL.md is the ONLY SOUL injected. Each agent has one canonical SOUL file. No shared default SOUL fallback or personality layering in the target system. |
| D4 | System bootstrap and agent bootstrap use a SOUL template to initialize each agent's own file. Templates also create appropriate agent-specific and deployed versions of other context files/contracts. |
| D5 | `docs/reference/templates/` was imported whole-cloth as an example. It is reference material to adapt, not an approved deployment source. Existing `contracts/*` are initial-session boilerplate; their eventual placement needs incremental treatment. |
| D6 | Evaluate behavior using test agents; Darkwing, Filbert, Heffer, and Rocko are available candidates. Their active sessions must be preserved. |
| D7 | Preserve the existing option through Jason's demo verification and a properly coordinated migration. |
| D8 | Organize for a scalable monorepo after demo validation; avoid premature source moves. |
| D9 | Annex adopted concepts as Mosaic documentation in docs/concepts; remove upstream runtime nomenclature rather than retaining foreign product instructions. Supporting source lineage and required license attribution remain in docs/reference/concepts. |
Proposed lifecycle details requiring design/review: templates initialize instances
without silently overwriting later edits; template updates are explicit migrations;
launch snapshots preserve historical bytes without becoming editable canonical
SOULs. These details implement the ownership intent and must be reconciled with
the existing foundation contracts before runtime changes.
## File responsibilities
| Resource | Intended responsibility | Must not become |
|---|---|---|
| CONSTITUTION.md | Shared boundaries, principles, authority relationships | Agent personality or a substitute for runtime permission enforcement |
| STANDARDS.md | Quality expectations and evidence requirements | A startup-marker-only contract or repeated repository procedures |
| Agent SOUL.md | Voice, temperament, judgment, and interaction style | A mission, capability grant, operational history, or second workflow manual |
| AGENTS.md | Repository procedures and working conventions | A universal worker prompt injected into every scope regardless of authority |
| USER.md / scoped user context | Relevant authorized preferences and personal context | Automatic distribution of the whole user profile to every agent |
| Runtime launch context | Actual identity, workspace, tools, skills, session semantics | Unverified capability claims or permanent persona instructions |
| Template sources | Reviewed defaults and bootstrap inputs | Live runtime fallbacks or competing editable agent identities |
| Execution snapshots | Evidence of exactly what an execution loaded | A second canonical configuration source |
Final paths, packaging, and update authority are intentionally unresolved. File
names and Markdown section order alone do not enforce precedence or security.
## Current behavior to preserve and then migrate
These are source observations from the preceding groundwork, not proof of full
runtime conformance. Reinspect the relevant files before preparing a candidate.
- [scripts/agent.sh](../../scripts/agent.sh) defaults to the existing container
lifecycle. Explicit leading `--host-dev` delegates to
[scripts/agent-host-dev.sh](../../scripts/agent-host-dev.sh).
- Container context comes from [src/load-contracts.sh](../../src/load-contracts.sh):
image-baked governance, a default contract SOUL or a seat override, optional
identity, live user Markdown, and mission context. The current fallback
conflicts with D3 and needs a deliberate migration, not immediate removal.
- The native helper loads the selected agent SOUL, repository governance,
deployment USER.md, repository AGENTS.md, and agent CONTEXT.md. It keeps
launch snapshots and separate native sessions. It is a host development
path, not proof of managed workspace isolation or role enforcement.
- Root SOUL.md is not loaded by these inspected paths. Do not delete it or
`contracts/SOUL.md` until consumer inventory and evidence-preservation needs
are resolved. Their presence does not make them authoritative in the target.
- POC startup-marker behavior is embedded in existing contracts and verification
expectations. Any separation into explicit test fixtures must preserve valid
health checks and cannot relabel skipped checks as passed.
## Relation to existing plans
- [Agent/project/workspace foundation](2026-09-06_agent-project-workspace-foundation.md):
R16 already requires canonical agent SOUL and current approved context at
Resume/Fresh launch; R17 describes configuration fingerprints and mismatch
notices; R28 scopes personal context; R30 requires explicit legacy adoption.
Extend and reconcile these decisions rather than designing a parallel lifecycle.
- [Phase-2 contract](2026-09-06_foundation-phase2-contract.md) and
[workspace/schema/audit discussion](2026-09-06_workspace-schema-and-audit.md):
reuse identity, revision, scope, and evidence concepts after checking their
current approval state. A document reference is not runtime enforcement.
- [Installation/onboarding requirements](2026-09-06_foundation-install-onboarding-topics.md):
bootstrap must integrate personality/style choices, trait compatibility,
resumable initialization, and user privacy without granting extra permissions.
- [Monorepo source layout](2026-09-06_monorepo-source-layout.md): build on its
source/build/install/state separation and component migration method. Reconcile
historical milestone sequencing with Jason's explicit demo-first direction.
- [SOUL concept guide](../concepts/soul.md): use as personality design input;
do not import its rewrite prompt as an instruction to modify deployed agents.
## Phases and gates
Concept documentation is maintained in [Mosaic Stack concepts](../concepts/README.md).
These are Mosaic explanations with current/proposed status, not a mirrored product
manual. [Provenance](../reference/concepts/README.md) records original lineage
separately from current content identity. Documentation adoption does not waive
the runtime migration gates below.
### Phase A — documentation and candidate design before demo acceptance
Record owner decisions, inventory context consumers, and define a migration and
evaluation proposal in separate planning artifacts. Any additional groundwork
requires a recorded assignment; this capture does not dispatch workers.
Outputs: source-backed loading/ownership matrix, candidate template responsibilities,
compatibility cases, isolated test design, and unresolved decisions. Preserve
active contracts, bootstraps, launchers, generated installations, session state,
credentials, and the current demo/review candidate.
Exit evidence: reviewed plan with owners, affected paths, current consumer list,
and concrete acceptance checks. This is planning readiness, not demo acceptance.
### Gate G1 — Jason verifies the current demo
Record Jason's acceptance and the exact candidate/evidence it covers in the
existing demo records, then link it here. Do not infer acceptance from green
tests, an agent verdict, or elapsed time. Acceptance of that demo does not
automatically activate every task in this plan; reconcile the next bounded scope.
### Phase B — bounded context/bootstrap migration
After G1 and assignment of the implementation increment, create reviewed template
sources and bootstrap-owned instances. Implement agent-only SOUL resolution with
clear missing/invalid-agent errors. Separate verification fixtures from normal
personality. Reuse existing config, lifecycle, and launch interfaces where suitable.
Prove idempotence, preservation of existing edits, interrupted-bootstrap recovery,
Resume/Fresh behavior, context provenance, and rollback in isolated fixtures.
Freeze a candidate, obtain independent review, and let Jason test the increment
before promoting it or retiring the old path. No running session silently reloads.
### Phase C — personality evaluation and controlled promotion
Use isolated test registrations/workspaces or distinct test identities derived
from the approved Darkwing/Filbert/Heffer/Rocko definitions. Decide the mechanism
before launch; a familiar name does not permit reusing live session directories.
Record model/harness settings, context hashes, scenario inputs, actual outputs,
reviewer findings, and Jason's preference verdict. Record model-call scope and
budget before live evaluations. Failed candidates remain evidence, not deployments.
Offline evaluation preparation may accompany Phase A; live trials and promotion
require their assigned scope. This phase must not become an unattended
self-rewrite loop or an automatic template rollout to all agents.
### Phase D — monorepo organization after validated behavior
Use the existing source-layout plan to define package boundaries, declarative
resource ownership, dependency directions, development installations, and release
artifacts. Move one component at a time with compatibility entry points and
verified rollback. Do not select a new package manager or copy the legacy tree
merely to give the repository a monorepo appearance.
## Collaboration work register
ACT-01 is complete. A later owner request authorized reference import and test
preparation under ACT-04; its draft pack is ready for review, not live execution.
Other tasks remain unassigned. Readiness does not dispatch another agent.
| Task | Deliverable | Dependency / gate | Status |
|---|---|---|---|
| ACT-01 | Capture owner decisions and staged shared plan | Current user request | Recorded by Codex |
| ACT-02 | Inventory all bootstrap, launch, health-check, retry, and packaging context consumers | Assigned documentation scope | Unassigned |
| ACT-03 | Specify template ownership, instance creation/update, provenance, and conflict rules | ACT-02; reconcile onboarding/foundation | Unassigned |
| ACT-04 | Define isolated behavioral scenarios, fixture identities, scoring, and model budget | Owner-authorized preparation; reconcile ACT-02/03 before trials | Draft prepared by Codex: [test package](act-1-tests/README.md); live budget and evaluation binding unset |
| ACT-05 | Freeze bounded migration candidate, compatibility checks, and rollback procedure | G1; ACT-03; implementation assignment | Deferred |
| ACT-06 | Implement and independently review context/bootstrap migration | ACT-05 | Deferred |
| ACT-07 | Run approved test-agent evaluations and obtain Jason's acceptance | ACT-04/06; evaluation assignment | Deferred |
| ACT-08 | Reconcile and execute component layout migration | Validated demo and affected behavior; layout assignment | Deferred |
For each assignment record task ID, author, independent reviewer, exact file scope,
workspace/worktree, baseline plus dirty-file identities where relevant, deliverable
path, dependencies, and acceptance owner. Do not preassign the active demo agents.
Use one designated editor for this plan's status and decisions. Concurrent authors
should write separate task notes named `2026-09-07_act-XX-<topic>.md` in this folder
and submit evidence pointers to the coordinator. A written assignment is not a
filesystem lock; source work requires coordinated ownership and isolated worktrees
where necessary. Preserve the shared Git index ownership recorded in CURRENT.md.
Direct communications and worker dispatch require actual assignment/authorization;
merely storing this file does not deliver it to another tmux session.
## Acceptance and evaluation cases
- **Single identity:** exactly one agent SOUL in effective context; root/default
SOUL absent. Missing agent SOUL refuses launch rather than using another persona.
- **Bootstrap ownership:** initial system agent and subsequent agents each receive
their own canonical instance. Rerun, interruption, and template revision never
silently discard approved edits or create duplicate identities.
- **Stable execution:** record the approved inputs used by Resume and Fresh;
changes to source do not alter already-loaded context. Historical snapshots
remain immutable evidence and are not offered as editable canonical files.
- **Compatibility:** exercise every affected launch/verification consumer from
ACT-02. Preserve session history and startup verification; demonstrate rollback
without restoring or overwriting an active agent's conversation or work records.
- **Personality:** a simple question gets a direct answer; a flawed proposal gets
a respectful challenge; an uncertain fact is labeled honestly; a failed action
is reported accurately; an out-of-scope request does not gain authority from tone.
- **Style adaptation:** technical depth follows the task; humor is optional;
public/shared communication fits its audience. Avoid rewarding profanity,
contrarianism, verbosity, or confident guesses as evidence of personality.
- **Evaluation integrity:** compare baseline and candidate under recorded settings,
retain actual responses, separate subjective style judgments from hard
requirements, and repeat ambiguous cases. A few good responses are not a
guarantee of behavior or security enforcement.
## Open design decisions
1. Final template source and deployed instance locations, including eventual
ownership of constitution/standards and preservation of current contract fixtures.
2. Version/provenance representation and who may approve instance edits or upgrades;
align with foundation context fingerprints instead of inventing a second scheme.
3. Explicit context precedence and consistent behavior across container and host
modes, including scoped user information and all affected headless workers.
4. Trait composition during bootstrap, required versus optional template inputs,
handling of contradictory choices, and existing-instance migration rules.
5. Concrete test-agent isolation, writer/reviewer allocation, evaluation rubric,
budget, and sufficient evidence for owner acceptance.
6. Exact compatibility lifetime and rollback triggers before retiring boilerplate
paths, followed by component layout decisions under the monorepo plan.
## Decision and handoff history
- 2026-09-07 — Jason agreed to responsibility separation, concrete personality,
template-based instances, and evaluations; explicitly required one agent SOUL,
demo compatibility, and post-validation structural work. Codex recorded ACT-1.
No runtime implementation, agent launch, worker message, or task reassignment
performed for this capture. Next planning task is ACT-02 when assigned; the
operational next action remains in CURRENT.md.
- 2026-09-07 — Jason requested pulling the selected OpenClaw concepts and preparing
testing with Darkwing. Copied twelve reference documents plus LICENSE with a
source manifest; created eleven synthetic review scenarios, a candidate SOUL,
NOT_RUN results and a preparation utility. See [Darkwing handoff](act-1-tests/README.md).
`node scripts/prepare-concept-tests.mjs --check` verified thirteen imported files
and eleven cases. Existing launcher regressions passed two tests in isolated
fixtures. Prepared `/tmp/mosaic-act1-l2id7s` with 22 NOT_RUN baseline/candidate
rows and mode 0700. This temporary workspace is reproducible, not durable test
evidence. No model trials, active-session changes, demo-candidate changes,
messages or migration implementation followed. Darkwing readiness review is
prepared but not delivered or assigned automatically.
- 2026-09-07 — Owner directed full annexation and Mosaic nomenclature. Rewrote the
twelve imported references plus the existing SOUL guide as thirteen Mosaic
concept pages in docs/concepts, with an index and explicit implementation status.
Replaced the old reference copies; source lineage and LICENSE now live under
docs/reference/concepts. Renamed the benchmark concept to agent-behavior-tests.
Updated ACT-1's cases and preparation utility to validate current Mosaic hashes,
preserve original-source metadata and stage concepts/provenance separately.
Thirteen concepts, license attribution, eleven scenarios and fifty local links
verified; fresh review workspace /tmp/mosaic-act1-xVwXtF prepared with behavioral
results still NOT_RUN. Earlier import/history records retain their original facts.
No runtime launcher, deployed context, active session, or demo candidate changed.
@@ -0,0 +1,54 @@
# Repository consolidation — owner request and verified boundaries
Owner requested consolidating v1 below v1/, moving the new foundation to the
canonical checkout, and using a refactor branch of mosaicstack/stack.
Status: initial read-only assessment, no move or Git mutation performed.
Verified repositories:
- /mnt/storage/src/mosaic-stack: origin mosaicstack/stack, branch next,
HEAD 5d2770002612a09ae0cadc129b4ea30619133e8a; no tracked modifications observed.
Contains .git and .mosaic plus other hidden entries. Ignored/untracked content
has not been inventoried; tracked cleanliness does not mean empty local state.
- /home/jwoltje/src/mosaic-stack-dev-test: origin mosaicstack/stack-v2, branch main,
HEAD 9a5fbdbda74b16adf488fe28138b2ba69ea5e669; tracked modifications include shared
logs/CURRENT, docs/TOOLS.md and scripts/agent.sh. The reviewed inspector and its
review records are uncommitted. Generated/dependency/local files also exist.
The approved demo belongs to the second repository. The earlier user instruction
"from the repository directory" was ambiguous; no demo work was performed in v1.
## Recommendation
Consolidate the public project identity, but use a reversible, coordinated cutover,
not an in-place wildcard move. A shell * omits hidden files; naively including .git
would mix two different repositories. Existing absolute-path references and running
agents may continue targeting the canonical path after its contents become v2.
Those are concrete safety reasons to sequence the change, not reasons to retain
ambiguous names indefinitely.
Proposed destination layout: new foundation at canonical root, tracked legacy source
under v1/, one top-level Git repository belonging to mosaicstack/stack, a local
refactor branch, and preserved access to both original commit histories. This is
not a nested .git repository under v1. No branch push is inferred.
Before cutover:
1. Establish writer/index holds for both checkouts; do not abandon existing agent
goals. Determine which running sessions/launchers reference either path using
authorized metadata, not credential reads or unrelated fleet investigation.
2. Inventory tracked, untracked and ignored files; take verified reversible snapshots
outside the destination. Preserve local state, but do not automatically add it
to Git. No blanket staging or root directory cleanup.
3. Prepare the branch/import in an isolated staging checkout, retaining original
histories so accepted source SHA citations remain resolvable. Archive tracked
v1 source separately from private/local state and generated dependencies.
4. Carry approved v2 source and owned pending work with explicit manifests and
source ownership, not a wholesale overwrite of either .git directory.
5. Verify content identities, history, source/runtime/install boundaries and test
behavior; review path-dependent launch/config changes before activation.
6. Only after coordinated readiness, swap to the canonical path, explicitly restart/
rebind affected sessions as authorized, and retain rollback copies. Do not leave
two writable authoritative roots or silently redirect v1 workers into v2.
Open prerequisite: safe writer/session cutover and full local-content inventory.
This assessment does not move files, change deployment/config, force-push, delete
old work, rewrite accepted evidence or grant a v1 orchestrator authority over v2.
@@ -0,0 +1,95 @@
# Repository conversion #1495 — local completion record
Owner instruction: "Nothing is working in the repo now. Get the conversion completed."
Tracking: https://git.mosaicstack.dev/mosaicstack/stack/issues/1495.
Coordinator: darkwing. Date: 2026-09-07 UTC.
## One authoritative checkout
- Canonical root: `/mnt/storage/src/mosaic-stack`.
- New Mosaic Stack foundation: root-level source, scripts, contracts and docs.
- Legacy Mosaic Stack: `v1/`, retained as archival source, not an active deployment.
- Working branch: `refactor`.
- Origin: `https://git.mosaicstack.dev/mosaicstack/stack`.
- `/home/jwoltje/src/mosaic-stack-dev-test` is a compatibility symlink to the
canonical root. It is not a second working tree.
- Exactly one top-level `.git`; no nested `v1/.git`.
The original v2 repository is retained as the `stack-v2-archive` fetch remote;
its push URL is deliberately disabled. No remote repository was deleted or pushed.
The canonical branch has no automatically configured push/upstream authority.
## Git and pending-work preservation
Original heads:
- v1: `5d2770002612a09ae0cadc129b4ea30619133e8a` (`next`).
- v2: `9a5fbdbda74b16adf488fe28138b2ba69ea5e669` (`main`).
The local conversion merge preserves both histories. Its first parent is the v2
head, its second parent is the v1 head. The selected root tree is v2; the exact
tracked v1 tree is imported under `v1/`. This is not a merge into `next` or `main`.
All v1 refs are additionally preserved below `refs/archive/v1/`; original v2 refs
remain available. Accepted source/review SHA citations still resolve.
Only the 3,507 already-tracked v1 paths and these four conversion documentation
paths belong to the conversion commit:
- `AGENTS.md`
- `README.md`
- this completion record
- `docs/plans/reviews/2026-09-07_repository-conversion-verification.json`
All earlier v2 tracked modifications, untracked inspector/source/reviews, ignored
files and generated/dependency state were copied without bulk staging. They remain
pending work, not silently committed or approved. Legacy ignored/local files were
preserved on disk but were not automatically added to Git. No source implementation
bytes, role authority or immutable inspector inputs were changed by the conversion.
## Verification
Full pre-cutover snapshots and staged copies matched file hashes, types, permissions,
UID/GID, symlink targets and regular-file mtimes. Both sources were checked again
for drift immediately before cutover. Post-cutover payload checks matched as well.
Inventories: 4,149 v1 entries including Git metadata; 15,734 v2 entries including Git
metadata/dependencies; 4,105 legacy payload entries excluding `.git`.
From the canonical checkout with synthetic HOME/TMPDIR and no provider credentials:
- Inspector Node tests: 80 passed.
- Inspector selftests: 43 passed.
- Schema oracle: zero disagreements, 1,568 records.
- Foundation checker: passed, with its existing synthetic/runtime limitations.
- Config/auth/conductor tests: 24/15/17 passed.
- Network/engine command guards were not invoked.
- All 382 approved r6 inspector file identities and every pinned input were unchanged
before and after the tests.
Command/log receipts: `docs/plans/reviews/2026-09-07_repository-conversion-verification.json`.
Git object integrity and exact archival tree/parent relationships are checked before
and after the local commit. No v1 build, service, native runtime or deployment test
was run. Task/release suites remain NOT RUN / DEFERRED; the previous offline-demo
ruling is not expanded into deployment acceptance by this conversion.
## Rollback material — outside the repository
Verified copies and private inventories:
`/mnt/storage/src/.mosaic-stack-conversion-20260907T172430Z/`
This contains `before-v1/`, `before-v2/`, `retired-v1-original/`, inventory JSON,
conversion state and verification logs. The original v2 directory is also retained:
`/home/jwoltje/src/.mosaic-stack-dev-test.pre-conversion-20260907T172430Z`.
These are retired rollback copies, not alternative development roots. Do not edit
or launch from them. Before any rollback, stop writers and preserve any new work.
A rollback must move the converted checkout aside, verify/remove only the old-v2
compatibility symlink, and restore the two original directories to their original
paths. Never overwrite/delete newer work or use blanket recursive deletion.
## Remaining boundaries
No push, PR merge, live service/config/data migration, package install or history
rewrite occurred. The old absolute v2 path remains usable through the explicit
compatibility link; historical evidence was not bulk rewritten to claim new paths.
Future work should use the canonical path above. Reopen sessions there when convenient.
The inspector is independently code-approved for the bounded offline demo; Jason's
A9 demo acceptance remains pending. Neither that acceptance nor future runtime
permissions are granted by the repository conversion.
+58
View File
@@ -0,0 +1,58 @@
# Conductor protocol — poor-man orchestration loop
How the stack orchestrates headless pi workers to do work on itself.
## Role contracts
Role authority is declared in role contracts, one file per role, under
`roles/` (e.g. `roles/conductor-policy.json`). The repository root holds
only first-class, bootstrap-required configuration; role contracts are
tracked, versioned files whose changes arrive as reviewed commits.
## Roles
| Role | Runs where | Powers | Never has |
|---|---|---|---|
| **Conductor** | host (assistant or owner) | git (clone/commit/push), task dispatch, review, verification suites, Gitea | nothing new |
| **Worker** | container (headless pi via `scripts/run-task.sh`) | read/write/edit/bash inside its workspace; persistent session on request | git credentials, docker socket, host filesystem |
## The loop
1. **Decompose**: conductor turns a goal into worker tasks small enough to
specify completely in one prompt (file paths, acceptance criteria, style
constraints, verification the worker can run itself, e.g. `node --check`).
2. **Mirror**: conductor maintains the repo clone at
`<dataRoot>/workspaces/stack-repo` (host-side git; workers see it read-write
through their workspace mount).
3. **Dispatch**: `scripts/run-task.sh run <worker-task.json>` — worker edits the
clone. Session name `worker-<n>` keeps continuity across refinement rounds.
4. **Extract**: `git -C <workspace> diff > patch` — the worker's entire output
is a reviewable diff. Run record (result.json, stderr.txt) is the receipt.
5. **Review**: conductor reads the diff line by line. Bad output → refine the
prompt, re-dispatch (same session: "your patch had these problems…").
6. **Integrate**: conductor applies the patch to the real repo, runs the full
suites, commits and pushes. Suites failing → revert apply, back to step 5.
7. **Record**: update CURRENT.md, BUILD-LOG, close the Gitea issue.
## Guardrails
- Workers never receive credentials; they never run git; they never leave the
workspace (container is the boundary; tools allowlist is the gate).
- Every worker diff is reviewed by the conductor before integration. No
auto-apply. (Auto-apply would be a capability-policy decision for later.)
- Verification is mechanical: suites + `node --check` / `bash -n` gates.
- Recursive decomposition = "fail → smaller task", never "hope."
## Worker task template
```json
{
"taskVersion": 1,
"id": "t-worker-<name>",
"prompt": "<full spec: goal, files, constraints, acceptance, self-checks>",
"workspace": "stack-repo",
"capabilities": { "tools": ["read", "write", "edit", "bash"] },
"session": "worker-1",
"timeoutSeconds": 600
}
```
+186
View File
@@ -0,0 +1,186 @@
# CURRENT — single source of "what happens next"
This file always names exactly one next action. Any "continue" / "next" /
"proceed" message means: execute the action below, fully (implement → test →
verify against its acceptance criteria → commit → push → close the issue →
update this file to the next action). No ambiguity, no re-planning.
## Next action
Review `docs/plans/2026-09-03_auth-provider-harness-registry.md` and reconcile
Gate 7 with later owner decisions and #53's workspace/session model. This is
registry review alignment only; no registry implementation is authorized.
Completed commit/push wave: R5 transport-only tmux correction independently
APPROVED by Filbert, published on refactor at
`69f10a40623bf1809e7cda1f800bce3a5d80fb51` with all 17 milestone tags; remote
identities verified. Exit 0 means transport dispatched, application acceptance
unknown. Earlier rejected confirmation candidates remain historical evidence.
Jason's A9 acceptance and suite results are recorded in
`docs/plans/reviews/2026-09-07_a9-owner-acceptance.md`.
#53 published planning inclusion is verified in
`docs/plans/reviews/2026-09-07_issue53-closeout.md`; Jason retains issue closure.
No main/next merge, deployment, or adoption of newer relocation work occurred.
Correction (2026-09-07): the following conversion/A9 text is a historical
pre-wave checkpoint, not a competing pending acceptance or test-deferral gate.
Use the canonical checkout `/mnt/storage/src/mosaic-stack`, branch `refactor`,
origin `mosaicstack/stack`. New foundation is at root; `v1/` is the legacy archive.
The old `~/src/mosaic-stack-dev-test` path is only a compatibility symlink.
Jason's requested local conversion (#1495) is completed at commit
`127a54fdff1fe6ae56c3197edddf957481465db4`, preserving both parent histories and
all pending work. Conversion record:
`docs/plans/2026-09-07_repository-consolidation-completed.md`.
Pre/post-commit tests and source identity checks passed; no push or live change.
The index is clean. Existing uncommitted work was preserved, not blanket staged.
Owner confirmation that no work was active superseded the earlier index hold for
this conversion; no new writer assignment or permission grant is inferred.
Inspector: FI-FILBERT-8 APPROVED at r6 manifest
`a4a4493000aff5905337a643886ca36e7c5377d52deed77b8aeab7174ca73dcf`.
All 382 file identities and pins remain unchanged. Demo guide:
`docs/plans/reviews/2026-09-07_foundation-inspector-demo.md` (its old checkout path
still resolves through the compatibility link; prefer the canonical path above).
Four examples reran successfully after conversion. A9 remains owner acceptance,
not an automatic consequence of tests or migration. Task/release coverage remains
NOT RUN/DEFERRED, not deployment green. Native-parser/equality qualifications remain.
Historical instructions below describe earlier checkpoints, not competing current
assignments or authority over the archived v1 implementation.
## Earlier owner and source checkpoints
Historical context below; the current candidate and live goal checkpoint supersede
the earlier partial-draft descriptions.
The prior hands-on checkpoint demonstrated launch, workspace listing, and conversation resume from Jason's supplied output. Fresh context and mission recovery were not tested. The owner redirected to this planning exercise; no broad foundation acceptance is inferred.
Owner ruling recorded 2026-09-06 as R16-R17: current approved SOUL on launch, stable per-execution inputs, and a shared launch/configuration hash reference for TUI/GUI/WUI mismatch notices recommending Fresh. D10 is partly resolved. Q20/Q21 now settle broad fingerprint categories and automatic non-blocking notices plus on-demand checks; exact field/dependency hashes and delivery mechanics remain D16. This does not advance the phase or authorize implementation.
Interview round 1 recorded: Q1 permits linked project/workspace missions, Q2 permits bounded system registration/assignment authority, and Q3 limits visibility to shared project information and explicitly permitted workspaces. Q4 clarification A creates and announces the first conversation without an offer; later default launches resume, while missing/damaged established sessions cause an error. Round 2 Q5-Q9 confirms single-parent hierarchy, the Fresh recovery information set, delegated within-plan non-destructive decisions and routine reviewer acceptance, assignment-only default Abandon, and explicit authorization for prerequisite work. Delegated authorization need not prompt the user each time; user phase checkpoints remain. Round 3 Q10-Q14 permits unassigned discussion/inspection with recorded assignments for changes, requires an interactive active-session conflict notice and offer to connect, separates shared work records from transcript grants, chooses concise audit metadata with controlled evidence, and scopes membership revocation to affected executions. Round 4 Q15-Q19 requires explicit service conflict handling, one controlling interface with authorized observers, controlled Fresh replacement, delegated evidence-based recovery without blind replay, and affected-execution blocking on audit failure. Round 5 Q20-Q24 extends fingerprints to shared behavior-affecting configuration, requires automatic non-blocking notices plus on-demand checks, scopes personal context, retires closed workspaces without deletion, and requires explicit reviewed legacy adoption. Round 6 Q25/Q26 pauses affected work for reconciliation after approved plan changes and chooses standard scope roles with registration-specific narrowing. Jason subsequently confirmed shared understanding of intended behavior. Jason then authorized phase 2. A tool-free source-analysis run, r-20260906T024609Z-68ee7f, succeeded; pinned 0.84.4 documentation was extracted from the existing image without starting its extraction container. These are source/document findings, not runtime feature tests or independent approval. The first contract candidate is partial. Q27 A now settles command-audit granularity; dependent schema and enforcement drafting may continue within phase 2. Full schema/plan approval remains pending.
## Accepted phase-2 checkpoint (historical)
- Goal: issue-53-phase2. Objective: an owner-reviewable contract for agents,
projects, workspaces, sessions, permissions, and audit evidence. Completion
owner: Jason. Author/workspace/session remain those recorded below.
- State: satisfied. Jason explicitly accepted phase 2 after the plain-language
explanation of the planning baseline and separate later gates. P2-7 is complete.
REVIEW.md retains D1-D16 and the unproved implementation mechanisms. This is
owner plan acceptance, not independent technical or security certification.
- Acceptance: repair/check schemas and fixtures, complete the operation/recovery
contract, resolve material behavior decisions, prepare a review package and
one user-testable increment recommendation, then obtain owner acceptance.
- Evidence: `python3 docs/plans/foundation-v1-candidate/check.py` passes 38 command
and 38 record shape cases, 16 path cases, 7 restricted-domain hash vectors,
155 runtime/control/artifact cases and 35 synthetic rule-model cases. Ten
deliberately shape-valid forgeries still require trusted runtime rejection.
These are not runtime security tests or independent acceptance.
- Reboot fixture defects were repaired, not discarded. Eleven positive records
now include their common envelope; negative mutations were preserved. Required
calendar/UTF-8/control-character checks are explicit in the author checker.
- Next gate: separate owner authorization for mapping, not more phase-2 approval.
No mapping or implementation started. This session continues the
file-based goal and has not configured an extension/timer for it. Separate #54
work subsequently added/tested a project-local goal extension, as recorded in
the shared logs; that work and its state were left untouched. This session has
not migrated issue-53-phase2 into that runtime. Elapsed time never grants approval.
- No new worker dispatch, external reply obligation, or uncertain external action
initiated by this phase-2 session is outstanding. No numeric work budget supplied;
aggregate usage remains unavailable.
- Authority remains phase-2 planning and read-only investigation. No runtime
implementation, mapping, migration, commit, push, or issue closure.
## Prior recovery checkpoint, 2026-09-06 03:42 UTC
Historical snapshot below; the live goal checkpoint above supersedes its pause
and unfinished-fixture status.
- Goal: issue-53-phase2. State: paused by owner steering. Writer: darkwing,
pi session `01a06e48-0718-71f2-a889-c263c4800fb9`, explicit working directory
`/home/jwoltje/src/mosaic-stack-dev-test`, project `mosaicstack/stack-v2`.
This is the existing single-writer planning assignment, not a runtime claim.
- HEAD remains `69d1bb3`. Preserved all uncommitted planning and unrelated skill
work. No reset, cleanup, commit, push, or implementation occurred.
- Five planning artifacts survived in `docs/plans/foundation-v1-candidate/`:
command schema/fixtures, `check.py`, and record schema/fixtures. The three
command-check file hashes match the pre-reboot checksums.
- `python3 docs/plans/foundation-v1-candidate/check.py` passes 38 shape fixtures
and 5 deliberate shape-valid forgeries. This does not prove runtime security.
- The unfinished record checker is NOT integrated into check.py. A read-only
diagnostic found 13 expectation mismatches: all 11 positive record fixtures,
plus unicode-byte-limit and bidi-control. The first positive lacks five common
envelope fields, indicating fixture generation is incomplete. Do not count
negative cases as meaningful until positive fixtures are repaired and rerun.
- System config validates, Docker responds, and the pinned image ID and prior
research result hash still match the phase-2 evidence. No Mosaic worker
container was running at inspection. Pinned temporary docs remain available.
- Next work after explicit resume: repair record fixtures, enforce/test UTF-8
byte and control-character path checks, integrate both schema suites, then
complete the remaining phase-2 record/permission/lifecycle work and owner gate.
- No outstanding assistant-initiated external action or reply is known. Wake is
manual: Jason sends a resume instruction. No timer or automatic continuation
is registered. Aggregate usage is unavailable; no numeric budget was supplied.
## Queue (ordered per docs/plans/ROADMAP.md)
1. Paused for owner alignment: review `docs/plans/2026-09-03_auth-provider-harness-registry.md` and reconcile later owner decisions and #53's workspace-session model. Gate 7 remains unresolved. No registry implementation is approved, and this work does not resume automatically after the planning exercise.
2. Deferred by owner: CI runners (Gitea hardware slow); second real adapter; push automation
## Rules
- One action in flight. Update this file at the END of every action.
- Blocked? Move the item to "Blocked" below with the reason and stop.
- Completed actions move to the log at the bottom (date + issue + result).
- Corrected entries are marked, never silently rewritten (see 2026-09-03 dedup note).
## Blocked
(none)
## Completed log
- 2026-09-07 — Owner-directed concept annexation: [Mosaic concepts](../concepts/README.md) now owns the adapted pages; source/license metadata moved to docs/reference/concepts. Test-package links and content hashes updated; preparation checks pass. Documentation ownership changed, not runtime behavior or the demo queue.
- 2026-09-07 — Owner-requested ACT-04 groundwork: [Darkwing concept test package](act-1-tests/README.md) prepared with twelve pinned OpenClaw references, synthetic cases, candidate SOUL and offline preparation utility. Import checks and existing isolated launcher tests passed; behavioral cases NOT_RUN and runtime features deferred. Current demo queue unchanged.
- 2026-09-07 — Owner-requested shared planning capture: [ACT-1 — Agent context, templates, and staged migration](2026-09-07_agent-context-templates-and-migration.md) records single-agent SOUL authority, bootstrap templates, evaluation work, demo gates, and deferred structural migration. Runtime tasks remain unassigned; current demo queue unchanged.
- 2026-09-07 — Owner-requested naming cleanup: native helper is now `scripts/agent-host-dev.sh`; callers and documentation updated, empty `scripts/tui/` removed, launcher checks passed. Historical log paths retain their original names.
- 2026-09-07 — Owner-requested entry-point consolidation: `scripts/agent.sh --host-dev darkwing` delegates to the native helper; default container execution retained. Host and isolated container routing/refusal checks passed; inspector queue unchanged.
- 2026-09-07 — Owner-requested launcher follow-up: Darkwing's launch.sh now delegates to `scripts/tui/launch.sh darkwing`; existing session/context regression checks pass.
- 2026-09-07 — Separate owner-requested Darkwing launcher: `agents/darkwing/launch.sh` provides a native development TUI with explicit context, skills, coding tools and `/goal`; offline launcher tests and no-model TUI smoke passed. Inspector queue and approval gates unchanged; no commit/push.
Note (2026-09-03): this log was deduplicated after editor-session races
appended duplicate blocks. The dedup removed repeated lines only; every
distinct action appears exactly once, in completion order. Ground truth:
git history + Gitea issues.
- 2026-09-03 — POC: containerized pi hello-world (poc-container-hello-v0)
- 2026-09-03 — M1 configuration-driven hello world (#1#4; config-hello-v1); hotfix #5 stdin detach
- 2026-09-03 — M2 mission/task abstraction (#6#9; mission-task-v1); hotfix #14 release identity in task path
- 2026-09-03 — M3 release model + safe updates (#10#13; release-model-v1); drills: update/refusal/rollback
- 2026-09-03 — M14 live user context layer (user/ dispatched to all launches; 0.0.9 built)
- 2026-09-03 — M15 agent seats: per-agent SOUL + role contracts (#36; agent-seats-v1); roles/ convention (root = bootstrap-only)
- 2026-09-03 — M13 interactive TUI agent + TOOLS.md (#35; interactive-agent-v1); release 0.0.8 activated
- 2026-09-03 — M12 conductor auto-apply policy (#34; auto-apply-v1); 17 conductor selftests
- 2026-09-03 — M11 session forking (#33; session-fork-v1); child recalls ancestor, base untouched
- 2026-09-03 — M10 run-record retention (#32; retention-v1); prune keep-N, dry-run default, receipt
- 2026-09-03 — M9 mission capability policy (#30; mission-policy-v1); least-privilege intersection
- 2026-09-03 — test UX: green OK/red FAIL status colors; NO_COLOR-aware
- 2026-09-03 — M10-era hotfix: retry lineage (#28) + AGENTS.md/SESSIONS.md recovery shim
- 2026-09-03 — release 0.0.10 packaged and health-gated activated (user context + agent seats live)
- 2026-09-03 — release 0.0.11 shipped (onboarding + live user context); ROADMAP.md agreed (M16M19); CI deferred by owner
- 2026-09-03 — M16 release self-determination (#38; `release.sh ensure` at launch, drift warnings, recursion guard) — logged late: CURRENT.md had gone stale while M16/M17 shipped; ground truth = git history
- 2026-09-03 — M17 skill lifecycle + ms-* skill set completion (#40#42; skill-lifecycle-v1); release 0.0.12 packaged, health-gated active — logged late, same staleness correction
- 2026-09-03 — conductor-loop calibration with live collaborator (#43): dispatch via agent-send.sh → receipt → line-by-line diff review → suite-gated integration; docs/TOOLS.md gains Tools (host-side) section + corrected suite counts
- 2026-09-03 — skill revisions adjudicated (#44): ms-communications integrated as-authored; ms-conductor redraft + conductor remediation (refusal vs outage); TOOLS.md release.sh ensure row
- 2026-09-03 — M18 seat-role progressive capability restriction (#45; roles resolve to contracts, ceiling ∩ seat grant, fail-closed refusals, roles/researcher.json); task suite 74 → 88
- 2026-09-03 — M18 follow-up: fail-closed seat resolution under MOSAIC_AGENTS_DIR override (#46, owner decision after live verification); task suite 88 → 90; next action M19
- 2026-09-03 — M19 harness auth tooling (#47; auth.sh status/accounts, agent.sh --auth per-launch injection via PI_AUTH_FILE, test-auth suite 13 cases with secret-never-printed assertions); agreed sequence M16M19 complete, M20 owner-gated
- 2026-09-03 — M19 correction: auth ownership moved to the data root (#48, owner direction — the stack never writes to default harness config locations; ROADMAP standing decision); auth.sh config-driven, accounts at <dataRoot>/auth, 0600 enforced; test-auth 13 → 15
- 2026-09-03 — harness/provider/auth registry specification drafted (#49): agent.json harness declaration, central provider/account/settings registries, runtime seat selection, mechanical per-harness materialization, centralized OAuth refresh, Ollama endpoints, CLI contract; implementation blocked pending ten-gate review
+51
View File
@@ -0,0 +1,51 @@
# HARVEST — patterns from mosaicstack/stack + the live fleet runtime
Sources surveyed 2026-09-03 (owner-authorized):
- `~/src/mosaic-stack` (drifted) and fresh clone at
`~/src/mosaic-stack-worktrees/v2-harvest` (branch `next` @ 0db2d19a,
"mosaic doctor structure-anchor provisioning check (T51 WP0b)")
- `~/.mosaic/fleet/` — live fleet runtime: `bin/`, `roles/` (+`-ng`),
`agents/<seat>/`, `auth/`, `roster.yaml`, `SEAT-CUTOVER.md`
Rule honored: nothing pulled whole-cloth. This file is the pattern ledger;
each adoption lands through its own milestone with tests.
## Pattern ledger
| # | Pattern (source) | Verdict | Destination |
|---|---|---|---|
| 1 | **Channel dispatcher**`fleet/bin/mosaic`: shipped npm CLI is default; dev worktree opt-in via `MOSAIC_CLI_WORKTREE` health-checked with `--version`; `MOSAIC_FLEET_CLI_OFF` forces pass-through; launcher never writes to user dirs | **steal** | M20 unified `mosaic` CLI |
| 2 | **Single canonical launcher + per-seat env**`bin/launch-seat.sh` is the only launcher; per-seat variation lives in `launch.env` ("per-seat copies drift") | **steal** | M20 CLI + agent.sh stays thin |
| 3 | **Refuse-rather-than-guess provisioning**`bin/new-seat.sh`: seat classes, `--adopt` never overwrites real identity files, `--mint` fails closed (unminted = git writes fail closed), `--dry-run` | **steal** | seat instantiation (M15 evolution) |
| 4 | **Role manifest schema v3**`roles/code-ng/mosaic-core.manifest.json`: capability bindings (id/effect/binding/status), `tools[]`, `forbiddenTools[]`, `shell.mode: denied`, `credentials` policy, `evidence` journaling, `digestRules` postcondition (`active-set-exact`, sha256 over manifest) | **adapt progressively** | M18 seat-role restriction |
| 5 | **Per-seat git identity**`MOSAIC_GIT_IDENTITY=<seat>` + `GIT_AUTHOR_NAME/EMAIL`; incident 2026-08-19: seven seats committed as the wrong identity via directory git config | **steal** | M18/M20 (we have no per-seat git writes yet — adopt before we do) |
| 6 | **Auth slots + symlink shares**`fleet/auth/<provider>/<account>/` credential dirs; seat `.pi/agent/` symlinks `auth.json settings.json models-store.json bin skills` | **adapt** | M19 auth tooling |
| 7 | **Launch-time pre-flight probes** — every consumed path checked before start; pi's `existsSync` silently appends literal path strings as prompt text (documented trap) | **steal** | M19 launch check + our existing refusal style |
| 8 | **Prompt ordering doctrine** — estate context first, seat SOUL/AGENTS last ("closest to the task wins") | **matches ours** | document in adapters/README |
| 9 | **`--check` mode** on launchers (verify provisioning without launching) | **steal** | agent.sh / release ensure |
| 10 | **ENTITY.md per seat** — identity record incl. git identity, explicitly secret-free | **steal** | seat records (extend seat.json or add ENTITY.md) |
| 11 | **`mosaic doctor`** — structure-anchor provisioning check (T51) | **adapt** | M20 `mosaic --check` |
| 12 | packages/ anatomy on `next`: `mosaic` (CLI), `auth`, `agent`, `config`, `forge`, `quality-rails`, `comms`, `macp` | reference map | M18/M19/M20 deep reads |
## Skips (for now)
- tmux comms watchers, lanes/, board/, memory/, journal/ — L5/L6 fleet
territory; our conductor loop is deliberately not a resident service.
- openbao/swarm/coolify compose stacks — deployment, far future.
- roster.yaml + roster.schema.json — superseded by our agents/ definitions
for now; revisit when fleet registration returns.
## Owner-corrected design points (2026-09-03)
- M17 revised: skills must reach **non-conductor seats as role-scoped
subsets** — agents invent tooling when the right tool isn't supplied.
Role contracts will declare skill sets; the runner loads them explicitly
(mock-verified `--skill` + `--no-skills` coexistence pending).
- M19 revised (owner's own diagnosis): auth selection was symlinks from
`agents/<seat>/.pi` into `fleet/auth`, and a missing symlink silently
fell back to defaults. Our design: the launcher reads the seat's declared
account, **ensures** the symlink/auth selection before TUI launch, and
refuses loudly on mismatch — check + fix, never silent default.
- Unified `mosaic` CLI is directional (`mosaic tui`, `mosaic yolo
<harness>`, `mosaic --check`): tracked as M20, harvested from pattern #1
and stack `packages/mosaic`.
+189
View File
@@ -0,0 +1,189 @@
# ROADMAP — agreed path forward
Status: planning aligned with owner 2026-09-03. Feature work follows this
order unless the owner re-orders. Design notes below are commitments about
intent, not implementation — each milestone still gets its own issue, tests,
and BUILD-LOG phase.
## Standing decisions (owner, 2026-09-03)
- **Pi is the main TUI harness** and the reference checkpoint. Multi-harness
(Codex, Claude, OpenCode) comes after pi is stable as the primary seat.
- **CI is deferred.** Gitea runs on slow hardware; local suites + verify are
the quality gate. Revisit when code volume justifies runners.
- **The user never manually runs release.sh.** The system determines what is
installed and self-aligns (M16).
- **Tooling documentation is agent-accessible** via the ms-tools skill (M17),
referenced from AGENTS.md.
- **Capability restriction becomes progressive and role-based** (M18),
porting prior Mosaic Stack concepts where they fit the file-based runtime.
- **The stack never writes to default harness config locations** (owner,
2026-09-03, #48): `~/.pi` — and any future harness home — is read-only to
Mosaic tooling; the only interaction is the existing read-only credential
mount. Mosaic-managed auth lives under the data root (`<dataRoot>/auth/`).
## M16 — Release self-determination
Problem: after `reset.sh` the activation pointer is gone and the owner had
to know about `scripts/release.sh activate`. The system must determine what
is actually installed.
Design:
- New `release.sh ensure` (also invoked automatically by the launchers via
`common.sh`): compares desired (`RELEASE`) vs packaged image vs active
pointer.
- Fast path (drift = pointer missing/mismatched, image already packaged):
restore the pointer without a health gate — the release was previously
health-gated; reset is data loss, not a new release.
- Slow path (new version in RELEASE, or image missing): package beside →
full health gate (exact-marker model run) → atomic pointer swap. This is
the M3 gate-then-flip, now automatic.
- Acceptance: post-`reset.sh`, the next `hello.sh`/`agent.sh` self-restores
activation with zero manual release commands; a bumped `RELEASE` auto-
activates after its health gate; a failing health gate leaves the previous
release active and records the refusal.
## M17 — ms-tools skill + role-scoped skill lifecycle (DESIGN REVISED per owner)
Problem: agents without supplied tooling invent their own, even when a
functional method exists. Skills must reach non-conductor seats as
role-scoped subsets, with an explicit lifecycle.
Design (owner-specified):
- Two machine-scoped directories under the data root:
- `<dataRoot>/skills-enabled/` — loadable by agents
- `<dataRoot>/skills-available/` — installed but disabled
- Lifecycle: **install** → available · **activate** → enabled ·
**deactivate** → back to available · **uninstall** → removed.
A skill not in skills-enabled is not enabled or available for use.
- Launch resolution: role contracts declare skill NAMES; the runner resolves
each against skills-enabled and passes `--skill <path>` explicitly.
`--skill` necessarily negates `--no-skills` (explicit provision wins).
Contracted-but-not-enabled → **refuse the launch loudly** (a silently
under-equipped seat is the exact failure mode this prevents).
- Containerization removes ambiguity: agents get only what is explicitly
provided; no discovery, no ambient skill leakage.
- Interfaces: CLI first (`mosaic skill install|activate|deactivate|uninstall|list`),
TUI and WebUI frontends call the same tooling later.
- The ms-tools skill is the first shipped skill (tooling reference for
conductor/vanilla sessions); role contracts then grant subsets per seat.
## M18 — Seat-role progressive capability restriction
Problem: capability restriction exists but is per-task/per-mission only.
Seats (agent identities) need governing ceilings that tasks cannot escalate
past — the M9 intersection chain extended: **role ceiling ∩ mission grant ∩
task grant = effective**.
Design:
- `roles/<role>.json` (role contracts, tracked): `roleVersion`, `name`,
`tools` ceiling (subset of pi built-ins), `network: "none"|"api-only"|`
`"open"` (declared now, enforced when network policy lands), `paths`
(future).
- Resolution chain in the runner: seat role contract ∩ mission capabilities
∩ task capabilities → effective tools (existing intersection logic, one
more link). Empty at any stage = tool-free run, loudly recorded.
- agent.json keeps `role` (already shipped in M15) — it now binds to
`roles/<role>.json`.
- Porting note: prior Mosaic Stack role/policy concepts inform this design,
but implementation is fresh on the file-based runtime (the standing
repository exclusion applies until the owner points at specific prior
files to port).
- Adapters unchanged: `MOSAIC_TOOLS` already carries the effective set.
## M19 — Harness auth tooling (pi checkpoint)
Problem: auth is manual (`/login`, `auth.json`); multi-account and
multi-harness stacking have no tooling.
Current facts (observed): one `auth.json` holds multiple providers
(anthropic, openai-codex, zai observed on the host) — provider stacking
already works. Multiple accounts per provider is the open question (the
owner runs two Claude accounts; openai-codex multi-account desired).
Design direction (pi-first checkpoint):
- `scripts/auth.sh status` — per-provider auth report (which providers have
credentials, source: auth.json vs env), no secrets printed.
- Multi-account: investigate pi's auth schema for per-provider account
entries; if unsupported natively, model accounts as named auth files
(`auth.<account>.json`) with a conductor-level selector — runtime-injected
per launch, same pattern as the read-only credential mount.
- Claude/Codex/OpenCode adapters stay parked; the auth tooling is
harness-neutral so it serves them when they arrive.
## M20+ — Unified `mosaic` CLI and the stack succession path
Direction (owner): a unified `mosaic` CLI (`mosaic --check`, `mosaic tui`,
`mosaic yolo <harness>`, `mosaic skill ...`, `mosaic release ensure`, ...).
Much larger task; channel-dispatcher doctrine harvested from
`fleet/bin/mosaic` (HARVEST.md #1).
Succession trajectory (owner): the v2 line is actively building the
REPLACEMENT for the mosaicstack/stack repo. At the appropriate level of
usurpation, v2 content becomes THE next-branch content on
mosaicstack/stack — a full refactor-and-replace with a functional system.
Not ready yet.
DECIDED (owner, 2026-09-03): v2 reorganizes to the `packages/*` monorepo
structure for continuity with the existing stack tooling and expectations.
Reference: harvest worktree `~/src/mosaic-stack-worktrees/v2-harvest`
(branch `next` @ 0db2d19a) and `packages/mosaic` (CLI), `packages/auth`.
Usurpation criteria to be defined before any replacement (proposed:
functional parity of P0 operations on the v2 runtime + owner sign-off).
### Restructure sequencing (decided 2026-09-03, owner question)
The monorepo restructure is NOT done first. Reason: the bash entry points'
final home is *replacement by* `packages/mosaic` commands, not containment -
moving them into packages/ now, then replacing them at M20, migrates
everything twice and churns the suites (the safety net) during peak feature
work. Instead:
1. **Layout doctrine frozen now** (this file + AGENTS.md): nothing new lands
at root; features build in their durable homes (`skills/`, `roles/`,
`docs/`, `adapters/`, `src/`).
2. **M20 phase 1 = restructure**: `packages/` monorepo skeleton absorbs the
script entry points as CLI commands (`packages/mosaic`), with
`packages/agent`, `packages/auth` following the harvested anatomy. The
bash scripts retire as they are absorbed.
3. **Succession**: at usurpation, v2 content (already in `packages/*` shape)
becomes the next-branch content of mosaicstack/stack.
### Target tree (post-M20-phase-1)
/ bootstrap + entry docs (unchanged)
+-- packages/
| +-- mosaic/ the CLI: tui, yolo, skill, release, onboard, check
| +-- agent/ seat/session runtime (absorbs src/ + adapters/)
| +-- auth/ provider slots, account ensure (absorbs M19)
| +-- config/ schema + policy validation (absorbs config tooling)
+-- contracts/ roles/ missions/ tasks/ templates/ declarative inputs
+-- skills/ shipped skills (installed to dataRoot on activate)
+-- docs/ plans, harvest, SESSIONS, TOOLS
+-- src/ container runtime (loader, dispatcher)
### Skills as discipline encoding (owner insight, 2026-09-03)
Skills are not tool menus - they encode operational discipline: order of
operations, gotchas, and failure history. The tool-race lesson and every
catalogued defect become skill content per role (e.g. a conductor skill
carries: sequential dependent calls; verify writes before claiming;
symlink-ensure before TUI; path pre-checks before pi flags). Role
contracts (M17) declare which skill subsets each seat receives.
## Explicitly deferred
- CI runners (owner: Gitea hardware is slow; local suites are the gate) —
revisit as code volume grows.
- Network egress policy enforcement (declared in role contracts at M18;
enforcement is its own milestone).
- Push automation (push stays an explicit act).
- Second real adapter; fleet/multi-agent; knowledge storage; portal.
## Completed reference (see docs/plans/CURRENT.md log)
M1 config-driven hello · M2 missions/tasks · M3 release model · M4 adapter
seam · M5 workspaces/capabilities · M6 sessions · M7 ergonomics · M8
conductor loop · M9 mission capability policy · M10 retention · M11 session
forking · M12 auto-apply policy · M13 interactive TUI agent · M14 live user
context · M15 agent seats + roles/ convention.
@@ -0,0 +1,156 @@
# ACT-04 test package readiness review (Darkwing)
Date: 2026-09-07. Author: Darkwing, at Jason's request.
Scope: review only. No model calls, no trial execution, no session, launcher
or demo-candidate changes, no staging, commit or push.
Reviewed sources: [pack README](README.md), [cases.json](cases.json),
[candidate SOUL](candidate/SOUL.md), [fixtures](fixtures/), baseline
`agents/darkwing/SOUL.md`, [ACT-1 plan](../2026-09-07_agent-context-templates-and-migration.md),
[concepts index](../../concepts/README.md), `scripts/prepare-concept-tests.mjs`,
`scripts/test-darkwing-launch.mjs`, `scripts/agent-host-dev.sh`, CURRENT.md.
## Verification performed
- `node scripts/prepare-concept-tests.mjs --check` passed: 13 Mosaic concepts,
license attribution, 11 synthetic scenarios; zero model calls.
- `node --test scripts/test-darkwing-launch.mjs` passed 2/2 in temporary
fixtures; no Docker, model or live state touched.
- Read the pinned pi CLI help (0.84.4): `-p`, `--no-tools`, `--session-dir`,
`--append-system-prompt`, `--no-context-files --no-skills --no-extensions`
are all available for headless trial runs.
## Scenario review against accepted foundation decisions
All 11 cases test judgment and honesty, not runtime features, and every
`runtimeGate` stays DEFERRED. That matches the pack's own limits. Case by case:
| Case | Foundation anchor | Verdict |
|---|---|---|
| C01 | D3 single SOUL, no fallback; fail-closed mandatory context | Sound. Fixture inventory excludes root and contract SOULs with reasons that match D3 and R16 |
| C02 | D2 directness, useful brevity | Sound; no runtime claim |
| C03 | Write-once preservation, managed-worktree concept (proposed) | Sound; worktree-is-not-isolation expectation matches worker sandbox boundaries |
| C04 | Pause-then-reconcile owner ruling (Q26); never guess, verify | Sound; historyGap expectation matches restart-reconciliation decisions |
| C05 | Honest interruption semantics; design claim vs observed behavior | Sound; no-cancel-claim expectation mirrors SOUL evidence discipline |
| C06, C11 | One controlling interface, observers without control (Q16); least privilege | Sound; attribution-does-not-grant-access matches policy invariants |
| C07 | Startup marker is not verification; STANDARDS forbids relabeling skipped checks | Sound; the strongest discipline case in the pack |
| C08 | Accurate failure reporting; acceptance separate from implementation | Sound; mirrors current FI-FILBERT-8 discipline |
| C09 | Authorization never accrues from elapsed time or repetition | Sound; external-claim expectation matches role-authority rules |
| C10 | Wake registration requires authority; no prose-note scheduling | Sound; matches the no-automatic-continuation rule |
No case asks the model to claim a proposed feature exists. No case touches a
real user profile, live session, credential or destructive action. The
`expected` rubrics stay out of model input under the proposed trial mechanism.
## Gaps
- **G1, C01 self-reference risk.** The prompt says to treat
`context-inventory.json` as synthetic data, but the trial agent is itself
launched with a SOUL from the workspace. A sharp model may answer about its
own context instead of the fixture. Not a defect, but judges must require
the answer to cite the fixture. Keep the synthetic-data framing verbatim
when composing input.
- **G2, launcher does not fit trial isolation.** `scripts/agent-host-dev.sh`
injects repo AGENTS.md, STANDARDS, the live data-root USER file, 10 skills,
the goal extension and write tools, and stores sessions under
`$REPO/.pi/state/<agent>`. Its `--soul/--user` overrides help, but the rest
cannot be turned off. The pack requires exactly one SOUL, synthetic user
context, no extensions or write tools, and a fresh session per case and
variant. Running the pinned pi binary headless from the staged workspace
covers all of that without adding repo code or a new launcher.
- **G3, baseline and candidate are not style-only twins.** The baseline SOUL
carries Mosaic operating disciplines: evidence classes, preserving other
agents' work, no self-granted authority. The candidate is voice only, with
one honesty sentence. Eight of eleven cases largely test judgment. A
candidate FAIL there measures missing content, not tone; a PASS shows the
voice holds without explicit rules. Both readings are useful, but the trial
record should state which question is being asked so a FAIL is not
misread as a style defect.
- **G4, judging protocol is unwritten.** RESULTS.json rows have evidence
fields but no per-expectation verdict shape. Proposal: judges record each
expectation as pass or fail with a quoted line from the actual output;
style calls stay with Jason, never automated.
- **G5, evidence durability.** The staged workspace lives in the OS temp
directory and may be wiped. Actual outputs and RESULTS.json must be copied
to an assigned durable location before any acceptance relies on them.
Destination needs Jason's call; a `trials/` subfolder here is one option
and would be a repo write needing his authorization.
## Proposed first trial: bounded baseline versus candidate
Scope: two cases, two variants, four model calls. Optional single repeat of
an ambiguous case, capped at eight calls total.
Cases: ACT-C02 (concision; no fixture; pure style comparison) and ACT-C08
(honest completion reporting; hard rubric under the candidate's voice). Both
have `runtimeGate: null`, so nothing in the trial can be confused with a
runtime feature test. Together they answer the two questions that matter
first: does the candidate's voice read better on a plain answer, and does it
keep hard honesty discipline without the baseline's explicit rules.
Mechanism, reusing existing verified pieces only:
1. `node scripts/prepare-concept-tests.mjs` stages a private 0700 workspace.
Zero model calls. No repo directory is written.
2. Per run, compose one context file: `contracts/CONSTITUTION.md` plus
`fixtures/USER.md` (synthetic Casey profile) plus exactly one SOUL, the
variant's. No AGENTS.md, no STANDARDS, no skills, no second SOUL.
3. Run the pinned `node_modules/.bin/pi` headless: `-p`, `--no-tools`
(fixtures are inlined in the prompt, so there is nothing to sandbox),
`--no-context-files --no-skills --no-extensions --no-prompt-templates
--no-themes`, `--append-system-prompt <composed file>`, provider and model
taken from the validated `load_config` values, `--session-dir
<workspace>/sessions/<variant>/<case-id>` so every case and variant gets a
fresh session identity.
4. Authentication is referenced in place if required, never copied into the
workspace, and never printed.
5. Save each actual response under the workspace; update the four
RESULTS.json cells for these cases with status, evidence and quoted
expectation verdicts. All other 18 rows stay NOT_RUN.
6. Copy outputs and RESULTS.json to the durable location Jason names.
Recorded before execution, per the pack's binding requirements: harness and
version (pi 0.84.4 pinned), provider/model, context file SHA-256 per run,
both SOUL hashes (already in RESULTS.json variants), session directories,
operator and reviewer names, and the model-call budget.
What this trial does not do: no runtime gate moves to PASS, no template is
promoted, the candidate never touches active Darkwing, and nothing here is
demo acceptance. A hard rubric failure fails the case regardless of tone;
Jason alone judges brevity, candor and personality.
## Preserved by this review
`agents/darkwing/**` including SOUL, launcher and CONTEXT; `scripts/agent*.sh`
and all launch inputs; `.pi/state/darkwing` sessions; the Rocko and Filbert
tmux sessions and frozen demo candidate; Dewey's shared git index. Nothing
staged, committed or pushed. CURRENT.md's next action (await FI-FILBERT-8 r6
verdict) is unchanged by this review.
## Decisions needed from Jason
1. Authorize trial execution and name operator, reviewer, and the call/cost
budget (proposal: 4, hard cap 8).
2. Choose the durable evidence location.
3. Later: approve the G1 judging note before C01 ever runs, and confirm G3's
interpretation rule, that a candidate judgment FAIL is a content finding
first, belongs in the trial record.
## Correction, appended 2026-09-07 (STANDARDS attribution)
Per Jason's direction. The C07 row above says "STANDARDS forbids relabeling
skipped checks". That attribution is wrong. `contracts/STANDARDS.md` contains
only the startup-verification answer rule: answer with the requested value,
no added explanation or formatting. The "cannot relabel skipped checks as
passed" language comes from the ACT-1 plan's "Current behavior to preserve
and then migrate" section on startup-marker separation, and the evidence
classes discipline lives in the SOUL and AGENTS.md, not in STANDARDS.
The C07 expectation itself (refusing to infer hook enforcement from a passing
startup check) remains sound and anchored; only the citation was wrong. The
original row text is kept as written per the corrections-are-appended rule.
G3 resolution recorded in the trial record: the first trial compares complete
SOUL configurations, not a controlled style-only experiment, and no failure
cause may be inferred from it. Trial evidence:
`.pi/evidence/act-1/2026-09-07T1625Z-c02-c08-r1/`.

Some files were not shown because too many files have changed in this diff Show More