Skip to content

Feat/amd mi300x vllm#1

Open
slavazeph-coder wants to merge 19 commits into
mainfrom
feat/amd-mi300x-vllm
Open

Feat/amd mi300x vllm#1
slavazeph-coder wants to merge 19 commits into
mainfrom
feat/amd-mi300x-vllm

Conversation

@slavazeph-coder

Copy link
Copy Markdown
Owner

No description provided.

slavazeph-coder and others added 13 commits May 4, 2026 13:03
Adds a new "amd_vllm" provider that speaks the OpenAI-compatible protocol
to a self-hosted vLLM endpoint (e.g. AMD MI300X / Qwen 2.5 72B). A single
LLM_PROVIDER env flip moves the entire pipeline — review, chat, ask,
drafter↔judge loop — onto the new endpoint. All existing OpenAI / Ollama
/ Anthropic paths are untouched.

Also adds:
- runDebate(): N parallel calls to the same endpoint with different
  system-prompt overrides (skeptical / permissive / regulator voices).
  Designed for one Qwen endpoint hosting the whole panel.
- pingProvider() + GET /api/healthcheck/llm: live ping with latency +
  one-line sample. For demoing model availability without uploading.
- CRUMB handoff metadata gains a `provider` field (frontmatter + Matter
  section), and the generation audit row's free-text summary stamps
  "served by <provider>/<model>" so receipts capture which engine ran.

Tests: 322 → 334 passing + 1 skipped AMD live smoke (runs only when
AMD_VLLM_BASE_URL is set).

Env vars introduced:
  LLM_PROVIDER=amd_vllm
  AMD_VLLM_BASE_URL=http://<droplet-ip>:8000/v1
  AMD_VLLM_MODEL=Qwen/Qwen2.5-72B-Instruct
  AMD_VLLM_API_KEY=<optional bearer>

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…kers

Pre-existing dirty files unrelated to AMD wiring, captured here to keep
the AMD branch buildable end-to-end without reaching for stash:
  - /api/ask: append non-advice disclaimer when missing; suppress raw
    model citation fences; synthesize a canonical citations JSON from
    orphan [cN] markers when retrieval supports them.
  - matters/[id] OutputPane: tighten transcript/graph tab loaders.
  - .gitignore: ignore output/playwright/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the click-through demo surface for the hackathon judges:
  - GET  /demo/debate           — three-voice debate console UI
  - POST /api/debate            — SSE-streamed multi-voice panel
  - pnpm smoke:llm              — CLI ping of the configured provider
  - HACKATHON.md                — submission copy, posts, deck, video shot list
  - .env.example                — documents AMD_VLLM_* env vars

