Skip to content

feat(ensemble): UI workflow + OpenRouter backend + explicit chapter selection#113

Open
kostadis wants to merge 3 commits into
mainfrom
feat/ensemble-workflow-ui
Open

feat(ensemble): UI workflow + OpenRouter backend + explicit chapter selection#113
kostadis wants to merge 3 commits into
mainfrom
feat/ensemble-workflow-ui

Conversation

@kostadis

Copy link
Copy Markdown
Owner

Summary

Turns the ensemble grounding-doc CLI workflow (docs/cli/ensemble_workflow.md) into a stepped UI page (Setup → Extract → Bundle → Synthesize), adds OpenRouter as a per-stage LLM backend through the single campaignlib seam, and makes chapter selection explicit. The existing Anthropic /grounding path is untouched.

Built via the Spec Kit flow — see specs/001-ensemble-workflow-ui/ (spec, plan, research, tasks, contracts, quickstart).

What's in it

Backend / seam (Constitution V — One Seam per Boundary)

  • campaignlib/api: _OpenRouterClient branch in make_client(backend="openrouter"); reasoning-off mapping; _require_nonempty guards stream_api/call_api against empty model output. OpenRouter is constructed only inside campaignlib/api.
  • Uniform add_backend_args / client_from_args; --backend/--endpoint added to the four synthesis scripts. Default backend=anthropic is byte-identical to before.

UI (Constitution VI/IX — CLI is Engine, UI Mechanizes)

  • New /ensemble route + nav entry; EnsembleWorkflow shell with disk-derived status; Setup, Extract, Bundle (scope + alias gates), Synthesize (diff-before-promote).
  • server/routers/ensemble.py shells out to the CLI and exposes disk-derived status; it issues no retrieval/render calls.

Chapter picker + new Constitution Principle X (operator-elevated)

  • X. Selection is Explicit; There is No Silent "All" added to the constitution (v1.1.0 → 1.2.0, MINOR).
  • New ChapterPicker.vue: resolve glob, Select all / none / only, natural sort, per-chapter extracted/pending badges.
  • ensemble_batch.py --chapters is now nargs="+" (unions globs/paths) — the engine gains the capability, the UI mechanizes it.
  • chapters_selected stores the literal chosen set; an empty selection is refused (no glob fallback) by GET /run/extract and the disabled Run button.

Testing

  • New suites: OpenRouter seam, ensemble status/gates/chapters, batch nargs.
  • Full suite 940 passed; retrieve/render isolation guard (Principle III) green; frontend builds clean.
  • Pre-existing unrelated failures remain (3 fail + 5 errors) where openai/dgxlib aren't installed in this env.

Notes / follow-ups

  • T027 (provenance stamping of backend+model into output artifacts) and T042 (full live-env quickstart run) are deferred — tracked in tasks.md.

🤖 Generated with Claude Code

kostadis and others added 3 commits June 27, 2026 15:20
Sister doctrine to the mneme constitution, governing CG's LLM rendering
pipeline. Nine principles, each naming the anti-pattern it kills:

I.   Disk is Truth, the Model is a Draft   (Optimistic Lies)
II.  The Human Checkpoint is Non-Negotiable (Error Compounding)
III. Retrieval and Render are Separated      (Renderer Scope Decisions)
IV.  Verbatim is Sacred                       (Hallucinated Dialogue)
V.   One Seam per Boundary                    (Fragmented Integration)
VI.  CLI is the Engine, UI is a Face          (Split-Brain)
VII. Extract Once, Synthesize Deliberately    (Depth Regression)
VIII. State is Discoverable                    (Opacity / Tribal State)
IX.  The UI Mechanizes; Claude Converses      (The Walled Garden)

Plus Architecture is Destiny (token/precision economics), Authority &
the Human Checkpoint (Spec Kit plans are drafts), and Governance
(I & II outrank all; semver amendments).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…election

