Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .claude/skills/acture-ai-assistant/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ No surveyed framework ships a first-class typed channel for exposing an app's *c

### 2. Human-in-the-loop confirmation — at the dispatch boundary

The industry converged on one beat: model proposes → runtime pauses with serializable state → UI renders the proposal → user approves/denies/edits → resume (research-11 §6). For a command-dispatch app the gate belongs at the **dispatch boundary, driven by declarative command metadata** — *the model proposes; the registry disposes*. A `requiresConfirmation`/`sideEffect: 'destructive'` command dispatched from the assistant channel without an approval token does not execute — it returns a **proposal as errors-as-data** (`code: 'confirmation_required'`, `{command, params, preview}`) which the chat UI renders as an approve/deny/edit card; on approve, re-dispatch with a **one-use token bound to the exact `{command, params}` hash**. Reproducible core: `docs/hand-written-assistant-runtime.md` Piece 1. This keeps confirmation caller-independent, declarative, and schema-validated regardless of surface — and `preview` can call a *view* to show *what will change*.
The industry converged on one beat: model proposes → runtime pauses with serializable state → UI renders the proposal → user approves/denies/edits → resume (research-11 §6). For a command-dispatch app the gate belongs at the **dispatch boundary, driven by a declarative risk convention** — *the model proposes; the registry disposes*. A `requiresConfirmation`/`sideEffect: 'destructive'` command dispatched from the assistant channel without a valid token does not execute — it returns a **proposal as errors-as-data** (`code: 'confirmation_required'`, `{command, params, preview}`) which the chat UI renders as an approve/deny/edit card. **The complete, secure pattern is `docs/hand-written-assistant-runtime.md` Piece 1** — build it from there.

Two load-bearing points that pattern gets right and a naive gate gets wrong:

- **The token is minted by the RUNTIME after a human approves, and never returned to the model.** If the gate put a usable token in the `confirmation_required` proposal, the model — which reads that tool result — would lift it and **self-approve**, defeating HITL. The proposal carries only `{command, params, preview}`; the runtime mints the one-use token out-of-band on human approval and re-dispatches.
- **Tokens are one-use and bound to the exact `{command, params}`** (a `createApprovalStore` with `approve`/`consume`), so an approval can't be replayed against different args.