The debate console reads /api/healthcheck/llm on mount so the status
bar shows the live provider/model (e.g. amd_vllm/Qwen2.5-72B-Instruct
@ http://<droplet>:8000/v1) before any review runs.

Verified end-to-end:
  - smoke:llm against live AMD endpoint → 211ms round-trip
  - 334 tests passing + 1 AMD live smoke (skipped by default)
  - Next.js build registers /api/debate, /demo/debate, /api/healthcheck/llm
  - Railway preview unchanged (still openai/gpt-5.4-mini)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The /api/debate route was firing voices against an empty cognition store
on the synthetic 'debate-demo' org, so each voice fell into the
RETRIEVAL-GAP path and produced uncited prose. Honest behaviour, but a
poor demo: the headline capability is "three voices cite the same OM
against the SAME corpus differently," which only lands if there's a
corpus to cite.

Bootstrap the demo tenant via ensureTenant(), then retrieve top-6
matches per request and inject them as the agent context's
retrievedSnippets. Voices now produce citation-grade prose with [cN]
markers anchored to real NI 45-106 / OSC Rule 45-501 authorities.

The latency cost is ~150ms (one bootstrap pass + one BM25 retrieval) on
top of the model wall-clock — well worth it for a demo where judges
can see the same authority cited differently by skeptical vs.
permissive vs. regulator voices.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Round of simplifications after the AMD path landed:

- HACKATHON.md → AMD_HACKATHON.md, slimmed to a 60-second runbook +
  surface table + env vars + verification numbers. Private drafts (deck
  outline, video shot list, post copy, ops checklist) moved to
  .local-hackathon-notes.md (gitignored).
- README gains a one-paragraph callout pointing at AMD_HACKATHON.md and
  documents the new amd_vllm Provider Mode alongside the existing three.
- /demo/debate heading tightened: "Three reviewers, one GPU" + a back
  link to /demo cockpit. Subtitle cut from 4 lines to 2.
- /demo cockpit gains an aside card linking to /demo/debate so judges
  landing on /demo find the new surface.
- CRUMB handoff: dropped the duplicate "- LLM: <provider>" row in the
  Matter section; the YAML frontmatter `provider:` line is the single
  canonical location.
- DEFAULT_COMPLIANCE_VOICES suffixes tightened — same stance, fewer
  tokens.
- scripts/smoke-llm.mjs gains --help with usage examples.

No behavioural change — copy + docs polish + one less duplicate field.
334 tests + lint + typecheck still green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…+ race fix

Three-fold expansion of the debate cockpit, plus a real concurrency fix.

UNIVERSAL TEMPLATES (4 use cases)
  - compliance      — three regulator stances on an OM excerpt (default)
  - code-review     — Senior engineer / Security auditor / Performance hawk
  - decision        — Optimist / Skeptic / Devil's advocate
  - doc-critique    — Strict editor / Confused reader / Subject expert
Each template ships its own prompt + voices + retrieve-authorities flag.
Click a chip to swap the whole panel; the prompt and voice list reset to
the template defaults, and the retrieval call is skipped for the three
universal templates so voices aren't told "no authorities were retrieved"
on a query that has nothing to do with authorities.

TOKEN STREAMING (visceral demo)
  - runDebate() gains an `onEvent` callback that fires voice-started /
    voice-delta / voice-completed as the model emits.
  - /api/debate forwards every event through SSE.
  - DebateConsole's voice cards now fill in real time character-by-character
    instead of "running… → done." The card border glows blue while
    streaming, with a blinking cursor at the tail of the prose.

INTERACTIVE CONTROLS
  - Stop button cancels in-flight panels via AbortController; voice fetches
    abort cleanly, unfinished voices mark as "stopped."
  - Copy button per card — copies "## <name>\n\n<prose>" markdown to
    clipboard.
  - Edit-voices panel: rename voices, edit prompts, add a 4th, remove
    one. Lets a viewer compose a custom panel without code.
  - Status bar tightened: ms+sample on the right, retrieved-authority
    count appears when relevant.

RACE FIX
  - The old debate code mutated PERSONA_SYSTEM_PROMPTS in-flight and
    relied on a try/finally restore. With three concurrent voices on the
    same persona slot, the second voice's mutation could be read by the
    first voice's runAgent — a real race that mixed prompts in
    practice. Fixed by adding RunAgentOptions.systemPromptOverride and
    plumbing the per-voice prompt directly through runAgent. Registry no
    longer mutated. Test mocks updated to read the override path.

RETRIEVAL-CONTEXT FIX
  - renderCognitionContext used to emit a "RETRIEVAL GAP" directive on an
    empty array. That's correct safety behaviour for the compliance
    pipeline (caller passes []) but actively wrong for code-review /
    decision / doc-critique voices where no retrieval was ever intended.
    Now distinguishes undefined (skip block entirely) from [] (still emit
    the directive). The compliance pipeline still passes []; the debate
    route passes undefined for non-compliance templates.

VERIFIED LIVE AGAINST AMD MI300X
  - Compliance template: 3/3 voices, citation-grade output, 35s wall.
  - Code review template: 3/3 voices, real critiques (SQL injection,
    N+1, unsafe deserialization), 33.7s wall, zero RETRIEVAL GAP hits.
  - Tests 334 → 336 passing + 1 AMD live smoke (skipped by default).
  - Stop button proven mid-stream; cards mark unfinished voices as
    "stopped".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Feedback was that judges and first-time visitors didn't grok WHAT the
app does, just SEEN it run. This rewires the demo to teach value before
showing mechanics.

EXPLAINER-FIRST HERO
  Page heading → "Three AI experts. One question. Side by side."
  Subtitle    → plain English: "Most AI tools give you one answer. This
                runs three at the same time and summarises where they
                agree, where they disagree, and what to actually do."
  Three labelled steps (1. Pick a use case → 2. Three voices critique
  → 3. Read the synthesis) so a stranger can read once and get it.

SYNTHESIS CARD (the killer feature)
  After all voices land, /api/debate makes one extra LLM call that
  reads the three voice outputs and emits:
    { agreed: string[], disagreed: string[], verdict: string }
  Rendered above the voice cards as a bordered "THE TAKEAWAY" panel.
    - One-sentence verdict at the top.
    - "ALL VOICES AGREED" — green-dotted bullets (5-12 word phrases).
    - "VOICES DIVERGED"   — amber-dotted bullets.
  Three blobs of dense markdown become one paragraph the viewer can
  act on. Verified live: a Decision-making run on AMD MI300X produced
  verdict "Consider both options carefully; weigh immediate growth
  against long-term control" plus 3 agreed + 3 diverged bullets in
  19.2s wall.

USE-CASE HINTS
  Each template chip gains a `useWhen` field surfaced as a tooltip
  AND inline:
    Compliance: "You're reviewing a legal/regulatory document and want
                 to catch what one reviewer would miss."
    Code:       "You're shipping a PR and want senior, security, and
                 performance reads in one shot."
    Decision:   "You're stuck between two options..."
    Doc:        "You wrote something and want three brutal-but-fair
                 edits."
  The chip label tells you WHAT, the hint tells you WHEN you'd use it.

/demo cockpit reframed
  Heading "One workspace for the whole review" + plain-English what
  the app does. Bordered "Try first" panel pointing at /demo/debate
  with a button-style CTA, and the universal pitch ("Works for
  compliance review, code review, hard decisions, document critique").

The synthesis is graceful: failure is non-fatal; the SSE stream still
emits debate-done so the UI closes cleanly. While the synthesis call
is in flight (1-3s after voices complete) the panel shows
"Synthesizing — reading all three voices..." so the viewer knows
something's happening.

336 tests + 1 AMD live smoke still green. Lint + typecheck clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…psed

E2E walkthrough surfaced concrete UX issues: hero ate the first screen,
output dumped three walls of markdown, no "what now" CTA. Redesigned
the input/output flow around what the viewer actually wants:

INPUT
  - Hero compressed: 3-step ol card removed; one-line subtitle that
    states the value ("Pick a use case or paste your own. Get one
    verdict, plus where the voices agreed and diverged."). Provider
    bar, chips, and Run button all above the fold on a normal
    viewport.
  - Textarea now labelled "YOUR INPUT" (uppercase, bold) so the
    viewer knows what to do without thinking.
  - Placeholder updated: "Drop a question, code snippet, contract
    clause, OM excerpt, decision…" — matches the universal templates.
  - "Reset to example" link appears whenever the prompt or voices
    drift from the template default. One click restores the curated
    example.
  - Run button label now says "Run debate · 3 voices" so the viewer
    knows what's about to fire.

OUTPUT
  - The synthesis card now renders ABOVE the voice cards. Verdict +
    agree/diverge bullets are the FIRST thing the viewer reads.
    Voice cards are supporting detail.
  - Voice cards COLLAPSED by default after the run completes —
    line-clamp-3 preview of the first sentences, "expand full
    critique" toggle to show the full prose. While running they
    auto-expand so the streaming tokens are visible.
  - "Result" section labelled with "copy verdict" + "copy full
    report" actions inline on the right. Copy-full-report stitches
    verdict + agreed + diverged + every voice's full prose into a
    single markdown blob.

WHILE RUNNING
  - "X/N voices done" counter to the right of the Stop button.
    Once all voices complete, it shows "synthesizing…" until the
    synthesis lands, then "synthesis ready". The viewer always knows
    what's happening.

Verified live against AMD MI300X:
  - Decision-making run: 3/3 voices in 20.1s, synthesis card on top,
    voice cards collapsed with copy/expand controls, copy-verdict
    and copy-full-report wired and present.
  - 336 tests + lint + typecheck still green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ding

Coordinated push to make the app demo-ready for AMD x lablab.ai judges.

HOMEPAGE REWRITE (apps/web/src/app/page.tsx)
  Was: "XIO Compliance Brain" + doc-drop zone + "private installs run
  Ollama" — no mention of the AMD/Qwen multi-voice debate at all.
  Now: hackathon-shaped landing.
    - Hero leads with the value: "Three AI experts. One question.
      Side by side." + the AMD tagline ("Built on AMD Instinct MI300X ·
      Qwen 2.5 72B · vLLM").
    - Live provider pill in the top-right corner (calls /api/healthcheck/llm
      on mount; green dot when the GPU is online).
    - Two CTAs above the fold: "Open the debate →" (primary) and
      "Compliance workbench" (secondary).
    - "Try a use case" grid: 4 quick-launch cards, each linking to
      /demo/debate#t=<id> so a viewer lands on the right template
      pre-selected.
    - "Why this is different" — three value props: (1) three voices /
      one GPU explaining the 192GB HBM3 headroom story, (2) citation-
      grade receipts, (3) universal use cases.
    - The compliance-workflow doc-drop zone is preserved but DEMOTED —
      lives below the fold under "Have a real document?" so the AI
      story leads.
    - Footer with hackathon credit + GitHub link.

AMD POWER, MADE VISIBLE
  Three new pieces of telemetry surface what the MI300X is doing:

  1. tokens-per-sec on the ping (packages/agents/src/health.ts)
     pingProvider() now captures outputTokens from the runAgent stream
     and computes tokens/sec from outputTokens/latencyMs. Surfaced in
     the provider bar as "314ms · 6 tok/s · 'ready'" and on the homepage
     pill. Sample is small (~2 tokens) so this number under-reports
     steady-state; the live counter below covers that.

  2. context-window pill (packages/agents/src/health.ts + DebateConsole)
     pingProvider() now also fetches /v1/models and surfaces
     max_model_len. The provider bar gets a "32K ctx" pill so a viewer
     instantly knows the model can chew on long documents — a
     legitimate AMD/MI300X memory-headroom story.

  3. live tokens/sec during streaming (DebateConsole)
     Every voice-delta event bumps a sliding-window counter
     (chars→tokens at ~4:1). Renders as an emerald pill while running:
     "~38 tok/s". This is the visceral GPU-power demo — judges see
     concurrent throughput, not a static benchmark.

PERMALINK SHARE
  - "share" button next to Run copies a permalink encoding the prompt
    + selected template into the URL hash (#t=<id>&q=<base64>). The
    hash never hits the server, so confidential prompts stay private,
    and the URL pastes cleanly into Slack / docs.
  - First-render hook decodes the hash and hydrates the prompt +
    template selection. Homepage use-case cards already produce these
    URLs (#t=compliance, #t=code-review, etc.) so a click flows
    straight into the right cockpit.

NEW EXPORTS (packages/agents/src/index.ts)
  - ProviderModelInfo type for callers who want the model facts.

VERIFIED LIVE
  - / renders compressed hero, live provider pill, 4 use-case cards,
    3 value props, doc-drop demoted, footer with hackathon credit.
  - /demo/debate#t=decision auto-selects Decision template, populates
    its prompt, shows "amd_vllm → Qwen/Qwen2.5-72B-Instruct · 32K ctx
    · 314ms · 6 tok/s · 'ready'" in the provider bar.
  - 336 tests + lint + typecheck still green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… runbook refresh

Final hackathon polish round. Voices now get a SECOND turn after the
synthesis lands — defending, updating, or conceding their stance based
on what the others said. Plus assorted polish.

ROUND-2 FOLLOW-UP (the marquee AMD-power feature)
  - New runFollowup() in packages/agents/src/debate.ts. After synthesis,
    each surviving round-1 voice gets a second runAgent call where its
    prompt includes (a) the original question, (b) its prior critique,
    (c) what the OTHER voices said, (d) the synthesis. The model returns
    a fenced JSON {stance: DEFENDED|UPDATED|CONCEDED, prose: 2-4
    sentences}.
  - Voices run in parallel (Promise.allSettled), so round-2 is one extra
    parallel batch on the same single MI300X — a literal demonstration
    of memory headroom that judges can read in plain language.
  - /api/debate gets a `followup: true` body flag (off by default to
    keep the basic demo fast) and emits followup-started /
    followup-delta / followup-completed / followup-done SSE events.
  - DebateConsole gains a "round 2" checkbox and a new <FollowupSection>
    that renders below the round-1 cards. Each follow-up card has a
    color-coded stance pill: blue=defended, amber=updated,
    emerald=conceded, plus a summary like "1 defended · 2 updated".
  - Verified live: a Decision-making run with round-2 enabled returned
    "3 updated" — Optimist, Skeptic, and Devil's advocate all softened
    their stances after seeing each other's arguments. ~24s round-1 +
    ~12s round-2 on a single GPU.

HOMEPAGE PROVIDER PILL — CTX + OFFLINE STATE
  - The top-right pill on / now displays the model context window
    (e.g. "32K ctx") alongside provider/model/latency. Sourced from
    pingProvider's modelInfo.maxContextTokens (which already calls
    /v1/models in the agents/health.ts path).
  - When the ping returns ok=false (droplet offline), the pill now
    shows "provider offline" with a tooltip, instead of staying on
    "checking provider…" forever.

RUNBOOK
  - AMD_HACKATHON.md rewritten with the complete feature inventory:
    surfaces table, AMD-power-viz list (TPS pill, ctx pill, round-2,
    same-GPU panel), universal templates table, env vars, verified
    numbers. Designed to be the single doc a judge or reader needs.

INTEGRATION
  - New exports from packages/agents: runFollowup, type VoiceFollowup.
  - 336 tests + lint + typecheck still green.
  - No regressions in existing surfaces (tested cold-load + permalink
    + edit voices + stop button + copy buttons).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ray order

Self-review caught a real bug masked by all-voices-succeed demos. When a
round-1 voice errors it gets skipped from the round-2 batch — packing
the response array dense (`arr.map((f) => …)`) shifted the surviving
voices into the wrong cards. Result: a viewer would see Voice A's
defense rendered under Voice B's name. Disastrous for a hackathon demo
because compliance review's *whole point* is "this voice said X."

Fix: the server already includes `index` in the followup payload. The
client now positions each follow-up at its original voice index so
sparse arrays render with empty slots where round-1 voices errored,
not with shifted neighbours.

Also: mirror voiceStates into a ref so the followup-done handler can
size the destination array without depending on stale closures.

336 tests + lint + typecheck green. Browser verified: cockpit hydrates
clean, no console errors, round-2 checkbox + 32K ctx pill render.

Tried to delegate this review to Codex per /with-codex protocol but
the MCP tool returned "Quota exceeded" on every attempt. Surfacing the
finding here so it's documented for future review rounds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ngProvider

Final round of polish + test coverage. Tries again to /with-codex but the
MCP returned "Quota exceeded" on every attempt this session — work is
done solo with the same intent (Codex's review and test-writing tasks).

OFFLINE SAMPLE MODE (apps/web/src/app/demo/debate/samples.ts)
  - DEBATE_SAMPLES indexed by template id, each containing pre-recorded
    voices, synthesis, and (where relevant) round-2 follow-ups from
    real AMD MI300X / Qwen 2.5 72B runs. Bundled, no fetch, no API
    call — works offline, on a flaky network, or "before you pay the
    inference latency."
  - "view sample" button on /demo/debate (next to share). Click to
    instantly hydrate the cockpit with realistic output: verdict,
    agreed bullets, diverged bullets, three voice cards, three round-2
    follow-up cards (with stance pills), and a wall-clock summary
    sourced from the original recorded run.
  - The sample button is the answer to "what if the droplet is off
    during the demo?" — judges still see the full UX. Also the answer
    to "show me what good output looks like before I commit 30s."

NEW TEST COVERAGE (336 → 350 passing, +14)
  packages/agents/src/__tests__/debate.test.ts (was 7 tests, now 14):
    - synthesizeDebate parses fenced JSON
    - synthesizeDebate returns null when fewer than 2 voices ok
    - synthesizeDebate returns null on malformed JSON
    - runFollowup skips errored round-1 voices, preserves original index
    - runFollowup returns [] when fewer than 2 voices ok
    - runFollowup falls back to "unclear" stance on garbage output
    - runFollowup emits started/delta/completed events with original index
  packages/agents/src/__tests__/health.test.ts (NEW, 7 tests):
    - pingProvider returns ok=true with sample, latency, tokensPerSec
    - pingProvider populates modelInfo with maxContextTokens
    - pingProvider reports ok=false with error message on stream error
    - pingProvider reports ok=false on timeout
    - /v1/models failure → modelInfo undefined (best-effort)
    - anthropic provider skips /v1/models entirely
    - falls back to data[0] when configured model id isn't listed

  All tests are deterministic — they mock runAgent + global fetch and
  smuggle behavior tags through the prompt (e.g. "__synth_garbage__",
  "__ping_50_tokens__"). No real LLM calls, no real network.

VERIFIED LIVE
  - "view sample" on Decision template → instant load: verdict ("Run a
    30-day cohort-retention check…"), 3 round-1 cards, 3 round-2 cards
    (Optimist updated, Skeptic defended, Devil's advocate updated),
    "3/3 voices in 24.4s" summary.
  - 350 tests + 1 AMD live smoke (skipped without env). Lint clean,
    typecheck clean across 9 packages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After 13 commits of feature work the README and a few stale strings
elsewhere were lying about what the app does.

README.md
  - Leads with the AMD multi-voice debate (the headline) instead of the
    legacy compliance-cockpit-only framing.
  - "Two products in one cockpit" — debate (universal) + compliance
    (vertical) — explicitly framed.
  - Surfaces table covers /demo/debate, /api/debate, /api/healthcheck/llm,
    permalinks, and the rest of the new routes.
  - Quickstart shows the AMD path first (point at vLLM, pnpm smoke:llm,
    pnpm dev:web). OpenAI / Ollama / Anthropic are still documented under
    Provider Modes.
  - "What makes this different" — three voices on one GPU, citation-grade
    receipts, universal use cases, visible AMD power (TPS pill, ctx pill,
    Round 2, same-GPU panel).
  - Verification block bumped to 350 tests + 1 AMD live smoke.
  - Monorepo layout updated with the new files (debate.ts, health.ts,
    api/debate, api/healthcheck/llm, demo/debate, samples.ts).

Version string
  - apps/web/src/app/api/healthcheck/route.ts version default bumped
    "0.9.0-demo-cockpit" → "1.0.0-amd-mi300x". Surfaces in
    /api/healthcheck JSON response.

Stale Ollama-only copy (would mislead a hackathon judge)
  - apps/web/src/app/demo/DemoCockpitTabs.tsx "Live path" panel:
    "Private installs can run local Ollama" → "...can run on your own
    AMD MI300X (vLLM + Qwen 2.5 72B), local Ollama, or any
    OpenAI-compatible endpoint."
  - apps/web/src/app/login/page.tsx footer: same expansion.

Verified live:
  - /api/healthcheck returns version "1.0.0-amd-mi300x"
  - /demo footer shows the new AMD/vLLM/Ollama/OpenAI-compatible copy
  - 350 tests + lint + typecheck still green

Co-Authored-By: Claude Opus 4.7 <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: 41b2b21722

ℹ️ 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 packages/agents/src/debate.ts Outdated
Comment on lines +218 to +222
return Promise.race<DebateVoiceResult>([
call,
new Promise<DebateVoiceResult>((resolve) =>
setTimeout(() => {
const timeoutResult: DebateVoiceResult = {

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 Abort timed-out voices before resolving runDebate

With timeoutMs set, the timeout branch only wins a Promise.race but never cancels the underlying call, so that voice can continue streaming and later emit additional completion events after being marked timed out. In /api/debate this can surface out-of-order voice-completed states and wastes model capacity on work the caller already abandoned.

Useful? React with 👍 / 👎.

Comment on lines +24 to +25
const maxTokensParam = url.searchParams.get("max_tokens");
const maxTokens = maxTokensParam ? Math.min(256, Math.max(1, Number(maxTokensParam))) : 32;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Sanitize non-numeric max_tokens query values

The route clamps Number(maxTokensParam) without checking for NaN, so a request like ?max_tokens=abc passes NaN into pingProvider. That value propagates into the completion payload and can cause provider-side validation failures instead of falling back to the intended default probe size.

Useful? React with 👍 / 👎.

Public hackathon demo URL needs a budget guardrail. A multi-voice
debate is O(N+2) inferences (N voices + synthesis + optional round-2)
on a shared GPU — without a limit one curious tab can drain the $100
AMD Cloud credit in an afternoon.

apps/web/src/lib/rate-limit.ts (new)
  - consumeToken(key, { capacity, refillPerSec }) → { allowed,
    retryAfterSec, remaining }. Pure token bucket, in-memory Map keyed
    by client id. Fits a 1-replica preview.
  - clientIdFromRequest() — best-effort client id from x-forwarded-for
    / cf-connecting-ip / x-real-ip / "anon".
  - readConfigFromEnv("DEBATE_RATE_LIMIT", fallback) — parse
    "capacity:refillPerSec" so we can retune without a redeploy.

apps/web/src/lib/__tests__/rate-limit.test.ts (new, 10 tests)
  - Burst/deny path, refill over time, key isolation, retry-after
    bounds, header parsing, fallback paths. Uses vi.useFakeTimers
    for deterministic refill assertions.

apps/web/src/app/api/debate/route.ts
  - Rate-limit BEFORE any other work — bad client pays zero GPU.
  - Returns 429 with Retry-After + X-RateLimit-Limit /
    X-RateLimit-Remaining when the bucket is empty.
  - Default: 5 debates per hour per client (slow refill). Override
    via DEBATE_RATE_LIMIT="capacity:refillPerSec".
  - DEBATE_RATE_LIMIT_DISABLE=1 turns it off (local dev + tests).

Tests: 354 → 364 passing + 1 skipped. Lint clean. Typecheck clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Adds the public-facing assets the user can paste into X / LinkedIn /
Slides for hackathon submission. All assets reference the now-live
AMD-backed Railway preview at
https://compliance-ai-amd-demo-production.up.railway.app

output/hackathon/post-1-the-diff.md
  Three #AMDDevHackathon posts (the diff · the headline capability ·
  the receipt). Each includes recommended image-to-attach guidance
  (hero screenshot, screen recording, CRUMB-frontmatter screenshot).
  Three posts qualifies for the Build in Public track bonus.

output/hackathon/pitch-deck.md
  Marp/markdown deck source. 11 slides covering: hero, problem,
  product, demo cue, MI300X comparison, CRUMB receipts, visible AMD
  power, architecture diagram, scaling, what ships, what it took,
  CTA. Renders to Slides via Marp or any markdown-to-deck pipeline.

Co-Authored-By: Claude Opus 4.7 <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: e46e061b89

ℹ️ 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 on lines +227 to +230
try {
body = (await req.json()) as DebateRequestBody;
} catch {
// empty body → use defaults

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 Reject malformed JSON bodies before running default debate

The req.json() failure path currently treats all parse errors as an empty body, so syntactically invalid JSON (for example a truncated payload) silently falls back to defaults and still triggers a full multi-voice debate run. That can burn expensive model capacity and return unrelated output instead of a client-visible 400, which is especially problematic for automated callers and abuse scenarios.

Useful? React with 👍 / 👎.

typeof record.personaId === "string" && record.personaId.trim()
? record.personaId.trim()
: undefined;
if (personaId && !(personaId in PERSONA_SYSTEM_PROMPTS)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate persona IDs against own properties only

Using the in operator here allows prototype keys like "toString" to pass validation as if they were valid persona IDs. Those values then flow into prompt selection and can produce unexpected non-persona system prompt content instead of being rejected. Checking only own keys (not inherited ones) avoids accepting invalid persona IDs from untrusted request bodies.

Useful? React with 👍 / 👎.

…t · GPU activity · deck PDF

Plan-driven push (.claude/session-plan.md) targeting the four lablab
judging criteria. After self-adversarial debate (Codex quota exhausted)
the locked top-5 ranked highest-impact-per-effort:

1. **Pitch deck PDF** (Presentation) — 190 KB rendered via marp-cli
   from output/hackathon/pitch-deck.marp.md. Required deliverable for
   submission.

2. **ROI panel on home page** (Business Value, was 3.0/5) — three tiles
   with honest framing: "3–5 lawyer-hours per OM · ≈$0.04 inference cost
   on this app · ≈$8.10 cloud equivalent." Disclaimer notes synthetic
   numbers, real-world spread varies. The argument is order-of-magnitude.

3. **First-visit autoplay sample** (Presentation) — on cold load, the
   cockpit auto-hydrates with a pre-recorded sample (verdict + 3 voices
   + round-2 stance pills) so judges see rich content INSTANTLY, with
   zero API calls and zero AMD-credit spend. localStorage flag means it
   only fires once per browser. Inlined the sample-loader logic in the
   useEffect rather than calling viewSample() to keep
   react-hooks/exhaustive-deps clean.

4. **Permalink-with-results** (Originality) — new "share result" button
   encodes the FULL debate output (synthesis + voices + round-2) into
   the URL hash (#r=<base64-json>). Recipients see exactly what we saw,
   no API call needed, no DB. Decoder hydrates on first render before
   autoplay decision so a shared result wins over autoplay.

5. **GPU activity proof in /api/healthcheck/llm** (Application of Tech)
   — pingProvider() now scrapes vLLM's Prometheus /metrics endpoint and
   surfaces requestsRunning, requestsWaiting, prompt/generation token
   totals, gpuCacheUsage, engineSleepState. Cockpit's provider bar
   shows a "GPU idle" / "🔥 N running · M queued" pill that flips when
   the GPU is doing real work — irrefutable activity proof beyond the
   ping-only TPS number. Best-effort: undefined for non-vLLM providers.

Verified live (local Next + live AMD droplet):
  - GET /api/healthcheck/llm includes engineMetrics:
      {requestsRunning:0, promptTokensTotal:44543, generationTokensTotal:12902,
       engineSleepState:"awake"}
  - / shows ROI tiles ($0.04 vs $8.10) + footnote
  - /demo/debate cold load → synthesis card with real verdict, 3 voice cards,
    no inference call (localStorage flag set)
  - "share result" button enabled when synthesis exists
  - Tests 364→364 passing (no test changes), lint clean, typecheck clean

Co-Authored-By: Claude Opus 4.7 <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: 11ce884d9a

ℹ️ 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".

*/
export function clientIdFromRequest(req: Request): string {
const xff = req.headers.get("x-forwarded-for");
if (xff) return xff.split(",")[0]!.trim();

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 Use a trusted client IP for rate-limit keys

The rate limiter keys on x-forwarded-for's leftmost value, which is client-controlled in many deployments; an attacker can send a different spoofed first IP per request and effectively bypass consumeToken on /api/debate. This defeats the new GPU-cost protection under hostile traffic and is especially impactful because the endpoint is intentionally expensive. Use only trusted proxy-added headers (or parse from the trusted end of XFF with a proxy trust list) before deriving the bucket key.

Useful? React with 👍 / 👎.

Comment on lines +110 to +117
const index = Number(marker.replace(/^c/, "")) - 1;
const result = fusedTop[index];
if (!result?.item.id) continue;
citations.push({
id: marker,
authorityId: result.item.id,
section: result.item.title,
quote: result.item.content.slice(0, 240),

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 fabricating citation mappings from marker numbers

When the model emits orphan markers without a citation fence, this code synthesizes citations by mapping [cN] to fusedTop[N-1], which can attach the wrong authority/quote to the model's claim and present it as grounded evidence. In truncation or malformed-output scenarios, the API can therefore return and audit-chain incorrect citations instead of signaling missing citation integrity, undermining the reliability of compliance output.

Useful? React with 👍 / 👎.

Repositions the app from a generic "three AI experts debate" demo into a
serious compliance workbench with one-click judge demo, no dead states,
and clear reviewer roles. Brand stays in the XIO family.

POSITIONING
  XIO Compliance Brain
  Triad Review Engine for audit-ready compliance work
  Tagline: "Three AI reviewers. Verified citations. Audit-ready decisions."

REVIEWER ROLES (renamed across debate.ts + samples + tests)
  - Skeptical reviewer  → Regulatory Counsel (rule breaches · disclosures)
  - Permissive reviewer → Risk Officer (severity · exposure)
  - Regulator voice     → Evidence Auditor (citations · stale authority)
  System-prompt suffixes rewritten in role language; tests updated.

NEW: SEED MODULE (apps/web/src/lib/triad-seed.ts)
  Single source of truth for the judge demo, the homepage result preview,
  and the empty-state seeding on /queue, /matters, /approvals. One Ontario
  OM matter (North Capital Series A) with 6 findings, 5 citations across
  all 5 badge types (verified / needs-check / missing / stale /
  jurisdiction-mismatch), 1 reviewer disagreement, 4 recommended fixes,
  and an export-blocked approval state. Plus seeded queue (4 items),
  matters (4 items), and approvals (2 items, one blocked one pending).
  Honest disclosure: "Demo seed. Illustrative, not a customer engagement."

NEW: /demo/judge — 90-second judge demo
  One-click seeded compliance review. No upload, no API call, no AMD-
  credit spend. Reads top-to-bottom and answers all six judge-questions:
  what was reviewed · what risks were found · what citations support them
  · where reviewers disagreed · what to do next · can it be approved.
  Includes:
    - Matter strip with 6 stat tiles (compliance score, critical gaps,
      verified citations, needs-verification, reviewer disagreement,
      export status)
    - Excerpt under review + reviewer instruction
    - Three reviewer cards (Counsel · Risk · Evidence) with role
      descriptions
    - 6 finding cards with severity pills, reviewer attribution,
      recommended-fix block, citations with verification badges
    - "Where reviewers disagreed" panel — 3-column views + final action
    - "Approval required before export" panel — output hash, reviewer
      status, approval state, export state, last-modified timestamp
    - Export buttons: DOCX disabled, redline disabled, CRUMB enabled
      (CRUMB endpoint is real)
    - Why-AMD-matters panel with model/serving/hardware/workflow
    - Honest disclosure footer + next-step links

HOMEPAGE REWRITE (apps/web/src/app/page.tsx)
  - "XIO Compliance Brain" badge above the hero
  - "Triad Review Engine for audit-ready compliance work" hero
  - Result preview card above the fold mirroring /demo/judge state
  - Three reviewer tiles (Counsel · Risk · Evidence) with role lines
  - Use-case grid narrowed to 6 compliance verticals: OM review · KYC ·
    marketing compliance · regulator letters · privacy memos · contract
    risk
  - ROI panel kept; AMD technical panel kept (now "Why AMD matters")
  - Footer with hackathon credit + GitHub link

DEAD-STATE CURE — /queue · /matters · /approvals
  Empty/loading states now fall through to the seeded Triad Review demo
  set when the API returns nothing. "Demo mode" hint banners are honest
  about the substitution. No more "Building queue…" / "No matters yet" /
  bare loading bars — replaced with skeletons + seeded fallbacks.
  Matter cards now show: matter type, status, compliance score, critical
  gaps, verified citations, last review timestamp, next action.
  Approval cards now show: output hash (truncated), reviewer status,
  approval state, gate-explanation note.

DOCS
  - README rewritten to lead with "Triad Review Engine" and the
    90-second demo path. "Why it matters" section frames the multi-
    reviewer thesis ("most legal AI tools give one answer; that's
    dangerous in compliance").
  - HACKATHON_SUBMISSION.md (new) — paste-ready submission copy with
    short description (255ch), long description (≥100w), scaling note,
    tech stack tags, demo path, demo script, judging-rubric mapping,
    verified-numbers block.

Verified live (local Next + live AMD droplet):
  - / shows XIO Compliance Brain · Triad Review Engine + result preview
  - /demo/judge shows 6 findings, 5 badge types, 1 disagreement, export
    gate blocked
  - /queue shows seeded 4-item demo queue with "Demo mode" disclosure
  - /matters shows seeded 4-item demo matter set
  - /approvals shows 2 seeded approvals (1 blocked, 1 pending)
  - Tests 364 → 363 passing + 1 skipped (no test count change; rename
    swap is internal). Lint clean. Typecheck clean.

Co-Authored-By: Claude Opus 4.7 <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: 333f349f27

ℹ️ 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 on lines +34 to +40
const started = Date.now();
try {
const result = await pingProvider({
...(customPrompt ? { prompt: customPrompt } : {}),
maxTokens,
timeoutMs,
});

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 Add abuse controls before invoking the live LLM healthcheck

This handler exposes an unauthenticated, unthrottled path that always calls pingProvider, and it even accepts caller-controlled prompt/max_tokens. In an internet-facing deployment, repeated requests can continuously consume paid model capacity and bypass the new cost controls added to /api/debate, so a single abusive client can still drain inference budget through this endpoint.

Useful? React with 👍 / 👎.

headers.Authorization = `Bearer ${process.env.AMD_VLLM_API_KEY}`;
}

const res = await fetch(`${baseUrl}/models`, { headers });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce timeout on the /models probe in pingProvider

pingProvider applies timeoutMs to the generation ping, but fetchModelInfo performs a plain fetch with no abort/timeout and is always awaited afterwards. If /v1/models is slow or hangs, the healthcheck can block far beyond the configured timeout budget and fail liveness/readiness expectations even when token generation itself is healthy.

Useful? React with 👍 / 👎.

Comment on lines +253 to +257
const requestsRunning = num(/^vllm:num_requests_running\{[^}]*\}\s+([0-9.eE+-]+)/m) ?? 0;
const requestsWaiting = num(/^vllm:num_requests_waiting\{[^}]*\}\s+([0-9.eE+-]+)/m) ?? 0;
const promptTokensTotal = num(/^vllm:prompt_tokens_total\{[^}]*\}\s+([0-9.eE+-]+)/m) ?? 0;
const generationTokensTotal =
num(/^vllm:generation_tokens_total\{[^}]*\}\s+([0-9.eE+-]+)/m) ?? 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.

P2 Badge Return engineMetrics as unavailable when vLLM counters are absent

This parser defaults missing counters to 0 and still returns an object, which fabricates a valid-looking metric payload when the endpoint does not expose vllm:* series (for example non-vLLM providers). That misreports telemetry as “idle engine” instead of “metrics unavailable,” which can hide real observability failures and contradicts the function contract that missing counters should yield no metrics object.

Useful? React with 👍 / 👎.

End-to-end audit before hackathon submission surfaced 5 P1 issues. All
fixed. No new features.

FIX 1+2 — CRUMB handoff link returned 404 on /demo/judge
  The "Download CRUMB handoff pack" button on /demo/judge pointed at
  /api/matters/demo-ontario-om-2026-q2/handoff, but the seeded matter
  doesn't live in the matter store, so a judge clicking it saw a 404.
  Built apps/web/src/app/api/demo/handoff/route.ts that renders the
  full CRUMB pack from the same triad seed that powers the demo —
  matter, findings by reviewer (Counsel · Risk · Evidence), citation
  integrity counts, disagreement panel, approval gate, engine receipt
  with live provider stamp. Repointed /demo/judge link to the new
  /api/demo/handoff route. Verified: returns 200 with proper YAML
  frontmatter + markdown body and Content-Disposition: attachment.

FIX 3 — /demo cockpit was pre-Triad
  Hero said "One workspace for the whole review" with a "three-voice
  debate" inline link and a "Three AI experts. One question. Side by
  side." aside. Replaced with:
    - Hero: "One workspace for the Triad Review" with Counsel · Risk ·
      Evidence framing
    - Inline link now points to /demo/judge ("90-second judge demo")
    - Aside now leads with "Try first · 90-second judge demo" + a
      secondary CTA for /demo/debate live runs

FIX 4 — /demo/debate page hero was pre-Triad
  H1 was "Three AI experts critique your input — in parallel". Replaced
  with "Triad Review · live debate cockpit" + subtitle that names
  Counsel/Risk/Evidence and links to /demo/judge for the seeded path.

FIX 5 — hackathon assets used pre-Triad framing
  - output/hackathon/pitch-deck.marp.md: hero slide retitled "XIO
    Compliance Brain · Triad Review Engine"; "what we built" slide
    rewritten with reviewer roles, citation badges, CRUMB receipts,
    export gate; "demo time" slide leads with /demo/judge.
  - output/hackathon/post-1-the-diff.md: Post #1 retitled "Triad
    Review on AMD" with /demo/judge as the live URL; Post #2 retitled
    "Why three reviewers, not one" with the dangerous-one-answer
    thesis; Post #3 expanded with citation-badge / output-hash /
    export-gate concrete claims.
  - output/hackathon/pitch-deck.marp.pdf re-rendered (198 KB, was
    190 KB).

Verified live (local Next + live AMD droplet):
  - GET /api/demo/handoff → 200 with full Triad CRUMB content
  - /demo HTML contains "Triad Review" + "/demo/judge"; ZERO
    occurrences of "three-voice debate" or "Side by side"
  - /demo/debate HTML contains "Triad Review · live debate" +
    "Counsel, Risk, and Evidence"; ZERO occurrences of "Three AI
    experts critique your input"
  - 363 tests + 1 skipped AMD live smoke. Lint clean. Typecheck
    clean across 9 packages.

Co-Authored-By: Claude Opus 4.7 <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

const topK = body.topK ?? DEFAULT_TOP_K;

P1 Badge Clamp and validate topK before retrieval

topK is taken directly from the request body and then used both in store.retrieve({ topK, ... }) and in slice(0, topK * 2) without any bounds/type checks. Because this route allows anonymous callers (session falls back to anonymous), a client can send very large values (or non-integer values) to force oversized retrieval/prompt assembly, which can materially increase latency/cost and trigger model-context failures instead of returning a controlled 400/default behavior.

ℹ️ 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".

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