Skip to content

Interactive UI + adaptive learning + best-iter composite scoring#1

Merged
ginaecho merged 21 commits into
mainfrom
claude/interactive-cluster-interface-Pu2Ci
May 21, 2026
Merged

Interactive UI + adaptive learning + best-iter composite scoring#1
ginaecho merged 21 commits into
mainfrom
claude/interactive-cluster-interface-Pu2Ci

Conversation

@ginaecho

Copy link
Copy Markdown
Owner

Summary

Ships the live web UI, the per-cluster feedback loop, and a tighter pipeline runtime that scores every iteration on a composite of F1, silhouette, and VIF — then auto-picks the best one across all 10 attempts.

What's in this PR

Pipeline runtime (agents/, run_pipeline.py)

  • Composite best-iteration scoringstate.update_best() now ranks by F1×100 + Silhouette×30 − log10(VIF)×5, runs on every iteration (not only Clarity-Gate passers), and state.current_max_vif() reads the latest fs_history entry for the VIF term.
  • Classifier runs every iteration — even on silhouette-target miss or Clarity-Gate fail. PersonaNamer is invoked with force_proceed=True when silhouette is below target so personas always exist for the classifier to score. Escalation rules apply after scoring, so a "bad" iteration still contributes to the best-iter tracker.
  • Approve path saves state.best_* — even if the human checkpoint approves mid-loop, the all-time best iteration is what hits outputs/personas.json (and the Named Clusters tab). Console logs note when an earlier iteration was kept over the current one.
  • Auto-approve waits for 3 passing itersrun_pipeline.py returns recluster until at least three iterations have produced a passing classifier, so the composite-best comparison has real data to work with. Recluster path now calls _ask_parameter_tuning for algorithm/k diversity.
  • Silhouette transparencysilhouette_target plumbed from orchestrator into ClusteringAgent.run (was hardcoded 0.25); _relax_silhouette_target emits a high-visibility Orchestrator agent_report so the 0.5 → 0.4 → 0.3 degrade lands as a warning chip.

Interactive UI (ui/, ui/static/, ui/templates/)

  • New Flask app fused with the pipeline through OrchestratorBus: every agent step, LLM call, gate decision, and escalation streams over Server-Sent Events.
  • Live pipeline view — architecture graph (7 agents light up in real time), agent ↔ Decision Maker chat bubbles, per-agent outputs panel, 3 cost ledgers (pipeline / evidence / naming).
  • Data & Evidence tab — dataset profile, per-column skewness, per-iteration PCA scatter, classifier per-class F1, lineage tree, final summary table with the winning iteration highlighted.
  • Named Clusters tab — every cluster as an editable card. Edit, regenerate-with-hint, merge, or chat multi-turn with the agent → Conclude → propose action (rename / merge / keep / save guidance).
  • Adaptive learning loop — feedback persists to outputs/user_feedback_log.jsonl, where the next run's Decision Maker prompts read it. Renames, merges, hints, and chat conclusions all influence subsequent iterations.
  • Bypass vs Interactive mode topbar toggle; Memory drawer for managing global rules.
  • Save Edit bug fixapplyChatConclusion('rename') now refreshes state.draft after the server update, so a subsequent Save Edits no longer overwrites the renamed title.
  • /api/explain dedup cache — 120 s in-memory cache so the multi-window recorder doesn't fire identical EvidenceExplainer LLM calls 3× per warning.
  • Refresh resilience — 20-second global watchdog re-renders graph, cost panel, Evidence tab, and Named Clusters grid even when SSE goes silent.

Recording (record_demo.py)

  • 8 region-isolated views (intent, graph, log, convos, outputs, evidence, tokens, named) plus a bare-UI full pseudo-region at 1280×800.
  • --port, --base-url, --skip-pipeline-check, --stop-on-key flags.

Docs (README.md, docs/screenshots/)

  • Slim README (277 → 76 lines): TL;DR · Architecture · Quick Start · Interactive UI · Configuration · Outputs.
  • 4 hero figures committed to docs/screenshots/:
    • 00_architecture.png — all gates surfaced (silhouette target, 40% size guard, Clarity Gate, F1 ≥ 0.70, VIF, composite best-iter)
    • 01_per_cluster_chat_and_save.png — Named Clusters tab with chat panel
    • 02_pca_with_adaptive_escalation.png — Data & Evidence with silhouette-miss warning
    • 03_adaptive_memory_drawer.png — Adaptive Memory drawer

Snapshots

  • outputs/ snapshot from the most recent runs (force-added past .gitignore).
  • recordings/data_evidence.mp4 + recordings/named_cluster.mp4 (1-minute embeddable clips).

Test plan

  • python run_pipeline.py --data data/raw/wholesale_customers/wholesale_customers.csv --ui-port 5090 boots the UI on 5090 and runs end-to-end.
  • At least 3 iterations run before auto-approve fires.
  • outputs/personas.json reflects the iteration with the highest composite (F1 ↑ + Silhouette ↑ − VIF penalty), not necessarily the last iteration.
  • Cluster card → chat → Conclude → Apply rename: persists the new name; subsequent Save Edits does NOT overwrite it; learning lands in user_feedback_log.jsonl.
  • Silhouette miss → orange Orchestrator warning chip with the 0.5 → 0.4 degrade.
  • Evidence tab keeps re-rendering during bursty LLM activity (no F5 needed).
  • python record_demo.py --port 5090 --regions full graph tokens produces the 3 webms.

