Skip to content

Dev Day: non-coder follow-along + A2A governed quorum ("policy beats consensus") - #25

Merged
manavgup merged 62 commits into
mainfrom
dev-day-non-coder-followalong
Jun 25, 2026
Merged

Dev Day: non-coder follow-along + A2A governed quorum ("policy beats consensus")#25
manavgup merged 62 commits into
mainfrom
dev-day-non-coder-followalong

Conversation

@manavgup

Copy link
Copy Markdown
Owner

Brings the Dev Day demo work from `dev-day-non-coder-followalong` into `main`.

What's included

  • A2A governed quorum (merged via A2A quorum demo — "policy beats consensus" #24): hybrid "policy beats consensus" — local crowd vote + governed A2A voters (chair orchestrator + room voters), private notes served over a corpus MCP server, OPA two-tier policy (quorum majority can APPROVE up to a $100k ceiling; gateway blocks the wire above policy).
  • Non-coder follow-along: companion dashboard scenarios, crowd voting / freeze / quorum card, present + money-shot scripts.

Provenance

Squashes the full follow-along branch history (incl. PR #24, already merged into this branch). All CI checks were green on #24 at merge time.

🤖 Generated with Claude Code

manavgup and others added 30 commits June 22, 2026 11:21
…cklist

Last live run: liked it but hard to follow. New bar = attendees who've
never coded and don't know GitHub. build.html defaults to the Codespaces
lane and surfaces UUIDs/tokens/SSE/make connect — unfollowable for them.

- docs/follow.html: new watch-along page. Sticky tap-along "YOU ARE HERE"
  stage tracker (auto-advances on scroll), every stage in plain English,
  the 4 controls as human-language cards. Optional collapsed "try it
  yourself" brave lane (install Bob, paste the one make-connect line,
  type bob, 2 safe prompts). build.html stays the deeper coder version.
- .github/workflows/pages.yml: publish docs/ to GitHub Pages so the
  follow-along gets a stable URL for a slide QR.
- docs/assets/follow-qr.png: QR to the published follow.html.
- docs/dev-day-runsheet.md: pin a "Fire up ContextForge from Codespaces"
  checklist at the top + an audience-follow-along note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A tap now wins over scroll-sync for ~1.1s while it animates to the
section (the tapped stage stayed lit instead of snapping back), and
reaching the page bottom pins the last short stage (Proof) as current.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Watch-only had no on-ramp to actually doing it. Replace the minimal
brave-lane card with a 3-way chooser: Just watch (default) / Try it —
Codespaces + Bob / Full local. The two hands-on lanes deep-link into the
existing build.html cards, preselected via ?mode=. Honest note that
hands-on needs a laptop + Bob (phones are watch-only).

build.html now honors ?mode=codespaces|local|watch so the chooser lands
visitors on the right lane. Dropped the now-unused tap-to-copy handler.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reframe the chooser into 3 tiers by how much is on the attendee machine:
  T1 Follow along + run it  — browser only (phone/laptop), no install:
     watch here + run scenarios on the shared Companion dashboard
  T2 Drive Bob yourself     — laptop + Bob, connect to cloud control plane
  T3 Do it all yourself     — Docker + Bob, make quickstart

T1 carries a "Run it live" button wired from ?dash=<companion-url> (http(s)
only, to block javascript: injection); falls back to "presenter will share
the link" when absent. New `make follow-link` (scripts/follow-link.sh) prints
the follow.html?dash=... share link, auto-detecting Codespaces (port 7070)
like make connect. Companion gets a phones media-query so Tier-1 mobile users
get a stacked, full-width dashboard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- runsheet: refresh the audience section to the 3 tiers; add a "Where the
  single control plane runs" ASCII diagram + how each tier connects + the
  shared-state/scale caveats.
- build.html: new "Where it runs — how the whole room connects" collapsible
  (diagram + tier→port table + caveat) beside the existing Architecture one.
- follow.html: optional "🗺️ The big picture" collapsible showing the existing
  architecture.svg with a plain-English caption + tap-to-zoom (collapsed so it
  never overwhelms the non-coder watch flow).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Companion: POST /api/register-agent (real register_mcp_server -> salestax-<INITIALS>,
dedup name + retry), GET /api/agents (count + recent initials), GET /wall (projector
view). Dashboard gets a register bar + live "agents built by the room" counter (polls
3s). AGENT_BACKEND_URL configurable.

make agents-reset (scripts/agents-reset.sh) clears salestax-*; folded into demo-reset.
build.html Stage 2 + dev-day-runsheet: name-with-initials participation prompt.

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

ContextForge enforces a unique url per gateway, so N agents could not share one
backend url verbatim (409 Gateway already exists). Carry the unique name as a query
suffix; the backend ignores it and serves /mcp normally. Verified: MG -> salestax-MG,
second MG -> salestax-MG-2 (dedup), count climbs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Bob-lane register prompt pointed at the bare sales-tax url, so a 2nd Bob
driver would 409 on ContextForge unique-url. Add the ?agent=<initials> suffix
(matches the phone path) and note the Operator persona + shared-vs-own gateway.
Verified: controlplane-register-mcp-server with the suffixed url -> registered:true,
count climbs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The chooser's 3 cards now each open a dedicated page with only that
tier's steps, instead of the shared mode-switching build.html that
attendees found confusing:

- path-run.html  (Tier 1, everyone)   — open dashboard, register, run scenarios
- path-bob.html  (Tier 2, intermediate) — install Bob, connect, drive the controls
- path-diy.html  (Tier 3, advanced)   — full local stack, build + govern end to end

Shared assets/path.css + assets/path.js (copy buttons, ?dash= forwarding).
follow.html cards repointed; ?dash= now forwards to path-run.html.

Verified against the live stack and captured in
evidence/followalong-proof.html: 12 concurrent registrations (0->12,
dedup MG/-2/-3, truncation, case-fold), 6/6 scenarios governed
(ALLOWED/BLOCKED/REDACTED/NEUTRALIZED/EXECUTED), dashboard + wall shots.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The connect line carries a ~470-char bearer — retyping it off a slide is
hopeless and error-prone. New opt-in /connect surface lets laptop
attendees copy or download it instead:

- GET /connect       — dark page: one-click Copy (Analyst/Operator),
                       Download settings.json, a fixed curl one-liner,
                       and the 3 proven drive prompts
- GET /api/connect   — JSON {base, analyst, operator, oneliner, prompts}
- GET /bob/settings.json[?persona=] — ready .bob config to curl into place
- Dashboard gains a "Connect Bob (laptop)" link

Gated behind EXPOSE_CONNECT=1 (a.k.a. make companion-connect) because it
reveals the token to the browser; off by default. Public gateway URL is
derived for Codespaces; copy uses the full command (verified 612 chars,
display dimmed). Aligned the drive prompts across path-bob.html and the
runsheet to the explicit make-connect wording. Evidence page updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A room needs BOTH ports public: 4444 (gateway) and 7070 (Companion) —
phones hit 7070 and Tier-2 laptops pull their connect config from
7070/bob/settings.json. Checklist now starts the companion with the
connect page on (make companion-connect) and flips both ports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Room registration (phones + Tier-2 Bob Operator) points at
http://sales-tax:8000/mcp, but the lite `make up` stack doesn't include
sales-tax — so every registration 422s with SSRF_DNS_FAIL_CLOSED.

- post-create.sh now scaffolds + runs sales-tax (make stage1-scaffold &&
  make salestax-up), non-fatal on a Docker Hub pull limit.
- Runsheet checklist adds the step (for reopened Codespaces) + a verify
  one-liner and the 422 explanation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registration points at http://sales-tax:8000/mcp, which the lite stack
omits — so attendees hit 422 (SSRF_DNS_FAIL_CLOSED) unless someone
remembered to run salestax-up first. Now `make companion` depends on a
new idempotent `salestax-ensure` (scaffolds + starts sales-tax only if
not already running), so `make companion-connect` is genuinely the one
presenter command. Runsheet collapsed back to a single companion step
with the 422 as a troubleshooting note, not a required step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes the GitHub Pages dependency for the live demo — the whole
attendee experience now runs on one public port:

- companion serves docs/ (follow.html, path-*.html, assets, diagrams);
  bounded to static extensions, source files 404.
- GET /qr  — full-screen projectable QR page (scan to join)
- GET /qr.png — the QR image, generated server-side (qrcode[pil])
  encoding <companion>/follow.html?dash=<companion>
- make companion adds qrcode[pil]; prints the /qr URL
- make follow-link defaults to the self-hosted follow.html (no Pages)
  and surfaces the /qr URL
- make ports-public — one command to set 4444 + 7070 Public via gh
  (instead of clicking the PORTS tab)
- runsheet checklist updated for both

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Dashboard roombar now has a prominent blue "📲 Join QR" link → /qr,
  so the presenter shows the room QR straight from the home page (no
  separate make follow-link / URL hunting).
- make companion-connect now runs ports-public first, so the single
  command makes 4444+7070 Public, ensures sales-tax, and serves the
  dashboard + connect page + QR. Falls back to a PORTS-tab hint if gh
  lacks the codespace scope (non-fatal).
- runsheet collapsed to: make companion-connect → click 📲 Join QR.

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

From pre-merge code review:
- register_agent: only retry on a name/url collision (409 / "already
  exists"); fail fast on other errors. A down sales-tax backend returned
  422 SSRF_DNS_FAIL_CLOSED and the loop hammered it 10× (~5min hang) —
  now returns instantly with "run make salestax-up". Verified 0s.
- /api/agents: sanitize recent chips to alnum in _initials_of (names can
  be registered directly via Operator Bob and land on the projector /wall).
- post-create.sh: use idempotent `make salestax-ensure` (gates scaffold on
  file-absence so it won't clobber a real server.py) instead of an
  unconditional stage1-scaffold; DRYs the duplicate logic.
- connect.sh: add the missing rcpt_injection drive prompt so `make connect`
  prints all three (the runsheet + evidence page claim "these match").
- build.html / path-diy.html: align Stage-3 drive prompts to the canonical
  verbose wording (Acme LLC, "use the finbyte-gateway tools").
- evidence page: drop the stale "enable GitHub Pages / merge PR" note
  (it's served from :7070 now).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the Codex redundancy review (two P1s):

- Drive prompts now have ONE source: docs/assets/prompts.json. The two
  runtime surfaces read it directly — companion/app.py (_load_drive_prompts
  + new public /api/prompts) and scripts/connect.sh (python loader, literal
  fallback). Static docs still embed copies for offline/copy-paste; new
  `make check-prompts` (scripts/check-prompts.py) fails if any surface drifts
  from the canonical set. Verified: guard passes now, catches an injected
  sentinel.
- _companion_base() now honors COMPANION_URL / COMPANION_PUBLIC_URL, matching
  scripts/follow-link.sh. Fixes the divergence where /qr + /bob/settings.json
  pointed at localhost/Codespace while the shared link used the override.
  Verified: COMPANION_URL=… → /qr encodes that host.

Deferred (Codex P2, post-talk): Flask templates extraction, shared site.css,
data-driven path-page generator — higher churn, no demo-day benefit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GitHub Codespaces forwarded ports return 404 to anonymous clients (a
phone with no GitHub login), so the companion-connect/Codespaces-ports
path can't serve a real room — only logged-in browsers. `make present`
(scripts/present.sh) fixes this:

- installs cloudflared if missing, opens public quick-tunnels for the
  Companion (:7070) and gateway (:4444)
- runs the Companion pointed at those tunnel URLs (COMPANION_URL /
  GATEWAY_PUBLIC_URL), so the QR + /connect line are publicly reachable
- prints a banner (QR / dashboard / gateway URLs) and opens the
  presenter's browser to the join QR
- Ctrl-C tears down the companion + both tunnels (trap kills by pid)

Quick-tunnel URLs are random per run; the QR is generated live so nothing
is hardcoded. Verified locally: tunnels up, companion serves tunnel URLs,
anonymous external fetch returns the dashboard JSON. Runsheet updated to
make `make present` the room command (companion-connect demoted to the
same-network variant).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- add a "PRESENT TO A ROOM" help group listing `make present` (the room
  command) — it was missing from `make` help entirely
- remove the duplicate `connect` line (it was printed under both the
  CODESPACES lane and DRIVE BOB groups)
- present.sh kills a stale Companion on :7070 before starting, so a
  re-run doesn't fail with "address already in use"

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- remove the "📸 Static evidence gallery" header link and the /proof +
  /screenshots routes that served it (not needed for the demo)
- add a "🛡️ Agentic AI Control Plane →" header link that opens the
  ContextForge admin at <gateway>/admin/login with the email pre-filled
  (?email=, which the gateway's login form honors) and the password shown
  to paste — throwaway demo creds (PLATFORM_ADMIN_* in .env). One click to
  the catalog where attendees can see their registered salestax-<INI> server.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Attendees create real, governed A2A agents (name + voting stance) that land
in ContextForge's own /a2a catalog — no external registry. A quorum scenario
fans an expense to every room agent via the gateway (votes audited), then OPA
blocks the $12k wire regardless of the tally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8 tasks, TDD where it applies (pure vote logic), grounded in the real
codebase: a2a-sdk room backend mirroring auditor, Companion /a2a registration
+ stance, gateway-mediated fan-out, and a headless quorum money-shot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Crowd votes locally (instant, no catalog writes); 5 fixed A2A voters seeded
once provide the governed proof; OPA blocks the $50k wire regardless. Adds a
gating ContextForge spike (Task 0), fixes the as_completed timeout crash with
wait(), and adds a presenter freeze + room cap. Removes per-phone catalog
tools, materialization races, and public-tunnel spam.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live spike against the seeded mesh: shared-backend ?agent= yields unique
callable a2a-* tools (<2s), the gateway bridges a text message to a Python
a2a-sdk agent (auditor completed + returned an artifact), and delete removes
the tool. Build is cleared. Artifact text is nested in result.content[0].text
so the json.dumps(resp)+grep approach in _vote_one is correct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
manavgup and others added 29 commits June 24, 2026 14:13
…hboard

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Leftover from the removed per-attendee register path (flagged in review).
Unused; deleting avoids the false impression that AGENT_PREFIX name-gen is live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final-review should-fixes for a public room:
- /api/freeze now requires PRESENTER_KEY when set (open when unset for local).
  Attendees with the shared QR dashboard URL get 403; presenter opens /?k=<key>.
- Remove the dead "register an MCP server named salestax-..." line from the
  Connect page (the register path was replaced by local crowd voting).
- Wall tab title + footer copy now reflect voting, not agent registration.

Verified live: attendee freeze->403, presenter freeze->ok, vote-when-frozen->423,
connect page salestax matches=0, quorum still BLOCKED, no-key local still open.

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Evidence page (docs/evidence/a2a-quorum/index.html) with 6 screenshots from a
live run, including the chair-agent orchestrated quorum (real agent->agent A2A)
and a 'how to test it' guide.

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

Re-adds /api/register-mcp (salestax-<initials> MCP servers → ContextForge),
removed when PR #24 repurposed the old endpoint for crowd voting. Adds a header
link to the a2a-inspector. Voting + quorum untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scales the quorum to a roomful of attendee voter agents; each call still
abstains on error so gather never raises.

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

Consolidates participation: each attendee taps Join (initials + stance) and gets
a room-<stance>-<initials> A2A voter (joins the chair-orchestrated quorum) AND a
salestax-<initials> MCP server (joins the catalog). Caps + presenter freeze.
Drops the now-empty crowd line from the quorum scenarios.
The gateway lowercases derived tool names, so an attendee agent room-strict-MG
creates tool a2a-room-strict-mg. The chair + companion built a2a-{name} with the
uppercase initials -> 'tool not found' -> abstain. Lowercase to match. Fixed
voters (no uppercase) were unaffected, which masked it until attendees joined.

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d votes per the rule

The voter fetches the owner's private note via the gateway corpus-get-corpus tool
(PII-redacted, audited) and votes by its rule (dollar cap / keyword); falls back to
stance when there is no note or the read fails. MCP now feeds the A2A quorum.
…ance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The chair/companion prompt ends '... agent=room-strict-MG.' so parse_owner
captured 'MG.' and the corpus lookup missed -> notes silently ignored (stance
fallback). Strip non-alnum; add a period-terminated regression test. Caught
live: a strict agent with an 'approve everything' note now correctly approves.
The chair now carries each voter's reason ([[name|vote|reason]]) and the card
renders it, so 'strict-AB -> approve' is explained by 'owner's note says approve'.
Also fix parse_threshold to prefer a $-amount: a PII-masked card '...-1111' was
being read as a $1,111 cap. Caught by surfacing the reason on the card.
…iling

The OPA policy now has two tiers: wires in [$10k, $100k) need approval, which a
governed quorum MAJORITY supplies (the chair/companion derive approval=true from
the tally); wires >= $100k are denied regardless — not even a unanimous quorum or
dual approval can authorize them (policy beats consensus). Adds a $150k ceiling
scenario + money shots. verify-controls 22/0.
…docs to two-tier

- companion/app.py: remove extraneous f-prefix (ruff F541) -> 'make lint' green
- room/agent_executor.py: drop unused 'import json'
- policy-docs MCP server: describe the two-tier policy (the $100k hard ceiling +
  the quorum-majority approval) so the documented policy matches the enforced Rego
A2A quorum demo — "policy beats consensus"
…lowalong

# Conflicts:
#	Makefile
#	companion/app.py
@manavgup
manavgup merged commit 7cfbc6d into main Jun 25, 2026
5 checks passed
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