**Design settled: middleware + convention, not `CommandRecord` fields.** `getRisk(id)` is an external map, so the closed record is untouched (the fields alternative opens the guarded surface — needs the named-need test). And like macros, the ~40-line gate ships as a **pattern**, not a package (hard-don't #2; no natural package home). This keeps confirmation caller-independent, declarative, and schema-validated regardless of surface — and `preview` can call a *view* to show *what will change*.

### 3. The dispatch chain is a macro — undo / replay / test fixtures

Expand Down
Binary file modified docs/hand-written-assistant-runtime.md
Binary file not shown.
145 changes: 70 additions & 75 deletions docs/next_session.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,73 @@
# Next Session — pick the next post-v1 increment (user-steered)

**Your role this session:** *one* of two things.

1. **If the user says "ship X":** read the X-specific brief below and run a single-increment session (Step 1 design decision → build → test → changeset → roadmap update → reflection → commit → push → merge Version PR → verify publish → pull bump). Mirror the v1.12 + v1.13 workflow exactly.
2. **If the user hasn't picked yet:** surface the three remaining post-v1 options below via `AskUserQuestion`. **Do not pick autonomously.** v1.12 + v1.13 were a pre-committed chain; everything past v1.13 is a fresh decision the user owns.

The autonomous-chain mode from the previous handoff is **off** by default. If the user says "do v1.14 + v1.15 autonomously" or similar, re-enter that mode with the same "truly stuck" criteria the v1.12 handoff defined.

## Outcomes of the previous chain

- **v1.12 — `acture-test-property@1.1.0`** — fast-check arbitraries over the command registry; random `CommandSequence`s replayed via `acture-e2e-playwright`'s `replaySequence`; invariants asserted end-of-sequence. Counter-examples carry the shrunk sequence on `PropertyTestFailure.sequence` (replayable verbatim). +29 tests. In-package Zod→arbitrary mapper covering the JSON-Schema-representable subset (the spec-listed `@fast-check/zod` package does not exist on npm). Consumer skill: `acture-test-property`. Reference: `docs/hand-written-test-property.md`. Full write-up: `docs/v1_12-reflection.md`.
- **v1.13 — `acture` on PyPI** — Python companion graduated from name-reservation placeholder to a real, thin MCP-client facade. `ActureClient(Mapping[str, Command])`, `Command`, `ActureError`, `stdio_transport` / `http_transport`. ~300 LoC. One dependency (`mcp >= 1.10`). +23 Python tests via the SDK's in-memory transport. Cross-language semver = lockstep (existing `sync-python-version.mjs`), deliberately not decoupled in this increment. Consumer skill: `acture-python`. Reference: `docs/hand-written-python-client.md`. Full write-up: `docs/v1_13-reflection.md`.
- **Status now:** 19 npm packages + 1 PyPI package; **489 npm package tests + 41 example tests + 23 Python tests** green. 26 skills, 7 reproducibility / recipe docs. Pending changesets: none — chain end is fully published.

## The three remaining post-v1 candidates

Pick one (or none). Each has honest trade-offs — they are NOT equally cheap, and none of them is the obvious next move.

### Option A — `acture-state-jotai` (additional `StateAdapter<S>` reference)

Adds a third reference state adapter. Jotai is **atom-tree**, not flat-state — every piece of state is its own primitive atom. The `StateAdapter<S>` interface assumes flat state (`getState(): S`, `setState((s) => …)`).

- **Effort:** the atom-tree-to-flat bridge has to either (a) flatten the atom tree into a single derived atom (loses Jotai's atomic-update benefit), or (b) introduce an "atom selector function" the adapter user supplies. Research-3 §3 flagged this; the implementation friction is real.
- **`PatchCapableAdapter`:** **may not implement cleanly.** Jotai mutations don't naturally produce Immer-style patches; the adapter would need to wrap every `set` and synthesize a patch from the before/after diff — expensive at large state.
- **Trade-off vs. alternatives:** Jotai is genuinely popular; users who already chose Jotai will want it. But the adapter would either compromise Jotai's strengths (option a) or push complexity onto the user (option b).
- **Recommended user-side decision:** "do we have a real Jotai consumer asking for this?" If no, defer.

### Option B — `acture-state-valtio` (additional `StateAdapter<S>` reference)

Same shape as A, different substrate. Valtio is **proxy-based** — mutations look like direct property assignment, and the proxy synthesizes update notifications.

- **Effort:** proxy-to-patch translation is non-trivial. Valtio's `subscribe` fires after the mutation has already happened; to emit Immer-style patches the adapter would have to do a structural diff against a captured pre-state snapshot, like the Jotai option (b) above.
- **`PatchCapableAdapter`:** same constraint as A — synthesizable but expensive at large state.
- **Trade-off vs. alternatives:** Valtio is less popular than Jotai but has a dedicated user base. The proxy approach is incompatible with the JSON-serializable state constraint (`acture-greenfield-state-model`'s four hard constraints) only at the structural-clone boundary; not a blocker, but a thing to document.
- **Recommended user-side decision:** "do we have a real Valtio consumer?" If no, defer.

### Option C — `acture-sandbox` ✅ shipped (the extension-system increment)

**Done.** The gating design is filed as research-9 (`docs/research/acture_research_9 -- Extensions and Plugin Systems.md`, the §7 brief); the maintainer overrode its honest no-named-user NO-GO to build the **isolation-only seam**. Shipped: the `acture-extensions` skill, `docs/hand-written-sandbox.md` (the ~15-line core-only host/loader pattern), and the `acture-sandbox` package — which ships *only* the `ExtensionRunner` port + an in-process transport.

- **What's settled:** the trust-tier model (research-9 Axis A), effects-as-data, the host/loader as a pattern (the `CommandRecord` IS the manifest), and isolation as the one rung that earns a package.
- **What stays deferred** (the genuine untrusted-author work): the real isolating transports (Worker / iframe / QuickJS-WASM / `isolated-vm`), CPU/memory quotas, and the capability/manifest/entitlement/marketplace layers — gated on a real untrusted-author user (research-9 §0).

### A fourth option — none of the above

Either the user has a different priority (a backlog item, a bug, a docs gap, a research question), or the suite genuinely is at a stable point and the next increment is "wait for real user feedback." Stable-and-waiting is a valid outcome of finishing v1; don't pull-forward speculative work just to keep shipping.

## How to surface the choice

When the user opens a session with "what's next" / "pick the next increment" / similar, ask via `AskUserQuestion`:

- **Header:** `Next increment`
- **Question:** "Two post-v1 state-adapter options remain. Which (if any) should we schedule next?"
- **Options:**
1. **`acture-state-jotai`** — Jotai adapter. Real implementation friction per research-3; needs a concrete consumer before pulling forward.
2. **`acture-state-valtio`** — Valtio adapter. Same friction class as Jotai; same gate.
3. (Other — let the user type a backlog item or a fresh ask.)

(`acture-sandbox` is no longer an option — its isolation-only seam shipped in the extension-system increment. The next sandbox work is gated on a real untrusted-author user.)

If the user picks 1 or 2, run a normal increment (Step 1 = `AskUserQuestion` on adapter-specific design choices — the atom-tree-vs-flat-state choice for jotai, the diff-vs-proxy-subscribe choice for valtio). If the user picks 3, **do not write package code** — propose a research-7 prompt first.

## Standing constraints (unchanged from v1.12)

- **`docs/positioning.md` is canonical.** Section 1 now names three delivery surfaces (skills/patterns, npm packages, PyPI). Each new package documents its hand-written equivalent in `docs/hand-written-*.md`.
- **`docs/redesign_takeaways.md` §6.** The rule of three is for acture *users*. For maintainer decisions, the principles are YAGNI / wait for a concrete named need, hard-don't #2 (no god-package), and the dev-tool-first principle. No callers-counting gate.
- **Hard-don'ts bind** (`acture-hard-donts` skill). For an adapter increment, #1 (no inner-platform DSL), #2 (no god-package), #3 (translate, don't decide) are the load-bearing ones.

## Publishing state at chain end

19 npm packages live on npm; 1 PyPI package live on PyPI (the real `acture` client, replacing the placeholder). No pending changesets. Release workflow has worked cleanly for v1.7 through v1.13; reuse the pattern. The "Publish Python stub to PyPI" job is now mis-named — it publishes the real client. Renaming is a cosmetic future PR.
# Next Session — user-steered

**Your role:** the v1.14–v1.18 consumer-gap chain is complete. From here the user
steers. Do **not** pick new package work autonomously — surface options with honest
trade-offs. Mirror the v1.15–v1.17 single-increment workflow (Step 1 design decision
→ build → test → changeset → roadmap → reflection → commit → PR → merge Version PR →
verify publish) when the user picks a package increment.

## What just shipped (v1.14 → v1.18)

The three consumer surfaces the user was about to implement — **command palette**,
**keyboard-shortcut customization**, and an **app-operating AI assistant** — were
gap-filled end to end. Command palette was already covered; the other two are now
complete.

- **v1.14 — research + skills + reference docs** (PR #41): `research_10` (end-user
keybinding customization) + `research_11` (AI assistant operating an app,
Vancouver-cited); new `acture-ai-assistant` skill; extended `acture-hotkeys`
skill; 3 `hand-written-*` refs (`keymap-override`, `view-registry`,
`assistant-runtime`). Adversarially reviewed; 8 correctness fixes applied.
- **peer-range republish** (PR #42): `acture-palette-react` / `acture-hotkeys` /
`acture-state-zustand` **1.0.1** — fixed the ERESOLVE exact-pin on `acture`.
- **v1.15 — MCP resources** (PR #44): `acture-mcp-server@1.2.0` — the AI read side
(`createMcpServer({ views })` → `resources/list`/`read`/`subscribe`).
- **v1.16 — getState tool** (PR #46): `acture-mcp-server@1.3.0` — the portable
read-side hedge for tools-only hosts (serves reelee-web's direct-Anthropic flow).
- **v1.17 — keymap customization** (PR #48): `acture-hotkeys@1.1.0` — end-user
remapping (`bindHotkeys({ keymap })` + `resolveKeys`/`detectConflicts`/capture &
display primitives).
- **v1.18 — confirmation gate** (PR #50): HITL for destructive AI dispatch, shipped
as a **pattern** (middleware+convention, not `CommandRecord` fields; hard-don't
#2). Complete secure impl in `docs/hand-written-assistant-runtime.md` Piece 1.

**The "operate my app" story is complete:** write side (tools/MCP) + read side
(resources + getState) + HITL confirmation + macro capture + runtime bridge — as
packages where a package earned it, as patterns/skills elsewhere.

**Named consumer:** `reelee-web` (its `src/ai/acture-tools.ts` bridges the registry
to Anthropic tools and classifies destructive commands) grounds all of the above.

## Candidates next (user's call)

1. **Wire the shipped pieces into `reelee-web`** — palette, hotkeys + keymap
customization, MCP resources + getState, and the confirmation-gate pattern. This
is the natural "use what we built" move; the named consumer is ready.
2. **`acture-state-jotai` / `acture-state-valtio`** — the remaining post-v1 state
adapters. Still gated on a real Jotai/Valtio consumer (research-3 friction; may
not implement `PatchCapableAdapter` cleanly). Defer unless a consumer surfaces.
3. **Stable-and-waiting** — a valid outcome. Don't pull-forward speculative work.

If the confirmation gate ever needs to be an *importable* helper (not a copied
pattern) because a second project reuses it, revisit the pattern-vs-package call —
but honor hard-don't #2 (no god-package-of-one; find an existing home first).

## Standing constraints (unchanged)

- **`docs/positioning.md` is canonical** — dev-tool-first; each package documents its
hand-written equivalent in `docs/hand-written-*.md`.
- **`docs/redesign_takeaways.md` §6** — rule of three is for acture *users*; for
maintainer decisions: YAGNI / named need, hard-don't #2 (no god-package),
dev-tool-first.
- **Hard-don'ts bind** (`acture-hard-donts`). The closed `CommandRecord` stays
closed unless a change passes the named-need test (the confirmation gate
deliberately used convention over fields to honor this).
- Release workflow (changesets → Version PR → publish) has run cleanly for
#42/#44/#46/#48; reuse the pattern.

## When unsure

Re-read `docs/positioning.md`, `docs/redesign_takeaways.md` §6, `docs/roadmap.md`, and the v1.11 / v1.12 / v1.13 reflections. If a change is irreversible, append to `docs/escalations.md` and ask the user.

**The autonomous chain is over.** From here, the user steers.
Re-read `docs/positioning.md`, `docs/redesign_takeaways.md` §6, `docs/roadmap.md`
(the v1.14–v1.18 entries), and the `acture-ai-assistant` / `acture-hotkeys` /
`acture-mcp` skills. If a change is irreversible, append to `docs/escalations.md`
and ask the user.
15 changes: 14 additions & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The live forward-planning surface. `docs/v1_plan.md` and `docs/implementation_pl

**How work proceeds:** phases are over. Work is small, tracked increments. Each picks one or two items from "Next" or "Deferred", ships them, updates this file, and replaces `docs/next_session.md` with the following handoff.

Last updated: **2026-07-06** (v1.17acture-hotkeys end-user keymap customization, the second v1.14-deferred accelerator).
Last updated: **2026-07-06** (v1.18confirmation gate (HITL) shipped as a pattern; the "operate my app" story is now complete).

---

Expand Down Expand Up @@ -160,6 +160,19 @@ The second v1.14-deferred accelerator. Lets a **user** (not just the developer)

---

### v1.18 — confirmation gate (HITL for destructive AI dispatch) — complete (this increment)

The last "operate my app" piece: a human-in-the-loop gate for destructive/irreversible AI dispatch (research-11 §6). **Design settled with the user: middleware + convention, NOT `CommandRecord` fields** — `getRisk(id)` is an external map, so the closed record stays closed (the fields alternative was declined to avoid opening the guarded surface). And like macros / `recordSequence`, the ~40-line gate ships as a **pattern**, not a package (hard-don't #2 — no god-package-of-one, no natural existing package home); the delivery surface is the reference doc + skill.

- **`docs/hand-written-assistant-runtime.md` Piece 1** rewritten from a sketch to a complete, **secure** implementation: `createApprovalStore` (one-use tokens bound to the exact `{command, params}`), `confirmationGate` (a dispatch wrapper that returns a `confirmation_required` errors-as-data proposal for risky assistant calls), and the runtime re-dispatch flow.
- **The security fix that motivated hardening it:** the token is minted by the **runtime after a human approves, and never returned to the model** — the prior sketch's `!ctx?.approvedToken` truthiness check (any token passes) plus a hinted token-in-proposal would let the model **self-approve** by lifting the token from the tool result. The proposal now carries only `{command, params, preview}`; tokens are one-use and call-bound.
- **`acture-ai-assistant` skill §2** updated: the trust-boundary rule (token minted post-human-approval, never to the model), the one-use call-bound tokens, and the settled convention-not-fields / pattern-not-package decision.
- Docs/skill only — no package, no changeset, no version bump (pattern delivery).

**With this, the "operate my app" story is complete:** write side (tools/MCP) + read side (resources + getState) + HITL confirmation + macro capture + the runtime bridge — all shipped, as packages where a package earned it and as patterns/skills elsewhere.

---

## Next

**The autonomous v1.12 + v1.13 chain is complete.** The remaining post-v1 items need user direction; the rewritten `docs/next_session.md` surfaces them. The candidates are: **`acture-state-jotai`** (atom-tree ↔ flat-state bridge is non-trivial per research-3; the adapter may not implement `PatchCapableAdapter` cleanly), **`acture-state-valtio`** (proxy-to-patch translation is non-trivial). (**`acture-sandbox`** is no longer a candidate — its isolation-only seam shipped in the extension-system increment; see below.) Pull-forward decisions are the user's; surface options with honest trade-offs when scheduled.
Expand Down
Loading