🤖 Generated with Claude Code

claude and others added 17 commits May 16, 2026 11:04
…earning

Adds an interactive web UI (Flask + vanilla JS/CSS) launched after the
multi-agent pipeline completes. The UI turns the final named clusters
into a feedback loop with persistent memory:

- Direct edits to name/tagline/description/traits/confidence persist
  back to outputs/personas.json
- "Regenerate with hint" calls the Decision Maker (PersonaNamingAgent)
  live for a single cluster, honouring a freeform user suggestion
- Merge 2+ clusters: stats are recomputed by weighted aggregation,
  the merged cluster gets a fresh LLM-generated name
- Global rules ("never use the word 'shopper'") apply to every future run

Every UI action is appended to outputs/user_feedback_log.jsonl with
priority/date/type metadata. PersonaNamingAgent now prepends a
"USER PREFERENCES" block built from that log to its prompt on every
future pipeline run, so the agent system genuinely learns from prior
user interactions.

Launch with:  python -m ui.launch
Pipeline ↔ UI integration upgrades
- Live architecture graph + per-iteration history pills (FeatureEngineer,
  FeatureSelector, Clusterer + PCA scatter snapshots per Clusterer iter)
- 3-ledger Tokens & cost panel: Pipeline / Evidence / Naming discussions
- Bypass-mode auto-decision: when a warning fires, LLM explains the
  pipeline's chosen action in active voice (Evidence ledger, separate spend)
- Interactive-mode pause-on-warning + decision modal that feeds the user's
  guidance back as a high-priority memory rule
- Per-cluster multi-turn chat with the LLM (POST /api/cluster-chat) →
  Conclude → propose rename/merge/keep/recluster actions
- Final pipeline summary card (top of Evidence tab on completion): per-iter
  silhouette / F1 / tokens / cost / time, winning iter highlighted
- Mode toggle (Bypass / Interactive), demo focus URLs ?demo=<area>
- 4 tabs: Live pipeline | Data & evidence | Named clusters; tokens panel
  pinned directly below the architecture graph
- Memory drawer redesigned: inline 'Add memory rule' form, filter chips,
  DELETE endpoint, user_change : priority : date catalog format

Pipeline behavior changes
- silhouette_target (default 0.5) — Clusterer outputs below target loop
  back to feature selection
- max_reselect_failures (default 3) — N misses → re-engineer features from
  raw data + Decision Maker picks fresh algorithm
- max_relax_failures (default 3) — N misses → bypass auto-lowers
  silhouette_target by 0.1, interactive opens relax modal
- All escalations + target changes log as Orchestrator agent outputs
- bus.report() pauses on warnings when mode=interactive; bus.ask() supports
  category='pipeline'|'evidence'|'naming' for per-ledger cost tracking
- OrchestratorBus init truncates events JSONL + deletes stale outputs so
  every restart starts clean

Plus
- 12 new public datasets via UCI + Kaggle (humans / products / signals /
  images-as-tabular) under data/raw/, total ~80 MB
- record_demo.py: Playwright-based 5-window recorder (one .webm per UI
  region: graph / convos / outputs / evidence / tokens)
- File upload + drop-zone + per-column skewness histograms in Evidence tab
- Bus emits llm_call_started / llm_call_finished events with full prompt
  + response for live chat-bubble rendering

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 3 session screenshots were debugging artifacts, not project assets.
Drop them from the repo and prevent future .png files from being tracked.

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

record_demo.py now records intent + log windows alongside the original
five, matching the views available via ?demo=<area>. style.css adds the
matching body.demo-intent and body.demo-log focus modes so each window
captures only its target region. Docs cover the macOS gotcha where
`setsid` is not a binary and the Python `start_new_session=True` recipe
is required to fully reparent the pipeline + recorder to launchd so
closing Cursor cannot SIGHUP them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pipeline behavior
- update_best now scores by F1×100 + avg_confidence so the actual winning
  iteration drives outputs/personas.json (and the Named Clusters tab),
  not whichever iteration happened to be approved first.
- auto-approve waits for 3 passing iterations before approving so the
  orchestrator can compare multiple {naming, F1} candidates.
- approve path saves state.best_* instead of the current iter, with a
  console note when an earlier iteration scored higher.
- human-checkpoint recluster now calls _ask_parameter_tuning so each
  exploration round tries a different algorithm/k for diversity.
- silhouette_target is plumbed from orchestrator into ClusteringAgent.run
  so the clusterer's success/warning chip uses the dynamic target
  (was hardcoded 0.25). max_relax_failures code default aligned to 3.

UI fixes
- applyChatConclusion('rename') now refreshes state.draft after server
  update, so a subsequent Save Edits no longer overwrites the renamed
  title with the stale draft. The chat proposal summary + reason are
  also POSTed to /api/feedback/global as a high-priority memory rule so
  the next pipeline run sees WHY the rename happened.