Turn the ensemble grounding-doc CLI workflow (docs/cli/ensemble_workflow.md)
into a stepped UI page (Setup → Extract → Bundle → Synthesize), add OpenRouter
as a per-stage LLM backend through the single campaignlib seam, and make chapter
selection explicit. The existing Anthropic /grounding path is untouched.

Built via the Spec Kit flow (specs/001-ensemble-workflow-ui: spec/plan/research/
tasks/contracts/quickstart).

Backend / seam (Constitution V):
- campaignlib/api: _OpenRouterClient branch in make_client(backend="openrouter");
  reasoning-off mapping; _require_nonempty guards stream_api/call_api against
  empty model output. OpenRouter is constructed only inside campaignlib/api.
- Uniform add_backend_args / client_from_args; --backend/--endpoint added to the
  four synthesis scripts. Default backend=anthropic is byte-identical to before.

UI (Constitution VI/IX):
- New /ensemble route + nav entry; EnsembleWorkflow shell with disk-derived
  status; Setup, Extract, Bundle (scope + alias gates), Synthesize (diff-before-
  promote). server/routers/ensemble.py shells out to the CLI and exposes
  disk-derived status; it issues no retrieval/render calls.

Chapter picker + Constitution Principle X (operator-elevated):
- "Selection is Explicit; There is No Silent 'All'" added to the constitution
  (v1.1.0 -> 1.2.0, MINOR). New ChapterPicker.vue (Resolve glob, Select all /
  none / only, natural sort, extracted/pending badges).
- ensemble_batch.py --chapters is now nargs="+" (unions globs/paths); the engine
  gains the capability, the UI mechanizes it.
- chapters_selected stores the literal chosen set; an empty selection is refused
  (no glob fallback) by GET /run/extract and the disabled Run button.

Tests: +new suites (openrouter seam, ensemble status/gates/chapters, batch nargs).
Full suite 940 passed; isolation guard green; frontend builds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ort + durable record

Implements spec 002-ensemble-run-observability (T001–T032; T033 = manual QA pending):

Engine / shared seam
- atomic_write_text / atomic_write_json in campaignlib.util (FR-014): temp-then-rename
  so a SIGKILL never leaves a truncated merged.json or dossier at a resume-trusted path
- subprocess_runner: classify_result(), extended _save_run_log with result field (T003/T004)
- subprocess_runner: start_new_session=True + SIGTERM→wait(4 s)→SIGKILL process-group
  teardown on every exit path — normal, explicit abort, and disconnect (T020/T021)
- subprocess_runner: emits `event: command` as first SSE event carrying the secret-free
  invocation string; `done` payload includes aborted flag on signal exit (T006/T022)
- ensemble_merge + facts_to_state: atomic cache writes (T018/T019)

Frontend
- sse.ts: onCommand callback; onerror while running closes EventSource (no auto-restart)
  and transitions to aborted — a network drop is an implicit abort (T007/T025, I1)
- useEnsembleRun: command state, aborted status, abort() method (T008/T024/T025)
- RunCommandBar.vue: monospace copyable command box (T009)
- EnsembleExtract/Bundle/Synthesize: RunCommandBar wired; Abort button while running;
  aborted/connection-lost labels; success vs failure color distinction (T010/T014/T015/T026)

Tests (tests/test_subprocess_abort.py)
- secret-safety + explicit-selection-faithfulness (T011/T012)
- process-group kill on explicit abort and disconnect, child + grandchild (T027)
- grace→force timing; aborted record written (T028)
- atomic-write integrity under SIGKILL; lock released after abort (T029)
- non-ensemble SSE route regression — group-killed on disconnect, no orphan (T031)
- success and failure run records verified (T017)

Docs: web_ui.md + ensemble_workflow.md updated with abort/reconnect/per-run-log notes (T030)
Spec: specs/002-ensemble-run-observability/ — full artifact set committed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant