Project memory switch UI and task record
Epic: Centralized project memory for runners, behind a per-task switch. This child is C2. It is the operator-facing switch: task/epic API fields, the Advanced-section select in the task and epic editors, the resolved default, and the per-task memory record on the task view.
Design sources: the BA PRD (Task 5403, `prd`) and the settled architecture (Task 5404, `architecture`), via `task_read_artifact`. It depends on C1, which adds the columns `tasks.project_memory bool NULL`, `tasks.memory_arm`, `tasks.memory_arm_source`, the knob `project_memory_default` (tenant_visible, default True), and `project_memory.events.memory_counts(session, task_ids)`.
## Epic requirement (verbatim)
> Runners re-derive what other runners already learned: … Build a centralized project-memory layer per the attached architecture ("Jaah Project Memory") … The attachments are research input: the BA and Architect own the design and may depart from them with reasons.
> 5. The switch: a "Project memory" setting in the task/epic Advanced section, inherited by an epic's children; its default is an operator System Settings entry. OFF means dispatch, hooks, tools and settle behave exactly as today, byte-for-byte on the prompt and with no new hook output.
> Done means: The operator default lives in System Settings and a task/epic Advanced setting overrides it.
## Settled requirements (verbatim)
- R-SW-1 A setting named "Project memory" in the Advanced section of the task editor and the epic editor, tri-state: *Default* (follow System Settings; shows the current default value, e.g. "Default (On)"), *On*, *Off*. Stored on the task row; absent = Default.
- R-SW-2 The default is an operator System Settings entry "Project memory" (On/Off) … editable on the existing Settings page with the existing `system:*` permissions.
- R-AB-1 (per-task leg) Per task (every dispatched task, ON or OFF): memory on/off …, units injected, units read, units cited.
## Approach (architecture, binding)
- **API.** The task create/update schemas accept `project_memory: bool | null` for tasks and epics. `TaskOut` gains read-only fields:
- `project_memory`, `memory_arm`, `memory_arm_source`;
- `memory_counts {injected, read, cited, followed}`, from `memory_counts()` and batched for lists (no N+1).
- **Editor** (`apps/web/src/components/tasks/TaskEditForm.tsx`, Advanced box around :2184-2403):
- A native select `id="task-project-memory"` labelled "Project memory", in both the task and epic forms.
- Options: `Default (On)`/`Default (Off)` (from the resolved `project_memory_default`), `On`, `Off`. They map to `project_memory: null|true|false` in `formFields()`.
- Expose `project_memory_default` wherever the form already reads its `task_default_*` values (`taskDefaults.ts`).
- It is NOT a create-form prefill (D-1): Default stays `null` on the row and is resolved at dispatch.
- **Task view** line: "Project memory: On (default) · 5 injected · 2 read · 1 cited". It is shown only when `memory_arm` is non-null, so pre-epic tasks show nothing (D-9).
- **System Settings:** confirm the `project_memory_default` knob renders as "Project memory" On/Off on the Settings page. Add a label or description only if the catalog does not render it legibly.
## Acceptance criteria (BA, verbatim — this child's legs)
- AC-4 UI reachable (R29). Settings shows "Project memory" with its value; the task editor and epic editor Advanced sections show "Project memory" Default/On/Off with the resolved default named ("Default (On)"); changing it persists after reload; an epic set Off yields children (incl. coder) dispatched OFF. Labels readable, no truncation, fits the Advanced box at 360px and desktop widths. *Verify:* Playwright e2e + manual screenshot. (This child: component tests plus a Playwright e2e against the local `./dc` stack. The dev-CP run is C9.)
- AC-22 Per-task record. Every dispatched task shows its memory arm and its injected/read/cited counts. *Verify:* API test; console task view.
- Quality bar: the select's label and options are fully readable (no clipping) at 360px and desktop. "Default (On)" names the real current default, not a hard-coded string.
Out of scope: Epic (verbatim): "moving OMC state to shared storage (ADR-0099); changing canon docs or the router's existing deterministic routes; org-wide (cross-project) memory beyond a scope field reserved for it; adopting an external memory SaaS." BA fences:
- N-5: OFF-task prompts and hooks are unchanged.
- N-6: the `task_settle` contract is unchanged.
- No migration here (C1 owns the columns).
- Do not touch dispatch, compose or arm stamping (C3), or the comparison and Project › Memory console (C8).
- Do not alter `autonomous` inheritance.
- Do not turn the existing Tasks › Defaults prefills into server-side defaults.
Done means:
- An API test shows `project_memory` round-trips null/true/false on a task and an epic, and `TaskOut` returns `memory_arm`, `memory_arm_source` and `memory_counts` (seeded events → correct counts).
- A web component test covers the select's three options, the resolved-default label for both default values, and the mapping into `formFields()`.
- A Playwright e2e on the local stack: set epic Off, reload, and the value persists; screenshots at 360px and desktop show the full label with no truncation.
- The task view line renders only for dispatched-with-arm tasks.
- `./dc check full` green.
Required checks: gate
Outcome
Project memory switch: API field + TaskOut arm/counts, "Project memory" Default(On|Off)/On/Off select in task & epic Advanced, task-view record line. PR #4731 merged, CI gate green, Jaah Deploy (dev) succeeded. Local full gate: only load flakes, all pass on re-run.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Awaiting response
- Awaiting response
- Runner resumed
- Runner ended (done)