- Removed the misleading "accuracy 0.964" chip from the Classifier card;
  F1 is now the single headline metric for the gate.
- Per-iteration summary table gets a Why column explaining each row's
  outcome ("silhouette 0.30 < target 0.50 → reselect features",
  "Clarity Gate failed", "F1 low"), plus algorithm-column hover that
  surfaces the auto-select reasoning and silhouette / target side-by-side.
- _relax_silhouette_target emits a high-visibility Orchestrator
  agent_report so 0.5 → 0.4 → 0.3 degrades land as a warning chip in
  the outputs panel, not a buried event-log line.

Recording
- record_demo.py: --skip-pipeline-check (record against previously-saved
  personas), --stop-on-key (manual interactions), and a 'full' pseudo-
  region that loads the bare UI at 1280×800 for free-form walkthroughs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Playwright captures only the viewport and there's no real user to scroll,
so long-running pipeline demos (outputs panel, evidence cards, named
clusters grid) appeared frozen as new content appended below the fold.

- New autoScrollForDemo() helper; no-op outside body.demo-mode so live
  sessions keep their natural scroll position.
- Hooked into renderOutputsPanel, renderEvidence, renderGrid.
- Scrolls both the relevant inner container and the window, so every
  demo mode is covered regardless of which CSS pattern it uses.

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

Pipeline
- agents/state.py: new composite_score() = F1×100 + Silhouette×30 − log10(VIF)×5.
  update_best() now uses it, runs on EVERY iteration (not just Clarity-Gate
  passers), so the best-iteration decision genuinely reflects all three
  user-named metrics (F1 ↑, Silhouette ↑, VIF ↓). Adds current_max_vif()
  helper that reads the latest fs_history entry.
- agents/orchestrator.py: refactored the main loop so PersonaNamer + Classifier
  always run when the clusterer produced labels — even on silhouette-target
  miss or Clarity-Gate fail. Naming uses force_proceed=True when silhouette
  is below target so personas exist for Classifier to score. Escalation
  rules (reselect/re-engineer/relax) now apply AFTER scoring, so a "bad"
  iteration's metrics still feed the best-iter tracker.

UI resilience during recordings
- ui/app.py: 120-second in-memory dedup cache on /api/explain so the multi-
  window recorder (3 Chromium contexts open simultaneously) doesn't fire
  3× identical EvidenceExplainer LLM calls for every warning.
- ui/static/app.js: 20-second global refresh watchdog re-renders graph,
  cost panel, Evidence tab, and Named Clusters grid even when SSE goes
  silent (proxy timeouts, backgrounded tabs, bursty event starvation of
  the per-event debounce). Evidence tab's event-driven debounce gets a
  20-s hard deadline so it can't be starved during long bursts.
- record_demo.py: --port / --base-url flags so the recorder can target an
  arbitrary UI port (e.g. --port 5090). 'full' pseudo-region opens the
  bare UI at 1280×800 for manual walkthrough recordings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the current state of the outputs/ directory (force-added past
.gitignore) so the run logs, events stream, persona artifacts, and
upload preview are tracked at this point in time. The in-progress run
(2026-05-17 12:46 → ongoing) is included but will continue to evolve;
re-snapshot after it completes if you want the final state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Describes the new live web UI (architecture graph, agent ↔ Decision Maker
chat bubbles, per-iteration outputs, 3 cost ledgers, Data & Evidence tab,
Named Clusters tab) and the user-feedback system: direct edit, regenerate
with hint, merge clusters, per-cluster multi-turn chat. Documents how
feedback in outputs/user_feedback_log.jsonl flows back into the next
pipeline run's Decision Maker prompts (adaptive learning across runs).

Also adds Bypass vs Interactive mode and the memory drawer. Cross-links
the new section from "How to Run".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two short, embed-friendly clips captured from the live UI on port 5090:
- data_evidence.mp4 (13 MB, 60 s) — Data & Evidence tab walkthrough
- named_cluster.mp4 (18 MB, 59 s) — Named Clusters tab walkthrough

Force-added past the recordings/ gitignore so the slide deck has a stable
public URL for these specific clips. Larger source webms and full-length
recordings are intentionally NOT tracked (would exceed GitHub's 100 MB
per-file limit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Selected from the demo MP4s (recordings/data_evidence.mp4 and
recordings/named_cluster.mp4). Three frames chosen to convey the whole
story in the README's Interactive UI section:

1. Per-cluster multi-turn chat + Conclude→propose-action panel
   (named_cluster.mp4 @ ~36s)
2. Per-iteration 2-D PCA projection with the orchestrator's silhouette-
   miss escalation warning rendered in line (data_evidence.mp4 @ ~27s)
3. Adaptive Memory drawer showing global rules with priority badges
   that the next pipeline run will read (named_cluster.mp4 @ ~56s)

Force-added past the *.png gitignore (one-off allowlist via -f).

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

- Reorders the 3 Interactive UI screenshots so the description text comes
  BEFORE its figure (the previous order made the figure float without
  context until you scrolled past).
- Replaces the broken GitHub user-attachments architecture image (only
  visible to logged-in users) with a self-contained Mermaid flowchart
  that GitHub renders natively. Covers all 7 agents, the OrchestratorBus,
  the Decision Maker, all backward-feedback loops with their thresholds,
  the Human Checkpoint, and the save step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitHub's Mermaid renderer treats '<' as HTML and aborts on "F1 < 0.70".
Rewrote edge labels to use "below" instead of "<", quoted all dotted-edge
labels, dropped the subgraph wrapper, and split the BUS<->agents fan-out
into individual edges (the '&' chained form had inconsistent rendering).
Also kept the new node layout but removed the unused 'gate' classDef.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the lexer-fragile mermaid block with the existing
agentic_labelling.png (157 KB) — 7 STEP boxes in a row with dotted
feedback arrows down to a central Orchestrator box. Renders identically
in every markdown viewer and Google Docs / Notion paste without needing
mermaid support.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the earlier minimal PNG with one that shows EVERY gate inside
its step box (purple chips) plus all the feedback routes labeled:

  - FeatureSelector:  PCA + AE  ·  VIF ≤ threshold  ·  LLM picks 25-55
  - Clusterer:        silhouette ≥ target  ·  size ≤ 40 %  ·  deepening
  - PersonaNamer:     Clarity Gate (conf ≥ 6, unique names)
  - Classifier:       macro-F1 ≥ 0.70  ·  per-class F1

  Escalation arrows down to the orchestrator:
    Clusterer → oversized cluster → reselect features
    Clusterer → silhouette below target → reselect features
    Clusterer → 3 consecutive misses → re-engineer + new algorithm
    PersonaNamer → Clarity Gate fails → re-cluster
    Classifier → F1 < 0.70 → reselect features
    Classifier → F1 < 0.70 → re-cluster

  Orchestrator panel now states the actual best-iteration rule
  (composite: F1 ↑, Silhouette ↑, max-VIF ↓) and that user feedback
  feeds back into the next run's prompts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Down from 277 → 76 lines. New structure:
  TL;DR · Architecture · Quick Start · Interactive UI ·
  Configuration · Outputs (with best-effort note)

Dropped: verbose "Problem" intro, per-agent role table (the figure now
carries it), "How each agent calls the Decision Maker" + concrete-prompt
example, separate Demo Dataset section (folded into Quick Start), the
"What you can see in real time" / "Feedback system" / "Adaptive learning"
/ "Bypass vs Interactive" / "Memory drawer" subsections (the 3 captioned
screenshots already convey them), Skills table, Setup duplicate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous version: gap_x=1.0 left only ~0.8 units for the arrow shaft and
the line was 2pt — visually almost absent, contradicting the README's
"solid arrows = forward path" caption.

Now: gap_x=3.4 + linewidth=3 + larger arrowhead. The forward path now
reads unambiguously against the dotted feedback arrows.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02a4f118d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread agents/orchestrator.py
save_outputs(cr, nr, clf, self.bus)
# Make sure the CURRENT iteration competes for best (it just passed
# all gates) so the all-time best comparison is fair.
state.update_best(nr, cr, clf)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass current max VIF when re-scoring approved iteration

The approve path re-scores the current iteration with state.update_best(nr, cr, clf) but omits max_vif; in PipelineState.update_best, a missing max_vif falls back to self.best_max_vif, so the current candidate can be compared using the previous winner’s VIF penalty instead of its own. When the current iteration has worse multicollinearity, this can incorrectly overwrite best_* immediately before save_outputs, violating the intended composite winner logic (F1 + silhouette − current VIF penalty).

Useful? React with 👍 / 👎.

Comment thread agents/user_input.py
Comment on lines +280 to +285
deadline = time.time() + UI_INTENT_TIMEOUT_S
try:
while time.time() < deadline:
if PENDING_INTENT_PATH.exists():
return self._consume_intent_file()
time.sleep(1.0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid blocking 10 minutes before terminal intent prompts

_wait_for_ui_intent() waits up to UI_INTENT_TIMEOUT_S (600s) in a polling loop, and run() calls it unconditionally before any CLI prompt. In headless or --no-ui runs where no browser writes pending_intent.json, users are forced to wait ~10 minutes (or manually Ctrl-C) before terminal input starts, which makes non-UI execution appear hung.

Useful? React with 👍 / 👎.

ginaecho and others added 4 commits May 17, 2026 16:45
…deos

Replaces the two figures (per-cluster chat + memory drawer) with the
existing recordings/named_cluster.mp4 — that single clip covers BOTH
the chat workflow and the memory drawer that captures it, so a combined
caption + one video reads more naturally than two stills.

Also swaps the Data & Evidence figure for recordings/data_evidence.mp4
so all three Interactive UI subsections are now live demos.

GitHub auto-detects bare *.mp4 URLs on their own line and renders them
as inline video players (the documented embedding approach).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bare *.mp4 URLs in README markdown trigger GitHub to serve them as
file downloads, not inline players. Explicit <video src=… controls
muted playsinline> renders the HTML5 player on the README page so
the demo plays in-browser without anyone having to download.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitHub serves both raw-repo mp4s and release-asset mp4s with
Content-Disposition: attachment, so <video> tags rendered as download
links instead of inline players. Replaced with markdown ![]() GIF
references — these auto-play and loop in the GitHub README without
any user action. Sized down to 1.9 MB / 10 s loops / 560 px wide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- run_pipeline.py: reconfigure stdout/stderr to UTF-8 so the Unicode separators
  ('─') in agent output do not crash _Tee on Windows cp1252 consoles.
- requirements.txt: add `requests` (used by download_datasets.py and
  record_demo.py) and `scikit-fuzzy` (used by the fuzzy_cmeans branch of
  agents/clusterer.py). Flask was already pinned but missing from local envs.
- ui/templates/index.html: tab title now matches the in-app brand
  ('⬢ Persona Studio · Live Cluster Discovery').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 18, 2026 09:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fuses the existing multi-agent clustering pipeline with a live Flask + SSE web UI, replaces the "approve on first pass" auto-checkpoint with a composite-score (F1 × 100 + Silhouette × 30 − log10(VIF) × 5) best-of-N selection across iterations, and adds adaptive-learning plumbing where UI feedback is persisted to outputs/user_feedback_log.jsonl and replayed into Decision Maker prompts on subsequent runs. It also slims the README, adds a Playwright-based multi-region demo recorder, and snapshots example outputs/logs.

Changes:

  • Composite best-iteration scoring (agents/state.py) + auto-approve waiting for ≥3 passing iterations (run_pipeline.py) + silhouette_target plumbed dynamically into ClusteringAgent.run.
  • New Flask UI (ui/) + OrchestratorBus event streaming, interactive-mode pause-on-warning, three cost ledgers, adaptive memory store (ui/feedback_store.py) injected into PersonaNamingAgent.
  • Playwright demo recorder, expanded download_datasets.py, slimmed README, new docs, and committed example outputs/ snapshots + per-run terminal logs.

Reviewed changes

Copilot reviewed 53 out of 101 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
run_pipeline.py Auto-approve now needs ≥3 passing iters; embeds UI launcher + --ui-port flag.
agents/state.py Adds composite-score update_best(), VIF tracking, escalation counters.
agents/clusterer.py Honors dynamic silhouette_target for success/warning chip + report.
agents/persona_namer.py Prepends build_preferences_block() from UI feedback store to prompt.
agents/user_input.py UI-first intent collection via outputs/pending_intent.json.
skills/orchestrator_bus.py JSONL event stream, interactive-mode decision wait, stale-file cleanup.
ui/* New Flask app, templates, feedback store, transient LLM bridge for regenerate/merge/chat.
record_demo.py Playwright multi-window recorder.
config.yaml, requirements.txt, download_datasets.py Escalation knobs, Flask + scikit-fuzzy, 8 new datasets.
README.md, docs/* Slim README, session/HOWTO/changelog docs.
.gitignore Adds *.png, data/uploads/, recordings/, *.webm.
outputs/* Force-committed run snapshots, mode file, feedback log, per-run terminal logs; removes some prior snapshot JSONs.
data/raw/*/README.md Per-dataset descriptions for new bundles.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +10
{"type": "global_rule", "rule": "Guidance for DatasetExaminer warning (\"Mean feature skewness=3.6 is high. Log-transform recommended before clustering.\"): yes apply log-transforms", "priority": "high", "id": "fb_6c970266", "date": "2026-05-16T14:47:20+00:00", "active": true}
{"type": "global_rule", "rule": "Guidance for DatasetExaminer warning (\"Mean feature skewness=3.6 is high. Log-transform recommended before clustering.\"): yes apply what you suggested", "priority": "high", "id": "fb_eaf08b0a", "date": "2026-05-16T14:57:03+00:00", "active": true}
{"type": "global_rule", "rule": "Guidance for Clusterer warning (\"Cluster 1 has 92.4% of data (>40% threshold)\"): recluster this too big cluster to a smaller size", "priority": "high", "id": "fb_acfb263c", "date": "2026-05-16T15:12:45+00:00", "active": true}
{"type": "global_rule", "rule": "Guidance for Clusterer warning (\"Cluster 1 has 92.4% of data (>40% threshold)\"): recluster this big cluster to proper size, you can decide after analyse it", "priority": "high", "id": "fb_e7df3d82", "date": "2026-05-16T15:13:58+00:00", "active": true}
{"type": "global_rule", "rule": "Guidance for Clusterer warning (\"Silhouette=0.127 < 0.15 — clusters overlap; interpretability may be low. Consider reviewing feature selection or k.\"): go back to feature selection", "priority": "high", "id": "fb_1c1c8bea", "date": "2026-05-16T15:18:28+00:00", "active": true}
{"type": "global_rule", "rule": "Guidance for PersonaNamer warning (\"Must-have cluster type(s) not found in any persona name/description: ['travellers']\"): try more k", "priority": "high", "id": "fb_8a3630fb", "date": "2026-05-16T15:19:32+00:00", "active": true}
{"type": "global_rule", "rule": "Guidance for Clusterer warning (\"Cluster 4 has 40.9% of data (>40% threshold)\"): recluster that cluster 4, to more clusters", "priority": "high", "id": "fb_540e77d0", "date": "2026-05-16T15:19:55+00:00", "active": true}
{"type": "global_rule", "rule": "Guidance for Clusterer warning (\"Silhouette=0.145 < 0.15 — clusters overlap; interpretability may be low. Consider reviewing feature selection or k.\"): let it pass", "priority": "high", "id": "fb_510836ec", "date": "2026-05-16T15:23:38+00:00", "active": true}
{"type": "manual_override", "target_cluster_id": "7", "target_cluster_name": "Entertainment & Miscellaneous Spenders", "before": {"confidence": 8, "description": "These 24 people spend 3–3.7x the average on miscellaneous purchases (both online and in-store) and 2.65x the average on entertainment. They live in very small towns despite their high spending, and they barely travel at all. Their money goes into local entertainment and diverse, large purchases rather than travel.", "dominant_features": ["sum_misc_pos_6m", "sum_misc_net_6m", "sum_entertainment_6m"], "name": "Entertainment & Miscellaneous Big Spenders", "tagline": "They drop large sums on fun and hard-to-categorise purchases", "traits": ["Miscellaneous spending is over 3x the average", "Entertainment spending is nearly 3x above average", "Very low travel spending — homebodies who spend locally", "Live in tiny towns despite very high expenditure", "Diverse, high-value purchases across categories"]}, "after": {"confidence": 8, "description": "These 24 people spend 3–3.7x the average on miscellaneous purchases (both online and in-store) and 2.65x the average on entertainment. They live in very small towns despite their high spending, and they barely travel at all. Their money goes into local entertainment and diverse, large purchases rather than travel.", "dominant_features": ["sum_misc_pos_6m", "sum_misc_net_6m", "sum_entertainment_6m"], "name": "Entertainment & Miscellaneous Spenders", "tagline": "They drop large sums on fun and hard-to-categorise purchases", "traits": ["Miscellaneous spending is over 3x the average", "Entertainment spending is nearly 3x above average", "Very low travel spending — homebodies who spend locally", "Live in tiny towns despite very high expenditure", "Diverse, high-value purchases across categories"]}, "priority": "high", "id": "fb_c17f009b", "date": "2026-05-16T22:44:19+00:00", "active": true}
{"type": "manual_override", "target_cluster_id": "8", "target_cluster_name": "Grocery-Obsessed Female Bulk Buyers", "before": {"confidence": 9, "description": "All 112 people in this cluster are female (0% male), and they are the single biggest grocery buyers in the entire dataset — spending 2.7–2.8x the average on both online and in-store groceries. Their grocery transaction counts are also 2.5x above average, meaning they shop for food very frequently and in large amounts.", "dominant_features": ["sum_grocery_net_12m", "sum_grocery_pos_all", "count_grocery_net_12m"], "name": "Grocery-Obsessed Bulk Buyers", "tagline": "They buy groceries constantly and in very large quantities", "traits": ["100% female — the only all-female cluster", "Grocery spending is nearly 3x above average", "Shops for groceries very frequently — over 2.5x the average count", "Large in-store and online grocery volumes", "Low gas spending — may rely on delivery or live close to stores"]}, "after": {"confidence": 9, "description": "All 112 people in this cluster are female (0% male), and they are the single biggest grocery buyers in the entire dataset — spending 2.7–2.8x the average on both online and in-store groceries. Their grocery transaction counts are also 2.5x above average, meaning they shop for food very frequently and in large amounts.", "dominant_features": ["sum_grocery_net_12m", "sum_grocery_pos_all", "count_grocery_net_12m"], "name": "Grocery-Obsessed Female Bulk Buyers", "tagline": "They buy groceries constantly and in very large quantities", "traits": ["100% female — the only all-female cluster", "Grocery spending is nearly 3x above average", "Shops for groceries very frequently — over 2.5x the average count", "Large in-store and online grocery volumes", "Low gas spending — may rely on delivery or live close to stores"]}, "priority": "high", "id": "fb_84b6045b", "date": "2026-05-16T22:45:17+00:00", "active": true}
Comment thread README.md
Comment on lines +39 to +43
![Named Clusters tab — chat with an agent, conclude, save guidance to Adaptive Memory](docs/screenshots/01_named_clusters.gif)

The script:
**Data & Evidence tab** — per-iteration 2-D PCA projection of the clustered data, with the orchestrator's adaptive-escalation warning surfaced in line: *"Silhouette=0.142 < target 0.40 — orchestrator will reselect features (or escalate after 3 consecutive misses)"*.

- Loads `.env` and `config.yaml`
- Auto-detects whether to run FeatureEngineerAgent (raw CSV) or skip it (parquet)
- Runs the Decision Maker loop with `max_total_iterations=10`
- After each failure, the Decision Maker proposes new VIF/k/algorithm/silhouette parameters
- At max iterations, delivers a best-effort result if no iteration fully passed
- Writes all outputs under `outputs/` and prints a full console report
![Data & Evidence tab — per-iteration PCA projection with adaptive-escalation warnings](docs/screenshots/02_data_evidence.gif)
Comment thread record_demo.py
Comment on lines +133 to +134
if "awaiting_intent" not in {e for e in []} and not args.auto_submit:
print(" [demo] note: pipeline state =", json.dumps(status, indent=2))
Comment on lines +1 to +109
[run_pipeline] Logging full output to /Users/tzu-chunchen/Documents/AI/AI_Personalization/outputs/pipeline_run_20260516_180657.txt
[run_pipeline] Launching live UI at http://127.0.0.1:5057/ (use --no-ui to disable)
[run_pipeline] Using raw CSV for fresh feature engineering: data/raw/fraudTrain.csv
* Serving Flask app 'ui.app'
* Debug mode: off
Address already in use
Port 5057 is in use by another program. Either identify and stop that program, or start the server with a different port.
[Orchestrator] Skill catalog loaded: 11778 chars (skill.md)
[Orchestrator] Agent catalog loaded: 20866 chars (agent.md)

=================================================================
Multi-Agent Clustering & Persona Discovery Pipeline
=================================================================
Config : {'dataset_path': None, 'n_clusters': None, 'clustering_algorithm': 'auto', 'max_cluster_size_pct': 0.4, 'sub_n_clusters': 3, 'max_depth': 2, 'silhouette_target': 0.5, 'max_reselect_failures': 3, 'persona_tone': 'easy', 'ae_bottleneck_cap': 32, 'ae_max_iter': 200, 'classifier_model': 'auto'}
Features : data/raw/fraudTrain.csv
Max iters : 10
Classifier F1 threshold: 0.7

=================================================================
AGENTIC CLUSTERING PIPELINE — Intent Collection
=================================================================

[UserInput] Waiting up to 600s for intent from the UI form
[UserInput] (open http://127.0.0.1:5057/ and submit the intent form,
[UserInput] or press Ctrl-C to fall back to terminal prompts)

[UserInput] Interrupted — falling back to terminal prompts.
Before we begin, please answer a few questions.
(Press Enter to skip optional questions and use defaults.)


1. What entity are you clustering?
Examples: customers, products, employees, merchants
[Non-interactive] Using default: 'customers'

2. What is the business purpose of this clustering?
Be specific — this shapes which features are built and how clusters are interpreted.
Example: 'understand customer shopping behaviour to personalise product recommendations'
[Non-interactive] Using default: ''

[UserInput] Your answer is quite short — a more specific purpose
leads to better features and cluster labels.

Can you elaborate? (or press Enter to continue with what you gave)
Tip: mention what decision or action the clusters will support
[Non-interactive] Using default: ''

Default dataset path: data/raw/fraudTrain.csv

3. Dataset path? (press Enter to use default)
[Non-interactive] Using default: ''

4. Any constraints or filters? (optional — press Enter to skip)
Example: 'only use last 12 months of transactions', 'exclude VIP customers'
[Non-interactive] Using default: ''

5. How many clusters would you like? (press Enter to let the pipeline decide)
Example: '5' for exactly 5 clusters. Leave blank for data-driven selection.
[Non-interactive] Using default: ''

6. Must any specific types appear as clusters? (optional — press Enter to skip)
List types separated by commas — these are semantic labels the pipeline
MUST produce as distinct personas.
Example: 'traveller, high-value-customer, weekend-shopper'
[Non-interactive] Using default: ''

─────────────────────────────────────────────────────────────────
Captured intent:
Target entity : customers
Business purpose :
Dataset path : data/raw/fraudTrain.csv
Clusters wanted : data-driven (auto-select)
─────────────────────────────────────────────────────────────────
[Bus] ✓ UserInput (iter 0): SUCCESS — Collected intent: target='customers', purpose=''
Doubt: Business purpose may still be too vague.

Loading raw transaction data: data/raw/fraudTrain.csv
Traceback (most recent call last):
File "/Users/tzu-chunchen/Documents/AI/AI_Personalization/run_pipeline.py", line 181, in <module>
result = orchestrator.run(
features_path=_default_features_path,
max_total_iterations=10,
skip_user_input=False, # UserInputAgent will prompt for your clustering intent
)
File "/Users/tzu-chunchen/Documents/AI/AI_Personalization/agents/orchestrator.py", line 472, in run
full_raw_df = _load_df(raw_data_path)
File "/Users/tzu-chunchen/Documents/AI/AI_Personalization/agents/orchestrator.py", line 52, in _load_df
return pd.read_csv(path, sep=sep, low_memory=False)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/tzu-chunchen/Documents/AI/AI_Personalization/.venv/lib/python3.13/site-packages/pandas/io/parsers/readers.py", line 873, in read_csv
return _read(filepath_or_buffer, kwds)
File "/Users/tzu-chunchen/Documents/AI/AI_Personalization/.venv/lib/python3.13/site-packages/pandas/io/parsers/readers.py", line 306, in _read
return parser.read(nrows)
~~~~~~~~~~~^^^^^^^
File "/Users/tzu-chunchen/Documents/AI/AI_Personalization/.venv/lib/python3.13/site-packages/pandas/io/parsers/readers.py", line 1947, in read
) = self._engine.read( # type: ignore[attr-defined]
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
nrows
^^^^^
)
^
File "/Users/tzu-chunchen/Documents/AI/AI_Personalization/.venv/lib/python3.13/site-packages/pandas/io/parsers/c_parser_wrapper.py", line 219, in read
data = self._reader.read(nrows)
File "pandas/_libs/parsers.pyx", line 814, in pandas._libs.parsers.TextReader.read
File "pandas/_libs/parsers.pyx", line 906, in pandas._libs.parsers.TextReader._read_rows
File "pandas/_libs/parsers.pyx", line 885, in pandas._libs.parsers.TextReader._check_tokenize_status
File "pandas/_libs/parsers.pyx", line 2076, in pandas._libs.parsers.raise_parser_error
File "<frozen codecs>", line 322, in decode
KeyboardInterrupt
Comment on lines +1 to +3
{
"mode": "bypass"
} No newline at end of file
Comment on lines +329 to +346
deadline = time.time() + DECISION_TIMEOUT_S
decision = None
try:
while time.time() < deadline:
if PENDING_DECISION.exists():
try:
decision = json.loads(PENDING_DECISION.read_text(encoding='utf-8'))
except (OSError, json.JSONDecodeError) as exc:
print(f" [INTERACTIVE MODE] Bad decision file: {exc}")
try:
PENDING_DECISION.unlink(missing_ok=True)
except OSError:
pass
break
time.sleep(0.6)
except KeyboardInterrupt:
print("\n [INTERACTIVE MODE] Interrupted — proceeding without decision.")
return
Comment thread agents/user_input.py
Comment on lines +276 to +278
print(f"\n [UserInput] Waiting up to {UI_INTENT_TIMEOUT_S}s for intent from the UI form")
print(f" [UserInput] (open http://127.0.0.1:5057/ and submit the intent form,")
print(f" [UserInput] or press Ctrl-C to fall back to terminal prompts)")
Comment thread ui/llm_bridge.py
Comment on lines +27 to +35
def _load_env_file() -> None:
env_path = _ROOT / '.env'
if not env_path.exists():
return
for line in env_path.read_text().splitlines():
line = line.strip()
if line and not line.startswith('#') and '=' in line:
k, _, v = line.partition('=')
os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
Comment thread ui/llm_bridge.py
if str(_ROOT) not in sys.path:
sys.path.insert(0, str(_ROOT))

_MODEL = 'claude-sonnet-4-6'
Comment thread run_pipeline.py
Comment on lines +117 to 137
_MIN_PASSING_BEFORE_APPROVE = 3
_passing_count = {'n': 0}

_orig_chk = _orch_mod.human_checkpoint
def _auto_approve(personas, cr, clf, bus):
_orig_chk(personas, cr, clf, bus)
print('\n[Auto-approve] Selecting option 1 (Approve).')
_passing_count['n'] += 1
n = _passing_count['n']
if n < _MIN_PASSING_BEFORE_APPROVE:
print(f'\n[Auto-approve] Passing iteration #{n} — continuing to collect '
f'more candidates before picking the winner ({_MIN_PASSING_BEFORE_APPROVE} target).')
return HumanDecision(
action='recluster',
feedback=(
f'Exploration round {n}/{_MIN_PASSING_BEFORE_APPROVE}: keep iterating '
'to find a higher-F1 cluster set. Try a different algorithm or k.'
),
)
print(f'\n[Auto-approve] Collected {n} passing iterations — selecting best by F1.')
return HumanDecision(action='approve')
_orch_mod.human_checkpoint = _auto_approve
@ginaecho ginaecho merged commit 3010a78 into main May 21, 2026
1 check passed
@ginaecho ginaecho deleted the claude/interactive-cluster-interface-Pu2Ci branch May 21, 2026 17:46
ginaecho added a commit that referenced this pull request May 30, 2026
…ssifier reporting

Two bugs surfaced by the latest text run on text_articles.csv:

1. LogisticRegression.__init__() got an unexpected keyword argument 'multi_class'
   sklearn 1.5+ removed the `multi_class` kwarg (lbfgs handles multinomial
   automatically when n_classes > 2). `_build_model` now introspects the
   constructor signature and only passes `multi_class='auto'` on sklearn
   versions that still accept it. Works on both old and new sklearn.

2. AttributeError: 'NoneType' object has no attribute 'cv_accuracy'
   When the classifier crashed (per bug #1) the orchestrator set `clf = None`,
   but `human_checkpoint` dereferenced `classifier_result.cv_accuracy` /
   `.per_class_f1` unconditionally. Both call sites are now None-guarded:
   prints "(classifier failed this iteration — no F1 available)" when clf is
   missing, skips the per-class table + worst-performers section, and the
   persona table just shows " n/a" in the CV-F1 column.

Together these prevent a single classifier failure from aborting the entire
human-checkpoint stage and losing the whole iteration.

Tests:
- experiments/test_tabular_regression.py     ✓
- experiments/test_text_clustering.py        ✓
- experiments/test_text_e2e_orchestrator.py  ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <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.

3 participants