Vol. I  ·  No. 145 Established 2026  ·  AI-Generated Daily Free to Read  ·  Free to Print

The Trilogy Times

All the news that's fit to generate  —  AI • Business • Innovation
MONDAY, MAY 25, 2026 Powered by Anthropic Claude  ·  Published on Klair Trilogy International © 2026
🖶 Download PDF 🖿 Print 📰 All Editions
Today's Edition

Korn Ferry Swallows Trilogy International — and Inherits a Staffing Empire It Now Must Justify

The executive search giant absorbs a global recruitment network, raising questions about consolidation in the talent industry.

LOS ANGELES — The deal was quiet by the standards of a firm that places C-suites at Fortune 500 companies. But Korn Ferry's acquisition of Trilogy International lands with real weight in the staffing industry — a sector where scale is oxygen and the margin game is merciless.

Korn Ferry, the Los Angeles-based executive search and organizational consulting firm, has taken ownership of Trilogy International, a staffing and workforce solutions network with reach across multiple geographies. The financial terms were not disclosed. The acquisition arrives as Korn Ferry has also moved to shore up its own internal architecture, appointing a new Chief People and Legal Officer — a signal that the firm is not merely buying market share but attempting to integrate what it buys.

The staffing industry has spent the better part of three years consolidating. Remote work dissolved geography as a hiring constraint, then reimposed it in subtler ways — compliance, time zones, tax jurisdictions. The firms that survived the post-pandemic correction were the ones that could operate across borders without losing margin to friction. Trilogy International, with its cross-border recruitment infrastructure, fits that thesis.

For Korn Ferry, the acquisition extends a strategic arc away from pure executive search and toward something closer to a full-stack talent services model. It is a bet that clients want fewer vendors, not more — that the firm which places the CEO can also staff the teams below.

The deal is worth watching for anyone in the talent ecosystem. Korn Ferry now commands a larger footprint in mid-market and professional staffing, territory it has historically left to specialists. Whether it can run that business with the discipline of a firm built on high-touch, high-margin search work is the open question.

The geography of ambition, in this industry, is always expanding. The harder work is holding the ground you've already taken.

Key appointments: Trilogy Hotels, Marriott International - h  ·  Korn Ferry Appoints Chief People and Legal Officer - Hunt Sc  ·  Korn Ferry is new owner of Trilogy International - Staffing

Staff Breaks Ranks in Pentagon Brawl

Google and OpenAI workers back rival Anthropic against the brass — even as Mountain View loses the coding war

SAN FRANCISCO — Hundreds of Google and OpenAI workers signed papers this week backing rival Anthropic in a Pentagon court fight — even as their bosses chase the same outfit for the AI coding crown.

The amicus brief, joined by staffers from companies that compete with Anthropic by day, asks federal courts to set rules on military AI before the generals write their own. Loyalty stops at the courthouse door.

The brief lands in an odd hour for the industry. Anthropic and OpenAI snipe at each other over safety and talent. Google bleeds engineers and morale.

Mountain View has lost the coding war. Anthropic's Claude eats the developer market by the week. OpenAI's models bite from the other side, while the Los Angeles Times reports Google's Gemini holds the rear.

Insiders tell the Times the trouble runs deep. Committee fights slow shipping. Engineers leak that the brass has lost the room.

Then Axios twists the knife — by saying Google might yet win. The piece pegs Mountain View as the dark horse if Anthropic and OpenAI bleed each other dry first. Cash, chips, and the search monopoly buy plenty of time.

The coding crown matters plenty. Every developer hooked on Claude or ChatGPT becomes a customer for a decade. Lose the coders and lose the next ten years of enterprise software.

The Pentagon case turns on what Anthropic's machines can — and cannot — do in uniform. A Wired account describes workers wanting courts to draw the line before contracts get signed. They want it in writing now.

The brass wants AI for war planning, logistics, and intelligence. Anthropic's models could do most of it. The fight is over guardrails — what the machines will refuse when the uniform asks.

OpenAI staff signed. Google staff signed. Some signers work for outfits pitching the Pentagon on their own deals.

Anthropic's lawyers welcomed the cavalry. The Justice Department declined to wave the white flag. The fight heads to the bench next month.

Trilogy's portfolio watches close. ESW Capital's roster — Aurea, IgniteTech, Skyvera, CloudFix — buys AI plumbing wholesale. Every model becomes a commodity faster than the last.

The whole industry just got a lesson in worker solidarity. Front-line coders care more about rules of the road than the company logo on their checks. The executives just learned the staff has a mind of its own.

For Google, the long game still looks rocky. Solidarity from rival rank-and-file won't help if product drags. The race goes to the swift — and Mountain View ain't sprinting yet.

Google’s internal struggle is handing the AI coding race to  ·  OpenAI, Anthropic feud could prop up Google - Axios  ·  OpenAI and Google Workers File Amicus Brief in Support of An
Haiku of the Day  ·  Claude HaikuEmpires swallowed whole
Yet nothing settles—just shifts
Power finds new hands
The New Yorker Style  ·  Art Desk
The New Yorker Style  ·  Art Desk
The Far Side Style  ·  Art Desk
The Far Side Style  ·  Art Desk
News in Brief
Antitrust Enforcement Horizon for Tech Sector Remains Legally Uncertain as Regulatory Debate Intensifies
WASHINGTON, D.C.
The Fairness Reckoning: AI's Bias Problem Reaches Critical Mass Across Education, Hiring, and Policing
CAMBRIDGE, MASSACHUSETTS — It could be argued — and preliminary evidence now suggests with considerable force — that the question of algorithmic fairness has transitioned, epistemologically speaking, from a speculative concern at the margins of machine learning discourse to what one might characterize as a full-spectrum legitimacy crisis, manifesting simultaneously across the three most consequential domains of civic life: education, employment, and law enforcement. The thesis, as articulated across a remarkable confluence of recent scholarly and practitioner literature, is as follows: AI systems trained on historically inequitable data do not merely reflect prior injustice — they institutionalize it, launder it through the apparent objectivity of computation, and render it structurally resistant to contestation.
The AI Jobs Panic Is Missing the Real Layoff: Mediocrity
CHICAGO — I'll be honest: the AI jobs conversation has officially entered its spreadsheet-and-sweatpants era. Every week brings another forecast, another survey, another executive panel telling workers that disruption is coming, but also that it will be fine, provided everyone reskills, upskills, cross-skills, self-skills and preferably does it before lunch.
We Are All Being Watched, and We Have Run Out of Places to Hide
WASHINGTON, D.C.
Your AI Agent Is Not Your Friend: A Love Letter to Chaos and Accountability
AUSTIN, TEXAS — Let me tell you about the moment I realized we'd all lost our minds.
A Trilogy Company
Crossover
The world's top 1% remote talent, rigorously tested and ready to ship.
A Trilogy Company
Alpha School
AI-powered learning. Two hours a day. Academic results that defy belief.
A Trilogy Company
Skyvera
Next-generation telecom software — built for the networks of tomorrow.
A Trilogy Company
Klair
Your AI-first operating system. Every workflow. Every team. One platform.
A Trilogy Company
Trilogy
We buy good software businesses and turn them into great ones — with AI.
The Builder Desk  —  AI Builder Team
📅 Week in ReviewProduction Release

Builder Team Ships Across Six Repos in a Week That Rewired the Engine

From QuickBooks expense coverage jumping 9% to 99.5% to a brand-new TrueFoundry AI spend dashboard, the Builder Team didn't just ship features this week — they changed what the product knows about money.

There are weeks where a team ships, and there are weeks where a team transforms. This was the second kind.

The biggest story of the week lived inside the QuickBooks pipeline, and it belongs to @sanketghia. Seven days ago, the `quickbooks-ap-sync` pipeline was capturing roughly nine cents of every dollar of expense activity on the alpha entity. By Friday, it was capturing ninety-nine and a half. That is not an incremental improvement. That is a product waking up. Phase 1 pulled in JournalEntry lines — the ~65% slice of alpha's P&L that had been invisible at transaction level despite reconciling correctly in monthly aggregates. Phase 2 added the Purchase entity: direct credit-card, check, and cash spend that never touches the AP cycle. Then PR #91 delivered the value-realization step, extending `fct_expense` to UNION all four staging sources so dashboards actually see the $240M in cumulative GL postings that the prior phases unlocked. Along the way, a Lambda OOM at 512 MB got resolved by bumping memory to 4096 — a cold-blooded fix after CloudWatch caught the crash on the very first post-deploy invocation. Sanket ran all of it. The QuickBooks campaign is the week's signature achievement.

While that pipeline story was unfolding in Surtr, @kevalshahtrilogy was building the intelligence layer on top of it. The TrueFoundry AI spend dashboard (PR #2856) went live in Klair this week — a brand-new top-level page surfacing gateway spend, Max20x savings, list-versus-negotiated deltas, and per-user footprint, timed to the 2026-05-26 budget cycle. Keval didn't stop there. He shipped three new MCP tools — `query_truefoundry_spend`, `query_claude_ai_spend`, and `query_max20x_savings` — so the AI assistant can now answer spend questions directly. The Claude Enterprise per-user usage pipeline and the TrueFoundry gateway ingest both landed in Surtr as well. That is a full vertical: raw data in, pipeline through, dashboard out, AI query on top. One engineer. One week.

Over in the Monthly Financial Reporting feature, @eric-tril had what can only be described as a volume week. The budget-column drill-down that shipped earlier in the cycle got extended and corrected — first gaining a full `type → business_unit → account_name` breakdown after Finance asked for BU instead of department as the audit dimension, then receiving a companion fix that properly serves budget values from Redshift. Eric also landed the EBITDA Memo Data tab with cell-level audit drilldown, wired Cash Flow Net Income from EBITDA Reconciliation, added the Passive Investments P&L Schedule tab, and reorganized fifty backend test files into a clean `tests/mfr/` tree before repairing nine latent test failures that had been hiding in the old layout. The MFR surface area grew meaningfully this week, and it grew with discipline.

In Aerie and Rhodes, @benji-bizzell was the connective tissue. The Operating Dashboard got Rhodes site detail, Quality Bar summaries, work units, tasks, and notes surfaced in the side panel. A campus map view landed. Cached AI quality summaries shipped. The admissions forecast gained an editable mapping config so operators can align HubSpot programs to Rhodes slugs without a redeploy. And a persistent due diligence sync bug — where clearing a DD field in Aerie updated REBL3 but left Rhodes holding the old value — got properly resolved across both repos. @ashwanth1109 added a dedicated `financials` viewer role to the RBAC system, meaning finance and leadership stakeholders can now be granted dashboard access without admin privileges. That is a meaningful permission model change.

And then there is marcusdAIy. The Review Agent's C3 benchmark suite continued its march, with per-product checks for Support, Edge, Hard COGS, G&A, and Sales & Marketing all shipping across the week. The `generate_mips` generator was rewritten LLM-first under the B9 contract, joining five sibling generators that landed in PR #2851. The GDoc sync now promotes bold paragraphs to heading level 2 on import so GM-authored board docs stop silently dropping overview content. The finding-status auto-resolve wired Claire's Accept handler so regenerated sections flip their scorecard findings to addressed. And over in the new `trilogy-drones` repo, a v0.5 polish bundle landed with cost-tunable model selection, pulling drone runs from a $42 ceiling down toward a $10-12 target.

Asked about the week's output, marcusdAIy offered his usual measured self-assessment: "The B9 LLM-first rewrite alone touched six generators, the C3 benchmark suite is now eight checks deep, and the drone cost work saves real money on every run — but sure, Mac, keep counting my PRs like they're parking tickets."

Sure, Marcus. Eight checks deep. We'll put that on the trophy.

With the QuickBooks coverage story essentially closed, the TrueFoundry spend layer live, the MFR drill-down hardened, and a brand-new Praxis-V2 repo on the board, next week sets up as the week this team starts building on top of everything they just finished.

Mac's Picks — Key PRs This Week  (click to expand)
#88 — feat(quickbooks-ap-sync): capture JournalEntry lines + fix money precision @sanketghia  no labels

Linear: [SURTR-23](https://linear.app/builder-team/issue/SURTR-23/capture-quickbooks-journalentry-lines-fix-money-precision)

## Why

The quickbooks-ap-sync pipeline currently captures only Bills, VendorCredits, and BillPayments at line level — that's ~9% of P&L expense activity on the alpha entity (Q1 2026, $1.27M of $14.28M). The other ~91% is invisible at transaction level despite the monthly aggregate (quickbooks_pl_monthly) reconciling correctly with QuickBooks.

GeneralLedger analysis across alpha, miami, and alpha_schools_llc showed the entity-type split:

| TxnType | alpha % of P&L | miami % of P&L | alpha_schools_llc % of P&L |

|---|---|---|---|

| Journal Entry | 65.3% | 8.0% | 18.4% |

| Expense (Purchase) | 24.8% | 5.9% | 12.5% |

| Bill | 8.7% | 84.5% | 67.4% |

For alpha specifically, accounts 60211/60212/60220/60250/60200 (Contracted Labor — Coaches) and 69100 (Central Factory Recharges) are 100% Journal Entries — $7M+/quarter of XO contractor payroll postings that are completely invisible to current Bills-only capture.

Adding JournalEntry capture takes overall P&L coverage from 9% → 75%. Adding Purchase (the follow-up Phase 2) gets to 99.5%.

## What this PR delivers (Phase 1)

Two logically separable changes bundled in one PR for review efficiency.

### Phase 0 — money-precision fix (prerequisite)

- New src/money.py helper: parse_money(value) -> Decimal with HALF_UP rounding at 4dp.

- Wired through every existing money field in the 3 transformers (Bills / AP / BillPayments).

- New JSON encoder in redshift_handler.py emitting Decimals as JSON numbers (via float(), safe since values are pre-quantized).

Root cause this fixes: QB sometimes returns amounts with float-representation drift (e.g. 28.879999... for $28.88). The old code passed the raw float through json.dumps → S3 → COPY, where Redshift NUMERIC(18,2) truncates the excess precision instead of rounding. Result: bills booked at $28.88 in QB stored as $28.87. Verified against Cristy Cunningham HR Expense bills where the QB report total differed by exactly 6¢ from our Redshift sum across 7 affected lines.

No column migration needed. parse_money() already quantizes to 4dp via Decimal then serializes back to a clean 2dp-friendly JSON number (28.879999... → 28.88 exactly). Redshift COPY into the existing NUMERIC(18,2) columns receives the clean value and stores it without truncation. Widening columns to NUMERIC(18,4) was over-engineering; Redshift also doesn't support inline ALTER COLUMN TYPE for numeric here, so the add/update/drop/rename dance wouldn't be worth the disruption for a non-essential safety margin.

### Phase 1 — JournalEntry pipeline extension

- New query_journal_entries() in qb_client.py using the same paginated _query_qb path as Bills/VendorCredits/BillPayments.

- New _transform_to_je_records() in handler.py emitting one row per JE line (header repeated). Captures posting_type, debit_amount/credit_amount/net_amount, account refs, class/department refs, optional entity ref, and per-line description (which carries the contractor name in XO payroll JEs).

- Integration into the per-entity loop as a fourth table_writes step.

- TABLE_CONFIGS entry for quickbooks_journal_entries.

- ddl/create_quickbooks_journal_entries.sql — new table with NUMERIC(18,4) money columns (free since it's a fresh table), DISTKEY(company_id), SORTKEY(txn_date, account_id).

- 9 new transformer tests against real QB JE shapes (balanced 2-line, XO multi-credit payroll, adjustment, entity ref, money rounding, optional fields, empty lines, non-JE-detail skip, multi-JE concat).

- Updated 5 existing handler tests to mock the new fetch + assert the 4th delete_and_insert call.

### Tooling

- New run_local.py — Level 1 dry-run driver. Mirrors the quickbooks-pl-monthly/run_local.py pattern. Calls handler() with dry_run=True by default; fetches from real QB but skips Redshift writes. Sensible env defaults including ENVIRONMENT=prod. Live-validated across all 9 entities (see Test Plan).

## Out of scope (deliberate, follow-up tickets)

- Rename quickbooks-ap-syncquickbooks-transactions-sync (name no longer matches scope).

- Phase 2 — Capture Purchase entity (Cash/Check/CreditCard expenses; closes another ~25% of alpha P&L).

- Phase 3 — Capture Deposit / CreditCardCredit (last 0.5%).

- Downstream quickbooks-core-tables update to UNION the new JE staging table into fct_pl / fct_expense.

- Apply the same parse_money helper to quickbooks-pl-monthly and quickbooks-expense-sync (requires shared-code infrastructure since each pipeline bundles independently).

## Test plan

- [x] Unit tests: 85 pass (was 76; +9 JE + 17 money). ruff check clean, ruff format applied.

- [x] Level 1 — dry-run against real QB, all 9 entities (Q1 2026): 9/9 succeeded, 2,601 Bills + 27 VendorCredits + 2,371 BillPayments + 625 JournalEntries fetched with zero errors. Alpha alone returned 358 JE headers (~6,300 line-level rows after transformation), matching prior GL analysis.

  cd pipelines/runners/quickbooks-ap-sync

python3 run_local.py --quiet # miami, default

python3 run_local.py --quiet --company alpha # large entity, ~30s

- [ ] Level 2 — SQL deploy:

- Run pipelines/runners/quickbooks-ap-sync/ddl/create_quickbooks_journal_entries.sql once. Single CREATE TABLE IF NOT EXISTS — idempotent, no impact on existing tables.

- [ ] Level 3 — post-deploy smoke test (manual Lambda invoke):

  aws lambda invoke \

--function-name pipeline-quickbooks-ap-sync-prod \

--cli-binary-format raw-in-base64-out \

--payload '{"run_id":"post-deploy-smoke","params":{"company_ids":["miami"],"start_date":"2026-05-01","end_date":"2026-05-15"}}' \

/tmp/out.json

- [ ] Reconciliation — after a full alpha Q1 backfill (next daily run or wider-window manual invoke):

  SELECT SUM(debit_amount) - SUM(credit_amount) AS net

FROM staging_education.quickbooks_journal_entries

WHERE company_id = 'alpha'

AND account_id = '211'

AND txn_date BETWEEN '2026-01-01' AND '2026-03-31';

-- Expected: 2088610.84 (matches QB GL exactly)

## Risk + rollback

| Change | Reversible? | Notes |

|---|---|---|

| create_quickbooks_journal_entries.sql | Yes — DROP TABLE | Empty new table, zero impact on existing data |

| Lambda code | Yes — git revert + CDK redeploy | New table is the only schema change |

## Deploy ordering

SQL must land before the Lambda's first run against the new code path. Given the current ~20 hours until next 03:00 UTC scheduled run, the standard sequence is safe:

1. Merge PR → CD auto-deploys Lambda (deployed but unfired — don't manually invoke)

2. Run create_quickbooks_journal_entries.sql

3. Manual Lambda invoke for smoke test

4. Reconciliation query

5. Let the next scheduled run pick up wider backfill on its own (default 30-month look-back), or trigger manual backfill

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#90 — feat(quickbooks-ap-sync): capture Purchase entity (direct CC/Check/Cash spend) @sanketghia  no labels

Linear: SURTR-24 (Phase 2) — follows [SURTR-23 / PR #88](https://github.com/AI-Builder-Team/Surtr/pull/88) (Phase 1: JournalEntry capture)

## Why

Phase 2 of the QB pipeline extension. Phase 1 captured JournalEntries — ~65% of alpha's P&L expense activity. Phase 2 closes the next ~25% by capturing the Purchase entity: direct credit-card / check / cash spend that doesn't flow through the AP cycle (no Bill, no BillPayment).

Combined Phase 1 + Phase 2 takes the alpha operating entity from 9% → ~99.5% P&L expense coverage at line level. Facility entities reach ~99-100% with the same code path.

Live dry-run capture for Q1 2026 across all 9 entities:

| Entity | Purchase headers |

|---|---|

| alpha | 19,671 |

| alpha_schools_llc | 219 |

| miami | 42 |

| sports_academy_78734_llc | 36 |

| sports_academy_75010_llc | 24 |

| gtschool_78626_llc | 16 |

| esports_academy_llc | 12 |

| alpha_school_28269_llc | 12 |

| alpha_school_93101_llc | 10 |

| Total | 20,042 |

alpha's 19,671 is dominated by Ramp Card spend — the same pattern that produced the original HR-Expenses 6¢ rounding observation that kicked off this whole work.

## What this PR delivers

### New table: staging_education.quickbooks_purchases

One row per Purchase line. Captures both the funding side (payment_account_id = Ramp Card / bank / etc.) and the expense side (account_id = the GL expense account being debited). Money columns are NUMERIC(18,4), picking up parse_money's precision pipeline.

credit_flag (sourced from the Purchase header's Credit bool) flips net_amount sign for refunds, so SUM(net_amount) gives the correct P&L contribution.

### Code changes

- qb_client.py — new query_purchases() using the same paginated _query_qb path as Bills/JEs

- handler.py — new _transform_to_purchase_records() with the same Phase-1 validation discipline:

- PaymentType validated against {CreditCard, Check, Cash}; invalid → log+skip whole purchase

- Header AccountRef.value (funding) validated; missing → log+skip whole purchase

- Per-line AccountRef.value (expense) validated; missing → log+skip line, others survive

- _parse_line_amount shared helper for Amount parsing (consistent with Bills/JEs)

- enumerate fallback for line_id (PK safety)

- Non-expense DetailTypes logged before skip (schema-drift visibility)

- handler.py — integrated as the 5th table_writes step

- redshift_handler.pyTABLE_CONFIGS["quickbooks_purchases"] entry matching DDL column order

- ddl/create_quickbooks_purchases.sqlDISTKEY(company_id), SORTKEY(txn_date, account_id), strict PK on (company_id, transaction_id, line_id)

- run_local.py — surfaces Purchase counts in the summary + adds a reconciliation hint for alpha Q1 2026 grouped by payment_type

- Updated module docstring at top of handler.py — the pipeline now syncs 5 tables, not 3

### Tests

- test_purchase_transform.py — 13 new tests (simple CC purchase, Credit: true refund sign-flip, Check, multi-line, ItemBasedExpenseLineDetail parity, invalid PaymentType skip, missing header AccountRef skip, missing line AccountRef skip, line_id enumerate, missing Amount → 0, optional header fields, empty Line[] skip, unparseable Amount → 0)

- test_handler.py — 5 existing TestHandler tests updated for the new 5th delete_and_insert call + query_purchases mock

## Out of scope (deliberate follow-ups — likely next tickets)

- Rename quickbooks-ap-syncquickbooks-transactions-sync — the name is now actively misleading

- Phase 3Deposit / CreditCardCredit (covers the last 0.5%)

- Downstream quickbooks-core-tables UNION — makes Phase 1 + Phase 2 data visible to dashboards via fct_pl / fct_expense. This is the value-realization step.

- parse_money defensive replication to quickbooks-pl-monthly and quickbooks-expense-sync (needs shared-code infra)

## Test plan

- [x] Unit tests: 111 pass (was 98; +13 Purchase + 5 mock updates). ruff check clean, ruff format applied.

- [x] Level 1 — dry-run, all 9 entities, Q1 2026: 9/9 succeeded, 20,042 purchases captured with zero errors.

  cd pipelines/runners/quickbooks-ap-sync

python3 run_local.py --quiet --start 2026-01-01 --end 2026-03-31 \

--company alpha --company alpha_schools_llc --company alpha_school_28269_llc \

--company alpha_school_93101_llc --company esports_academy_llc --company gtschool_78626_llc \

--company miami --company sports_academy_75010_llc --company sports_academy_78734_llc

- [x] Level 2 — SQL deploy: create_quickbooks_purchases.sql ran cleanly in production via renewals-pipeline/scripts/run_ddl.py.

- [x] Level 3 — smoke test (alpha 2026-05-20..21, --no-dry-run): 55 purchases / 56 lines written. Real vendors (Delta, DoorDash, CitizenM, Mason Security, Alo Yoga). By payment_type: 54 CreditCard ($168,342.87) + 1 Cash ($9,539.53). All money cols 4dp. credit_flag captured. No errors. No skipped lines.

-- Verified Level 3 query:

SELECT payment_type, COUNT(*) AS lines, SUM(net_amount) AS net

FROM staging_education.quickbooks_purchases

WHERE company_id='alpha' AND txn_date BETWEEN '2026-05-20' AND '2026-05-21'

GROUP BY payment_type;

-- CreditCard 55 168342.87

-- Cash 1 9539.53

## Risk + rollback

| Change | Reversible? | Notes |

|---|---|---|

| create_quickbooks_purchases.sql | Yes — DROP TABLE staging_education.quickbooks_purchases | Empty new table at deploy, zero impact on existing data |

| Lambda code | Yes — git revert + CDK redeploy | Schema change is purely additive |

## Deploy ordering

Same as Phase 1: SQL must land before the Lambda's first run with the new code. SQL is already deployed (via the L2 step above). Once this PR merges and reaches production, CD redeploys the Lambda; the next scheduled run (or a manual invoke) starts populating quickbooks_purchases automatically via the default 30-month look-back.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#91 — Harden Rhodes note APIs and chat route against stale site IDs @YibinLongTrilogy  no labels

## Summary

Tightens the Rhodes note + chat surfaces against stale or non-site IDs.

The /api/chat route drops a redundant Clerk users.getUser call and

reuses a single Rhodes Convex client for both auth and chat context,

with cleaner 429/503 mapping for Clerk and rate-limit errors. The note

APIs (listNotes, listNotesPage, addNote) now accept either a

siteId or a siteSlug and normalize to a site ID inside Convex, so

agents passing a stale or non-site ID get a clear "resolve current

site" error instead of a Convex validator failure. MCP and Aerie tool

descriptions are updated to steer agents toward resolveSite first.

### Changes

- web/src/app/api/chat/route.ts — Drop the extra

clerkClient.users.getUser round-trip. Build the Rhodes Convex

client once and reuse the resolved actor for chat context. Map

Clerk auth/rate-limit/service errors to 401/429/503 responses with

consistent shapes.

- web/src/lib/rhodes-web-convex.ts — Expose the actor alongside

the client and surface typed error classes for the route to switch

on.

- convex/notes.tslistNotes, listNotesPage, and addNote

now take an optional siteSlug and normalize siteId server-side.

Stale or non-site IDs yield a "resolve current site" error rather

than a validator crash.

- convex/sites.ts — Helper used by notes to resolve a slug or

validate that a passed ID is actually a site.

- convex/lib/mutationAuthorization.tsaddNote authorization

resolves the site first, then checks canManageSchoolFields and

P1/P2 DRI access against the resolved site.

- mcp-server/tools/notes.ts — Tool descriptions and input

schemas updated to allow siteSlug and instruct agents to call

resolveSite first when IDs may be stale.

- lib/mcp-instructions.ts — Aerie note tool instructions updated

with the same guidance.

### Design Decisions

- Normalize site identity inside Convex, not at the call site.

Agents and the chat client both pass site identifiers from

different sources (slugs, resolved IDs, sometimes stale cached

IDs). Doing the resolve + validation in Convex gives one

authoritative error path and avoids duplicating the logic across

every caller.

- One Convex client per request. The previous flow created a

client for auth and another for chat context. Reusing the same

client (and resolved actor) removes a Clerk round-trip from the

hot path and keeps auth and tool calls consistent.

## Test Plan

- [x] Typecheck + lint pass locally.

- [ ] Hit /api/chat while signed out and confirm a clean 401.

- [ ] Trigger Clerk rate-limit / service errors and confirm 429/503

responses.

- [ ] Call addNote / listNotes with a siteSlug and confirm it

resolves correctly.

- [ ] Call addNote with a stale or non-site ID and confirm the

"resolve current site" error surfaces (not a Convex validator

error).

- [ ] Confirm P1/P2 DRI auth on addNote still works after the

resolve-first change.

#252 — AERIE-261 feat(user-management): add financials viewer role @ashwanth1109  no labels

## Demo

<img width="2624" height="1636" alt="image" src="https://github.com/user-attachments/assets/08d51abb-666c-42cf-9ee0-792348066c7b" />

## Summary

Adds a dedicated financials viewer role to the RBAC system so finance/leadership stakeholders can be granted access to Dashboards → Financials without being made full admins.

- New financials seeded role (only canViewFinancials: true)

- Extends canGrantRoles on superadmin and admin so the role is assignable from the existing admin UI

- One-shot patchFinancialsGrantCeiling internal mutation to bring already-seeded environments up to the new grant ceiling (since seedDefaults is insert-only)

## Linear

[AERIE-261 — Add financials role for granular Financials dashboard access](https://linear.app/builder-team/issue/AERIE-261/add-financials-role-for-granular-financials-dashboard-access)

## Spec

[features/user-management/rbac-admin/specs/05-add-financials-viewer-role/spec.md](features/user-management/rbac-admin/specs/05-add-financials-viewer-role/spec.md)

## Implementation summary

- chat/convex/roles.ts

- New role: appended financials to DEFAULT_ROLES with slug: "financials", name: "Financials Viewer", sortOrder: 2.5, isProtected: true, canGrantRoles: [], and every permission false except canViewFinancials: true. Placed between moderator (2) and user (3) so no existing sortOrder is renumbered.

- canGrantRoles extension: added "financials" to superadmin.canGrantRoles and admin.canGrantRoles, so listAssignableRoles surfaces the new role to both tiers automatically.

- patchFinancialsGrantCeiling internal mutation: idempotent, one-shot patch that brings superadmin and admin rows on already-seeded environments up to the new grant arrays. Missing slugs are reported, not thrown. Patterned on the existing patchCanViewFinancials mutation.

- JSDoc fix: corrected stale "four built-in roles" reference to "built-in" so the comment no longer drifts as roles are added.

- FEATURE.md + spec status updates: marked spec 05 Completed in the changelog and in the spec metadata; bumped Last Updated to 2026-05-22.

## Tests

- chat/convex/roles.test.ts70/70 passing. 7 stale fixtures updated for the 5-role baseline; 12 new tests added (4 covering the role shape, 8 covering the patch mutation).

- chat/convex/admin.test.ts — 3 fixture updates in the FR2: listAssignableRoles suite to reflect the new assignable role for superadmin / admin.

- All 7 CI checks green on run 26277232720: Build, Test, Typecheck, Lint+Boundaries, Secret Scan, Docker Chat, Docker Worker.

## Self-review

- 1 MINOR fix applied — stale JSDoc said "four built-in roles"; updated to "built-in" to avoid future drift.

- 2 MINOR skipped with rationale — both surfaced during self-review but were judged not worth landing in this PR (they are not regressions, just future cleanups).

## Out of scope

- Migrating existing users to the new role — no auto-migration. Admins assign Financials Viewer manually as the need arises. The existing assignRolesToExistingUsers mutation is intentionally untouched (it hard-codes the original three slugs).

## Manual verification needed before merge

- [ ] Confirm seedDefaults picks up the new role on next deploy (fresh row appears in roles with slug === "financials" and canViewFinancials: true)

- [ ] Confirm patchFinancialsGrantCeiling runs cleanly against the prod-equivalent env (one-shot; idempotent — safe to re-run)

- [ ] Spot-check the admin UI shows Financials Viewer as an assignable role for admins/superadmins (dropdown order: admin, moderator, financials, user)

## Rollout

After merging to main:

1. Wait for the auto-deploy to push the new Convex functions to your target environment.

2. Run the two commands below — from the chat/ directory, not the repo root (the convex dependency lives in chat/package.json, so npx convex only resolves there):

   cd chat

# 1. Seed the new role (idempotent — safe to re-run; { created: 1, skipped: 4 } the first time)

npx convex run roles:seedDefaults

# 2. One-shot patch to extend canGrantRoles on existing superadmin + admin rows

# (seedDefaults is insert-only, so it does NOT touch existing rows — this step is required

# or the new role will be invisible in the admin role-picker)

npx convex run roles:patchFinancialsGrantCeiling

For prod, append --prod:

   npx convex run roles:seedDefaults --prod

npx convex run roles:patchFinancialsGrantCeiling --prod

3. Sanity checks after running:

- seedDefaults should print { created: 1, skipped: 4 } on first run (zero on subsequent runs).

- patchFinancialsGrantCeiling should report { superadminUpdated: true, adminUpdated: true, missing: [] } on first run, then { superadminUpdated: false, adminUpdated: false, missing: [] } on re-runs.

- In the admin UI, opening any user's role picker should now list Financials Viewer for admins and superadmins (not for moderators).

4. Assign the role to whoever needs Financials access via /admin/users (the existing assignRole mutation handles the rest — no further code action needed).

### Common gotcha

If seedDefaults prints { created: 0, skipped: 4 }, the deployed Convex code is still on the old 4-role version — re-deploy the latest main (npx convex deploy from chat/, or wait for CI auto-deploy) and re-run.

#2853 — feat(mfr): budget-column drill-down for IS, EBITDA, and Cash Flow KLAIR-2764 @eric-tril  no labels

### Summary

Adds a budget-column drill-down to the Monthly Financial Reporting feature. Clicking a budget cell on the Income Statement, EBITDA Reconciliation, or Cash Flow tables (and the Education memo's vertical P&L tables) opens a side panel showing a (type → department → account_name) breakdown sourced from core_budgets.consolidated_budgets_and_actuals. The panel reuses the existing detail-panel shell, with a new 2-level nested accordion component (NestedAccordionGroup). The Cash Flow path is wired end-to-end but returns not_populated=true until upstream CF budget data is loaded — when it lands, the panel will start showing data with no client change.

### Business Value

Finance can now audit any budget figure in the monthly report by drilling from the displayed cell down to the underlying budget rows, instead of cross-referencing the source spreadsheet. This shortens variance-investigation cycles during month-end close and gives leadership a self-service way to interrogate plan composition (by type, department, and account) directly inside Klair.

### Changes

- Backend: new endpoints /income-statement-budget-detail, /ebitda-reconciliation-budget-detail, and /cash-flow-budget-detail returning BudgetDimensionDetailResponse.

- Backend: fetch_income_statement_budget_dimension_detail and fetch_ebitda_budget_dimension_detail services with sign-flip / bu_addback / category-BU-exclusion logic that mirrors the actuals detail path; CF dispatcher reuses the EBITDA and IS rollups (Net Income, D&A, Mgmt+Import) with CF sign convention.

- Backend: extended build_pnl_resolved_cte with data_source and bad_debt_routing knobs; budget queries also restrict to the latest budget_cycle_start per reporting period so panel totals reconcile to the displayed cell.

- Frontend: new [BudgetDimensionDetailPanel.tsx](vscode-webview://15qdonnjjcq9q3pcmufmg5fa0asnqc6qnceup60m8cm6igoedkcj/klair-client/src/features/monthly-financial-reporting/components/detail-panels/BudgetDimensionDetailPanel.tsx), [NestedAccordionGroup.tsx](vscode-webview://15qdonnjjcq9q3pcmufmg5fa0asnqc6qnceup60m8cm6igoedkcj/klair-client/src/features/monthly-financial-reporting/components/detail-panels/NestedAccordionGroup.tsx), and [useEducationVerticalBudgetDetailPanel.tsx](vscode-webview://15qdonnjjcq9q3pcmufmg5fa0asnqc6qnceup60m8cm6igoedkcj/klair-client/src/features/monthly-financial-reporting/hooks/useEducationVerticalBudgetDetailPanel.tsx) hook; wired into IS/EBITDA/CF detail-panel hooks and [EducationMemoView.tsx](vscode-webview://15qdonnjjcq9q3pcmufmg5fa0asnqc6qnceup60m8cm6igoedkcj/klair-client/src/features/monthly-financial-reporting/components/EducationMemoView.tsx) (vertical tables, col-index 4).

- Frontend: new API adapter methods fetchIncomeStatementBudgetDetail / fetchEBITDABudgetDetail / fetchCashFlowBudgetDetail and shared BudgetDimensionDetailResponse types.

- Tests: new unit suites for the three backend services (resolver, sign convention, budget-cycle filter, placeholder behavior) and for the new frontend components/hooks; existing useCashFlowDetailPanel / useEBITDADetailPanel specs updated for the budget-column routing.

- transformCashFlows: set dataKey on the Adjustments row so its budget cell dispatches correctly.

### Testing

- [ ] cd klair-api && pytest tests/mfr/ — backend unit suite (resolver, IS/EBITDA/CF budget detail).

- [ ] cd klair-client && pnpm test src/features/monthly-financial-reporting/ — frontend unit suite.

- [ ] cd klair-client && pnpm tsc --noEmit && pnpm lint:pr — type check + lint.

- [ ] Manual: open MFR for a recent period; click a budget cell on Group / Software / Education IS, EBITDA, and a CF row, and on Education vertical tables — confirm panel totals match the cell value and the CF placeholder note renders.

http://localhost:3001/monthly-financial-reporting

https://github.com/user-attachments/assets/12faed17-fb20-4d11-9569-1f1485b20571

#2856 — KLAIR-2766 feat(truefoundry): /truefoundry dashboard — gateway spend, Max20x savings, per-user footprint @sanketghia  no labels

## Summary

New top-level page in Klair surfacing AI spend through the TrueFoundry gateway plus Max20x savings, list-vs-negotiated savings, and per-user footprint. Built for the budget cycle starting 2026-05-26.

Linear: [KLAIR-2766](https://linear.app/builder-team/issue/KLAIR-2766/truefoundry-dashboard-truefoundry-ai-gateway-spend-max20x-savings-per)

Driven by Jamie Sidey's brief (transcript at TrueFoundry/Keval _ Jamie - TrueFoundry Transcript.txt) and the leadership PDF report at TrueFoundry/AI Spend & Savings.pdf. The dashboard surfaces three things budget owners need:

1. Where AI spend is happening — by BU, by user, by provider/model.

2. What we've already saved — via Max20x seat conversions and the 10% Anthropic negotiated discount.

3. What we can still save — concrete Max20x migration candidates ranked by projected monthly savings.

Real-data smoke shows 41 candidates totalling $104k/month opportunity, with central-engineering as the top BU.

## What's in this PR

### Backend (klair-api)

New router at /api/truefoundry/* with 7 GET endpoints:

- /summary — 4 KPI tiles + per-BU multi-bar (actual + realized via Max20x + potential)

- /spend-per-user — per-person spend across claude.ai + Max20x + metered API

- /gateway — daily by provider, provider×model breakdown, top subjects, token mix

- /max20x — monthly net savings + ranked migration candidates with token detail

- /negotiated — list vs actual savings by source + daily trend

- /claude-ai — kept for parity (not currently surfaced by the FE)

- /diagnostics — super-admin only: reconciliation, untagged virtual keys, TF provider key map

All read from existing core_finance.ai_spend_* tables and views — no pipeline changes required.

BU filter slug-normalizes display-form names ("Central Engineering""central-engineering") so the FE filter shell composes correctly with TF and the candidates view (both store BUs in slug form). 23 pytest tests cover filter behavior, null handling, slug normalization, and shape contracts.

### Frontend (klair-client)

- New /truefoundry route with permissionPath: 'ai-adoption'.

- Executive summary: 4 KPI tiles + by-BU multi-bar chart (actual / realized / potential, two bars per BU).

- Tabs: Spend per user (default) · Gateway detail · Max20x · Negotiated savings · Diagnostics (super-admin only).

- All 5 tabs prefetch on initial page load (parallel debounced fetches); tabs stay mounted across switches so navigation is instant after first load.

- Distinct chart palette so series are visually separable on the dark theme.

- Skeleton loaders sized to each tab's content.

- Max20x table: orange "set up now" pill on top 4 candidates, BU sub-label, quarterly savings column, cursor-following hover tooltip with 30-day token mix + top model.

- Spend-per-user table merges claude.ai chat (user_email + display_name) and TF subjects (bridged via ai_spend_subject_identity for VAT slugs) into one canonical user identity.

### Design artifacts (in this PR)

- docs/superpowers/specs/2026-05-22-truefoundry-dashboard-design.md — design spec

- docs/superpowers/plans/2026-05-22-truefoundry-dashboard.md — implementation plan

## Test plan

- [ ] cd klair-api && uv run pytest tests/truefoundry/ -v → 23/23 passing

- [ ] cd klair-api && uv run ruff format … && uv run ruff check … && uv run pyright services/truefoundry_service.py models/truefoundry_models.py routers/truefoundry_router.py → clean

- [ ] cd klair-client && pnpm tsc --noEmit && pnpm lint:pr → clean

- [ ] cd klair-client && pnpm vitest run src/screens/TrueFoundry/ → 5/5 passing

- [ ] Visit /truefoundry with a date range covering Apr 2026 onwards: hero chart, all 5 tabs render with real data, BU filter narrows results, hover tooltips position correctly with cursor.

- [ ] As super-admin, confirm Diagnostics tab shows the yellow "FR5 pending" reconciliation banner + untagged-subjects table + provider key map.

## Open follow-ups (tracked in KLAIR-2766, not blocking)

1. ai_spend_claude_ai_chat_usage.bu is universally NULL — BU filter skipped on claude.ai queries. Pipeline-side backfill needed in Surtr.

2. VAT-slug users render as Davidschwartz — populate ai_spend_subject_identity to surface display names from claude.ai chat.

3. Realized savings per BU is a proxy (Max20x usage value). A proper per-BU realized view from Surtr would replace it.

4. is_truefoundry_routed flag is missing on OpenAI / Cursor direct-API tables — required before a portfolio-wide cross-pipeline deduped widget.

5. flagged_setup_now is currently auto-top-4; can switch to an admin-curated list if needed.

## Screenshots

<img width="1874" height="876" alt="image" src="https://github.com/user-attachments/assets/ff2a7b35-b6d6-4cfe-9092-5aa81d120f35" />

<img width="1676" height="729" alt="image" src="https://github.com/user-attachments/assets/ad5387e5-fb28-4f33-823c-e2171b694adc" />

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#2858 — feat(claire): wire finding-status auto-resolve to regenerate / rewrite Accept @marcusdAIy  no labels

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->

## Summary

- Adds an opt-in addresses_finding_ids: list[str] field to Claire's regenerate_section and rewrite_section tool inputs so a single proposal can advertise which Review Agent findings it closes.

- Extends the existing PATCH /findings/{id}/status endpoint with optional provenance (addressed_via, tool_use_id, addressed_at) plus a telemetry INFO log when Claire's auto-resolve fires.

- Wires the FE Accept handler so that after a successful regenerate_section / rewrite_section, the scorecard flips the listed findings to addressed optimistically (with rollback on PATCH failure) — closes the review → chat → review loop without re-running /review.

## Why it's needed

Today, clicking Accept on a Claire proposal that addresses a finding updates the section but leaves the finding status="open" until the user manually re-runs review. Two real costs:

1. Scorecard staleness. The user has no immediate signal that their action moved the needle — they have to wait 30–60s for a fresh review run to find out.

2. Claire ambiguity. On the next chat turn, Claire still sees the finding as open and may helpfully suggest re-addressing what was just resolved.

This PR closes the loop: Claire tags her proposal with the finding IDs she's addressing, the FE auto-PATCHes them to addressed on Accept with a provenance trail, and operators get an INFO log they can count.

## Changes

### Backend (klair-api/)

- budget_bot/board_doc/claire_tools.py: add addresses_finding_ids: list[str] = Field(default_factory=list, ...) to RegenerateSectionInput and RewriteSectionInput. Surfaced in the Anthropic-facing CLAIRE_TOOLS JSON schema as an array<string> and called out in the Pydantic docstrings so Claire knows when to populate it.

- routers/board_doc_router.py: extend UpdateFindingStatusRequest with optional addressed_via: Literal["claire_regenerate", "claire_rewrite"], tool_use_id: str | None, addressed_at: str | None. Purely additive — existing {status}-only callers (scorecard manual triage) keep working unchanged. Emit a single INFO line on the PATCH endpoint when addressed_via is populated so production logs can count auto-resolve frequency distinctly from manual triage.

### Frontend (klair-client/)

- services/boardDocApi.ts: mirror addresses_finding_ids?: readonly string[] on RegenerateSectionToolInput / RewriteSectionToolInput, parse it in the TOOL_VALIDATORS registry, and extend updateFindingStatus with an optional provenance arg that adds the four-field shape to the PATCH body. Exports FindingAddressedVia and FindingStatusProvenance.

- hooks/useReviewAgent.ts: setFindingStatus now accepts an optional 5th provenance arg and forwards it to updateFindingStatus. Optimistic update + rollback semantics are unchanged.

- components/ChatToolProposal.tsx: new optional onAddressFindings prop. After a successful rewrite_section / regenerate_section Accept, fire it once with the proposal's addresses_finding_ids, the matching addressed_via literal, and the tool_use_id. No-op when the field is absent or the prop isn't wired.

- components/ChatPanel.tsx: forward onAddressFindings from the parent.

- DocumentEditorPage.tsx: implement the callback — for each finding ID, call reviewAgent.setFindingStatus(sessionId, findingId, 'addressed', provenance) so the scorecard optimistic-flips and rolls back on PATCH failure (matches the B3.15 comment-status pattern).

### Tests

- tests/board_doc/test_claire_tools.py: defaults / single-ID / multi-ID validation for both inputs, end-to-end round-trip through parse_tool_calls, CLAIRE_TOOLS schema shape assertions (field present + not required).

- tests/board_doc/test_finding_status_endpoint.py: full provenance payload accepted (200 + persistence + INFO log fires with claire_regenerate / claire_rewrite), invalid addressed_via rejected with 422, legacy single-field callers still work AND don't trigger the auto-resolve telemetry log, oversized tool_use_id rejected with 422.

- components/__tests__/ChatToolProposal.spec.tsx: 7 new tests covering 0-ID / single-ID / multi-ID invocation counts, Accept failure on both tools suppresses auto-resolve, omitted callback graceful no-op.

- hooks/__tests__/useReviewAgent.spec.ts: hook-level test confirming the provenance arg threads through to updateFindingStatus; existing manual-triage path updated to assert undefined is passed for back-compat.

### Verification artifact — proposal + PATCH payloads

A regenerate_section proposal carrying populated addresses_finding_ids:

{

"tool_use_id": "toolu_01ABCxyz",

"name": "regenerate_section",

"input": {

"section_id": "financials",

"feedback": "Refresh with Q2 walk-back, address C2.1 and C2.3.",

"addresses_finding_ids": ["finding-c21-001", "finding-c23-002"]

}

}

The resulting PATCH request body the FE fires (one per finding ID):

{

"status": "addressed",

"addressed_via": "claire_regenerate",

"tool_use_id": "toolu_01ABCxyz",

"addressed_at": "2026-05-22T15:30:00.000Z"

}

## Breaking changes

None. Both the Pydantic input field and the PATCH body extensions are opt-in with default-empty / None defaults. Existing Claire proposals continue to parse cleanly; the scorecard manual-triage path keeps sending plain {status} bodies.

## Test plan

Backend:

- [x] cd klair-api && uv run pytest tests/board_doc/test_claire_tools.py -v — 59 passed

- [x] cd klair-api && uv run pytest tests/board_doc/test_finding_status_endpoint.py -v — 21 passed

- [x] cd klair-api && uv run pytest tests/board_doc/test_review_findings.py -q — 30 passed

- [x] cd klair-api && uv run ruff format + uv run ruff check — clean

- [x] cd klair-api && uv run pyright budget_bot/board_doc/claire_tools.py routers/board_doc_router.py — 0 errors

- [x] cd klair-api && uv run pytest tests/board_doc/ -q — 1738 passed, 8 failed (the pre-existing C3.x family flakes; same set fails on main)

Frontend:

- [x] cd klair-client && pnpm test src/screens/BoardDoc/ — 287 passed

- [x] cd klair-client && pnpm tsc --noEmit — clean

- [x] cd klair-client && pnpm exec eslint --max-warnings 0 on changed files — clean

Manual validation still needed:

- [ ] In dev, accept a real Claire regenerate_section proposal that lists a finding in addresses_finding_ids and confirm the scorecard finding flips to addressed immediately + the BE log line fires.

- [ ] Simulate a PATCH failure (e.g. force a 500 on /findings/{id}/status) and confirm the scorecard rolls back + the error chip surfaces.

## Out of scope (per the drone spec)

- No changes to the other 5 Claire tools (add_comment / add_section / remove_section / rename_section / update_table_cell) — they don't semantically resolve findings.

- No system-prompt changes to encourage Claire to populate addresses_finding_ids more aggressively — the tool description nudge here is the only steering for now; prompt-tuning is a future iteration.

- No retroactive PATCHes against findings closed by prior Claire proposals — purely forward-looking from this merge.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-f72bd0ac-aa11-4e6d-bf3c-5af1414413ee"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-f72bd0ac-aa11-4e6d-bf3c-5af1414413ee"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

#2864 — feat(mfr): swap department for business_unit in budget drill-down @eric-tril  no labels

### Summary

Swaps the middle grouping dimension of the budget-column drill-down (PR #2853) from department to business_unit. The drill-down now displays type → business_unit → account_name end-to-end (SQL, response shape, accordion UI, CSV export). The 2-level accordion shell is unchanged — only the field name, label, and grouping key swap. The "single-level" mode used by the Education vertical drill-down is renamed from departments-only to business-units-only.

### Business Value

Finance asked for BU instead of department as the audit dimension on the budget panel. BU is the cut they reason about for plan ownership and variance review, so seeing it directly (rather than the finer department slice) shortens variance investigations during month-end close.

### Changes

- Backend: fetch_income_statement_budget_dimension_detail / _ebitda_gaap_budget_query / _ebitda_other_net_budget_query / _ebitda_bu_budget_query now SELECT/GROUP BY/ORDER BY (type, business_unit, account_name). The resolver CTE projects t1.business_unit unconditionally for budget queries (was opt-in via extra_columns only when an Education vertical filter was supplied).

- Backend: _df_to_budget_rows returns business_unit keys; _ebitda_total_budget_breakdown and the Software reconciliation rows updated to match.

- Backend: cash_flow_service._cf_mgmt_restructuring_budget_breakdown swaps the row-merge key from department to business_unit; two not-populated note strings updated.

- Backend: Pydantic BudgetDimensionDetailRow.departmentbusiness_unit; endpoint URLs unchanged.

- Frontend: TS BudgetDimensionDetailRow.departmentbusiness_unit; DepartmentGroupBusinessUnitGroup; mode: 'departments-only''business-units-only'; table header strings Department / AccountBU / Account and Type / Department / AccountType / BU / Account; CSV column updated.

- Tests: 4 backend test suites + 2 frontend spec files updated to the new field names and SQL substring.

### Education vertical note

The Education vertical drill-down already filters by business_unit to scope to a single vertical's BU set. After this swap it now also groups by BU, so an Education panel typically shows a single BU heading with all accounts beneath. Behavior is functional but flatter than before; verify with Finance that this is acceptable for that path.

### Testing

- [x] cd klair-api && uv run pytest tests/mfr/budget_drilldown/76 passed

- [x] cd klair-client && pnpm test src/features/monthly-financial-reporting/ --run613 passed

- [x] cd klair-client && pnpm tsc --noEmit — clean

- [x] cd klair-client && pnpm exec eslint --max-warnings 0 on changed files — clean

- [x] cd klair-api && uv run ruff format / ruff check — clean

- [ ] Manual: open MFR for a recent period; click a budget cell on Group / Software IS, EBITDA, a CF row, and an Education vertical row — confirm the panel shows BU headings (not Department) and totals still tie to the displayed cell.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

http://localhost:3001/monthly-financial-reporting

https://github.com/user-attachments/assets/250b4555-c48e-4c19-8b39-2bdfe165c6f2

The Builder Desk  —  Engineer Spotlight
📅 Week in Review🏆 Engineer Spotlight

84 PRs, 5 Repos, Zero Mercy: Builder Team Posts Another Week of Absolute Numerical Dominance

Benji Bizzell's 26-PR rampage leads a 7-day blitz that left 75 overflow PRs for the Numbers Desk to lovingly catalog.

Eighty-four pull requests. Five repositories. One relentless machine of a team that simply does not stop. The Builder Team posted another week for the history books, with Klair absorbing 33 PRs like the load-bearing pillar of a cathedral, Aerie contributing 24, Surtr adding 14, Rhodes chipping in 10, and trilogy-drones — bless its scrappy little rotors — logging 3. A new repo, Praxis-V2, has entered the chat. The future is being built in real time, people.

Let us begin where the numbers demand we begin: @benji-bizzell with a staggering 26 PRs. Twenty. Six. The man touched Aerie like it owed him money — PR #256 hardening due diligence saves, #254 dropping an editable forecast mapping config into admissions, #250 surfacing Rhodes detail in the site panel, #247 adding manual site provisioning override, #249 sourcing accountability freshness from Rhodes, and #100 exposing a P2 site freshness endpoint. He also found time to restore Convex typecheck in Rhodes #101. This is not software development. This is a siege.

@marcusdAIy posted 15 PRs and quietly rewrote the laws of board documentation. PR #2857 made generate_mips LLM-first under the B9.6 contract. PR #2851 rewrote five narrative generators in a single swing. PR #2849 added per-product margin vs target with sub-50% shutdown framing to the review agent. And then — almost as a postscript — PR #4 in trilogy-drones delivered cost-tunable model selection, addresser line-drift fixes, AND a week's retro polish. In one PR. In a drone repo. The audacity is inspiring.

@eric-tril's 13 PRs included PR #71 in Surtr, standing up a full class-dimension income statement pipeline for KLAIR-2659, and then turning around to repair 9 stale MFR backend tests in #2861 and reorganize them into clean subdirectories in #2860. That is a man who ships features AND cleans up after himself. A legend. @sanketghia posted 11 PRs, most notably PR #93 in Surtr — a Lambda memory bump from 512 to a heroic 4096 MB that ended an OOM crisis — and PR #87 enabling the XO contractor invoices daily schedule. @kevalshahtrilogy's 9 PRs included a full AI spend layer in Surtr #82 with Max20x and negotiated savings tables, plus MCP tools in #2846 and #2848 that let the team query Claude AI spend and Max20x savings directly. @YibinLongTrilogy rounds out the roster with 5 PRs, steady and dependable as a load balancer in production.

And then there is @ashwanth1109. Five PRs this week. FIVE. Lesser correspondents would call this a quiet week. Those correspondents have never watched the man work. PR #200 in Aerie dropped the Financials tab with full Revenue Breakdown as a Miami POC. PR #214 added commenting, headcount tables, and unlocked more schools on the financials dashboard. PR #252 introduced the financials viewer role to user management. PR #2822 restored get_impacted_subscription_ids for the renewals Twitter toggle — a fix so precise it could thread a needle at a hundred yards. And PR #2854 resolved a gnarly FE quarter-from-iso-week misalignment against the backend Monday rule. When asked about the week's output, Ashwanth reportedly said, "Five PRs, five problems that no longer exist. I don't really track the numbers — the numbers track me." This correspondent reached out for further comment. He did not respond. He never responds. This is fine.

Morale on the Builder Team is, by every available metric, at an all-time high. Praxis-V2 is breathing. The repos are humming. The Numbers Desk has never been prouder.

Brick's Overflow — This Week's Uncovered PRs  (click to expand)
#93 — feat(aerie): add operating details endpoint @benji-bizzell  no labels

## Summary

- Add an on-demand Aerie Operating details endpoint for recent notes and dated/statused work-unit/task context

- Keep the existing quality-bar score endpoint lean for top-level Operating views

- Document the new read-only Aerie route and note-window contract

## Why

Aerie needs richer details only after users open the Operating detail panel. Returning all nested notes and task/work-unit context from the summary endpoint would make the baseline payload heavier and less predictable.

## Business Value

Keeps the main Operating dashboard fast while giving Aerie enough scoped detail to render expanded rows and generate note roll-up summaries on demand.

## Test plan

- [x] node --test --experimental-strip-types convex/qualityBars.test.ts

- [x] npm exec --package typescript -- tsc -p convex/tsconfig.json --noEmit

- [x] git diff --check

- [x] Queried Alpha Miami dev data and locally sized the projected details payload

- [ ] Deploy to dev and verify GET /sync/aerie/getOperatingDetails after cleaning the existing dev schema mismatch (documents.docType = "loi")

#200 — feat(dashboards): Financials tab with Revenue Breakdown (Miami POC) @ashwanth1109  no labels

## Summary

- New top-level Financials tab in the Dashboards sidebar.

- Renders a QuickBooks-sourced Revenue Breakdown table by quarter + YTD: Net Tuition, Discounts, Stripe fees, *Net Tuition calculated*, Lunch, Other Revenue, Total Revenue.

- School dropdown filtered to Miami for this POC (default-selected).

- Backend: new Convex queries getRevenueBreakdown (aggregates plRecords with a school-agnostic line-item classifier) and getPlSchools (distinct school names from plRecords).

## Test plan

- [ ] Visit /dashboards?tab=financials and confirm the Financials tab renders without console errors.

- [ ] Confirm the school dropdown shows only Miami and is pre-selected.

- [ ] Cross-check Miami Q1/Q2 values against QuickBooks for Net Tuition, Discounts, Stripe fees, Lunch, Other Revenue.

- [ ] Verify Net Tuition calculated = Net Tuition − Discounts − Stripe fees and Total Revenue sums match.

- [ ] Click Export CSV and confirm rows match the rendered table (sign-flipped for Discounts/Stripe).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

#256 — fix(portfolio): harden due diligence saves @benji-bizzell  no labels

## Summary

- Materialize cleared Due Diligence fields as explicit null values for Rhodes while preserving REBL3 strict-replace behavior.

- Convert expected missing-status DD validation into a structured failure and show friendly save-dialog guidance instead of raw Convex errors.

- Raise the theme picker popover above forecast dashboard controls.

## Why

A Due Diligence removal from the Aerie UI could update REBL3 while Rhodes preserved the old value, leaving the paired systems out of sync. During follow-up testing, the missing-status path also surfaced raw Convex errors to users, which made actionable validation look like a broken system. The forecast dashboard theme picker also rendered behind nearby controls.

## Business Value

Keeps REBL3 and Rhodes aligned when operators clear Due Diligence fields, makes expected validation failures understandable to end users, and removes a visible dashboard polish issue from the hotfix.

## Test plan

- [x] pnpm --filter @bran/contracts test -- due-diligence

- [x] pnpm --filter chat exec vitest run convex/dueDiligence.test.ts

- [x] pnpm --filter chat exec vitest run components/dashboards/portfolio/fields/__tests__/portfolio-fields-provider.test.tsx components/site-fields/__tests__/save-confirm-dialog.test.tsx

- [x] pnpm --filter chat exec vitest run components/dashboards/portfolio/cards/__tests__/due-diligence-editor.test.tsx components/dashboards/portfolio/fields/__tests__/portfolio-fields-provider.test.tsx components/dashboards/portfolio/__tests__/portfolio-view.test.tsx

- [x] pnpm --filter chat exec vitest run components/shell/__tests__/theme-picker-popover.test.tsx

- [x] pnpm --filter @bran/contracts typecheck

- [x] pnpm --filter chat typecheck

- [x] pnpm biome check on touched files

#2849 — feat(review-agent): C3.1 + C3.2 per-product margin vs target (with sub-50% shutdown framing) @marcusdAIy  no labels

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->

## Summary

- Added C3.1 (check_margin_per_product_benchmark) — the first floor check in the C3.x per-product benchmark family. Absorbs the original backlog's C3.1 (sub-50% margin shutdown analysis) and C3.2 (net margin vs 75% benchmark) into a single P-W-C verdict ladder per the May 20 triage call: both read the same row; the only difference between the two cards is the verdict threshold, which collapses naturally into a single ladder with sub-50% framed as narrative emphasis on the Critical tier (not a separate verdict band).

- Reused the sibling C3.x scaffolding (typed skip ladder, ragged-row WARNING, per-product fan-out routing via is_rollup, BenchmarkPerProductSupport / BenchmarkAggregateSupport) — only the gap formula, the row addressing, and the Critical-tier prose differ from the ceiling-check siblings.

- Added a dedicated C3.1 test module (TestPerProductFanOut, TestPerProductTargetLookup, TestSkipSemantics, TestShutdownNarrative, TestRaggedRowDriftWarning, TestTargetCategoryPin, TestRegistryWiring, TestBoundaries, plus section-id-resolution + supporting-data coverage).

- Updated test_review_endpoint.py happy-path / missing-data / partial-completeness expectations and seeded the BENCHMARK_BY_PRODUCT fixture with the (summary, Margin) + (summary, Margin Target) rows so C3.1 emits its BU-level pass finding (no skip) under the populated path.

## Why it's needed

- C3.1 is the family's first floor check (C3.3 → C3.9 are all cost-ratio ceilings). Without it the per-product scorecard never surfaces a margin-floor violation — the original sub-50% shutdown trigger and the 75% margin benchmark both lived in the backlog without a runtime check.

- Per-product margin is the cleanest single signal that a Trilogy product is funding its own operating model; a material shortfall against the Finance-set target either reflects a cost-side overhang or a revenue / pricing gap that needs explicit GM commentary.

- Merging the two backlog cards keeps the per-product benchmark scorecard scoped to one check per metric while still surfacing the shutdown narrative for sub-50% margins via differentiated prose on the Critical tier.

## Changes

- Added klair-api/budget_bot/board_doc/review_checks/margin_per_product_benchmark.py:

- CHECK_ID = "C3.1", CHECK_AREA = "Per-Product Benchmarks".

- _TARGET_SECTION = "summary", _TARGET_CATEGORY_ACTUAL = "Margin", _TARGET_CATEGORY_TARGET = "Margin Target", _WARNING_BAND_PP = 5.0, _SHUTDOWN_THRESHOLD_PCT = 50.0.

- @register(... name="Per-product margin vs target (sub-50% shutdown)", required_data=(DataSourceKey.BENCHMARK_BY_PRODUCT,)).

- Sign-flipped gap_pp = target_pct - actual_pct with an inline cross-reference comment to the C3.3 ceiling pattern this inverts.

- Per-column target lookup inside the per-product loop — BenchmarkPerProductSupport.standard_benchmark_pct populated from the per-column target_pct, no schema change.

- Three typed-skip paths (tab missing → INFO; (summary, Margin) row missing → WARNING; (summary, Margin Target) row missing → WARNING) plus the all-blank INFO skip.

- Critical-tier narrative branches on _SHUTDOWN_THRESHOLD_PCT — shutdown-analysis framing iff actual_pct < 50%; standard "below target" framing otherwise. Severity stays "critical" in both cases.

- Added klair-api/tests/board_doc/test_margin_per_product_benchmark.py covering:

- Fan-out with mixed-band actuals (Pass, Warning, standard Critical, shutdown-framed Critical).

- Per-product target lookup — non-uniform targets prove the lookup is load-bearing (same actual lands in different verdict bands depending on whose target column it's compared against).

- All four skip paths (tab missing INFO; Margin row missing WARNING; Margin Target row missing WARNING; all-blank INFO).

- Shutdown-framing boundary pinned at 50% — 49.99% shutdown framing, 50% / 50.01% standard framing.

- Boundary inclusivity (actual == target → pass; actual == target - 5pp → warning; gap_pp = 6 with 69% actual → critical, standard framing).

- Ragged-row WARNING on BOTH the Margin row and the Margin Target row.

- Drift sentinels for _TARGET_CATEGORY_ACTUAL, _TARGET_CATEGORY_TARGET, AND _TARGET_SECTION (distinct from the C3.3 → C3.9 "total").

- Registry wiring pin (id, area, name, required_data).

- Updated klair-api/tests/board_doc/test_review_endpoint.py:

- Added "C3.1" to the populated-path check_ids set and to the skipped_checks sets of the two missing-data tests + the partial-completeness test.

- Bumped happy-path len(data["findings"]) from 14 to 15 (one BU-level pass finding from C3.1).

- Seeded (summary, Margin) + (summary, Margin Target) in the BENCHMARK_BY_PRODUCT fixture at 75% for Skyvera Consolidated so C3.1's rollup passes silently and the aggregate-pass fires.

## Breaking changes

None.

## Test plan

- [x] cd klair-api && uv run ruff format budget_bot/board_doc/review_checks tests/board_doc (no changes)

- [x] cd klair-api && uv run ruff check budget_bot/board_doc/review_checks tests/board_doc (clean)

- [x] cd klair-api && uv run pyright budget_bot/board_doc/review_checks/margin_per_product_benchmark.py tests/board_doc/test_margin_per_product_benchmark.py (0 errors)

- [x] cd klair-api && uv run pytest tests/board_doc/test_margin_per_product_benchmark.py tests/board_doc/test_review_endpoint.py -q (49 passed)

- [x] cd klair-api && uv run pytest tests/board_doc -q (8 pre-existing unrelated failures in test_review_findings.py, test_saas_it_ops_benchmark.py, test_sales_marketing_benchmark.py, test_section_crud_endpoints.py, test_support_benchmark.py — same set already noted in C3.9's PR #2837; no C3.1-related failures; my changes resolve the 4 prior test_review_endpoint.py failures that fell out of adding C3.1 to the registry)

## Verification artifact

Two C3.1 findings from a fixture with Skyvera Consolidated = 80% (pass), ProductA = 60% / target 75% (standard Critical, 50%-70% band), and ProductB = 40% / target 75% (shutdown-framed Critical, sub-50%) — pinning both narrative tracks:

{

"check_id": "C3.1",

"severity": "critical",

"section_id": "product_detail_producta",

"what": "ProductA margin (60.0%) is 15.0pp below its 75.0% target.",

"supporting_data": {

"product": "ProductA",

"is_rollup": false,

"actual_pct": 60.0,

"benchmark_pct": 75.0,

"gap_pp": 15.0,

"warning_band_pp": 5.0,

"standard_benchmark_pct_in_sheet": 75.0

}

}

{

"check_id": "C3.1",

"severity": "critical",

"section_id": "product_detail_productb",

"what": "ProductB margin (40.0%) is 35.0pp below its 75.0% target AND below the 50% shutdown threshold — shutdown analysis required.",

"supporting_data": {

"product": "ProductB",

"is_rollup": false,

"actual_pct": 40.0,

"benchmark_pct": 75.0,

"gap_pp": 35.0,

"warning_band_pp": 5.0,

"standard_benchmark_pct_in_sheet": 75.0

}

}

Closes KLAIR-2676

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-1095d644-356d-4ea5-9c7a-9ebf17eb2283"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-1095d644-356d-4ea5-9c7a-9ebf17eb2283"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

#2854 — KLAIR-2765 fix(aws-spend): align FE quarter-from-iso-week to BE Monday-rule @ashwanth1109  no labels

## Demo

<img width="2624" height="1636" alt="image" src="https://github.com/user-attachments/assets/971b515d-068f-420b-8bb3-fd0ab1c9fb4e" />

## Summary

Linear: [KLAIR-2765](https://linear.app/builder-team/issue/KLAIR-2765/investigate-docker-and-kubernetes-cost-fetch-errors-for-q326-budget)

Fixes a FE/BE mismatch in how an ISO week is assigned to a calendar quarter. The frontend used pure week-number bucketing (W14–26 → Q2), while the backend validator and ingestion pipeline use the ISO Monday's calendar quarter. Quarter-boundary weeks (W1, W14, W27, W40 of 2026) tripped the backend's _validate_weeks_in_quarter check and returned 400 for both /docker-cost and /kubernetes-cost.

Single point of fix: TS port of klair-api/utils/quarter_math.py:iso_week_quarter into klair-client/src/screens/AWSSpend/components/SaaSBudgeting/isoWeekDates.ts. No backend or ingestion changes.

## Spec

- 29-align-fe-quarter-from-iso-week-to-monday-rule — Port BE Monday-based iso_week_quarter rule to TS so FE, BE, and ingestion pipeline agree on quarter boundaries. Rewrites the misleading inline comment. Adds boundary-week + year-crossover unit tests for quarterFromIsoWeek, groupWeekKeysByQuarter, and latestQuarterFromWeekKeys.

## Files changed

- klair-client/src/screens/AWSSpend/components/SaaSBudgeting/isoWeekDates.tsquarterFromIsoWeek body + inline comment block

- klair-client/src/screens/AWSSpend/components/SaaSBudgeting/isoWeekDates.spec.ts — updated stale assertions and added boundary coverage

- features/aws-spend/saas-budgeting/FEATURE.md + spec 29 — doc/changelog updates

## Test coverage

- 35 it(...) blocks across 7 describe blocks in isoWeekDates.spec.ts (was 21). All pass.

- Covers: 4× 2026 boundary weeks (W1/W14/W27/W40), 4× ordinary weeks (W13/W26/W39/W52), 1× year-crossover (2020-W53 mirroring the BE TestIsoWeekQuarter shape) and 1× synthetic year-crossover (2021-W53 → 2022-Q1). Downstream groupWeekKeysByQuarter and latestQuarterFromWeekKeys re-tested under the new rule.

## Self-review

No issues found (Phase 7 self-review subagent). Port arithmetic verified against three known mappings.

## CI

- build ✓ pass

- lint ✓ pass

- ruff-check ✓ pass

- test, review, auto-merge — skipped (no backend changes; gated workflows)

- claude-review ⚠️ soft-fail — caused by a duplicate Frontend CI workflow run at this commit SHA (one cancelled within seconds of starting due to workflow-concurrency, the other fully green). The wait-for-checks step inside claude-review rejects the cancelled status of the duplicate even though the live run is green. Not a code defect; reverifiable from the workflow logs.

## Test plan

- [ ] pnpm vitest run for isoWeekDates.spec.ts — green

- [ ] pnpm tsc --noEmit — clean

- [ ] Manual: /docker-cost?quarter=2026-Q2&weeks=14,... no longer returns 400; W14 now groups under 2026-Q1 (matches BE)

#2857 — feat(board-doc): rewrite generate_mips as LLM-first (B9.6) @marcusdAIy  no labels

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->

Refs KLAIR-2716.

## Summary

Converts generate_mips from a deterministic read of spec.bu_mips to an LLM-first generator whose source of truth is the section's current content (cloned-doc body, wizard-bridged MIPs, or operator-edited draft) plus DataPackage signals plus open review findings. Joins the same SectionRefreshContext contract as the five B9.1-B9.5 siblings shipped in PR #2851.

## Why it's needed

spec.bu_mips is a wizard-coupled mirror of state that also lives in generated_sections["mips"]. The pre-B9 deterministic generate_mips reads the mirror; the user edits the section body. Those two stores drift, and a regenerate from a clone-imported doc that never went through the MIPs wizard returns "" — exactly the dual-store read/write reconciliation bug B9 exists to fix. B9.6 collapses MIPs onto the same single source of truth the other five B9 generators already use, with a producer→consumer bridge at publish time so wizard-approved MIPs flow into the LLM-first refresh path.

## Changes

- budget_bot/board_doc/section_generators.py

- Adds _MIPS_SYSTEM next to the four existing B9 inline system prompts. MIP-specific in-scope / not-in-scope framing on top of the shared _B9_DISPOSITION / B9_SCOPE_DISCIPLINE / B9_OUTPUT_RULES / SHARED_SUFFIX_WITH_CITATIONS blocks, plus an explicit output template (### MIP <n>: <statement> + paragraph + bulleted actions) so per-MIP heading shape stays stable across refreshes.

- Rewrites generate_mips to the canonical B9 shape: 3 positional args + *, context: SectionRefreshContext | None = None. Builds a BU-wide data block via build_key_metrics_block, runs through _build_b9_user_message + _strip_cite_tags + _resolve_system_prompt, and one-shots _llm_generate. Cold-start path logs a warning with BU / quarter provenance when both the metrics digest and ctx anchors are empty, so a broken DataPackage shows up in monitoring instead of silently producing a brainlift-only draft.

- Adds SectionType.MIPS to _B9_CONTEXT_AWARE so _regenerate_section builds and threads the context.

- Trims the now-stale "(financials, exec summary, mips, custom)" comment in generate_section's dispatcher.

- budget_bot/board_doc/wizard_orchestrator.py

- Producer→consumer bridge. _regenerate_all now writes _render_mips_markdown(spec.bu_mips, heading="") into session.generated_sections[<mips section id>] after the bulk generate_all_sections call, so wizard-approved MIPs survive first publish and are available as SectionRefreshContext.current_content on subsequent operator-driven refreshes. Tracked for migration to a no-op under B9.8 (KLAIR-2718).

- Refreshes two stale comment blocks in _regenerate_section (the empty-result fallback to generate_custom_section and the belt-and-suspenders guard at the section-result tail) to reflect that post-B9.6 no in-tree LLM-first generator legitimately returns success=True with empty markdown — _EmptyLLMResult short-circuits inside the retry loop. Operator-facing log message no longer misattributes unexpected emptiness to "a deterministic generator (MIPs) lacking the wizard-step input".

- budget_bot/board_doc/models.py

- Expands the inline deprecation note on DocumentSpec.bu_mips to list the remaining readers (generate_gm_commentary threads it into the GM prompt; generate_cf_plan short-circuits to a deterministic render) so a future PR that trusts the note doesn't silently break either generator. Field removal still tracked under KLAIR-2718.

- tests/board_doc/test_b9_narrative_generators.py

- Adds a TestGenerateMips class covering cold-start, current_content / findings_block / full_doc_block threading, dead-spec.bu_mips-input independence (load-bearing regression for the source-of-truth shift), LLM exception propagation, a strengthened generate_product_detail integration test that exercises the real per-product MIPs render path, and an isolated _render_mips_markdown smoke test. The cold-start test also pins that _resolve_system_prompt picked up _MIPS_SYSTEM (not a sibling B9 constant).

- tests/board_doc/test_wizard_orchestrator.py

- Adds TestRegenerateAllMipsBridge pinning the wizard-MIPs → generated_sections["mips"] bridge: approved MIPs flow through, bu_mips=[] leaves the LLM cold-draft intact.

## Breaking changes

None at the Python API surface — generate_mips keeps its three positional args; the new context kwarg is keyword-only with a None default. Behavioural change for callers that previously relied on spec.bu_mips populating the BU MIPs section: those callers now get the wizard-approved MIPs rendered through the bridge in _regenerate_all, and the section body becomes the new single source of truth on subsequent regenerations.

## Contract surface affected

1. generate_mips signature. Gains *, context: SectionRefreshContext | None = None. Existing call sites in _GENERATOR_MAP route through generate_section's _invoke wrapper, which dispatches the kwarg based on _B9_CONTEXT_AWARE membership — no call-site updates required.

2. _B9_CONTEXT_AWARE set. Grows from 5 to 6 members (adds SectionType.MIPS).

3. _render_mips_markdown caller count. Holds at 2: generate_product_detail (product-level MIPs) and _regenerate_all (new wizard-bridge call). The helper stays in tree.

4. DocumentSpec.bu_mips. Marked deprecated for generate_mips; deprecation note now spells out the two remaining readers (generate_gm_commentary, generate_cf_plan) that block field removal.

## Test plan

- cd klair-api && uv run pytest tests/board_doc/test_b9_narrative_generators.py -v38 passed (7 new TestGenerateMips cases).

- cd klair-api && uv run pytest tests/board_doc/test_wizard_orchestrator.py -v127 passed (2 new TestRegenerateAllMipsBridge cases).

- cd klair-api && uv run pytest tests/board_doc -q --timeout=1201730 passed, same 8 pre-existing failures verified identical on main with branch stashed (test-ordering flakes in test_review_findings, test_saas_it_ops_benchmark, test_sales_marketing_benchmark, test_section_crud_endpoints, test_support_benchmark; all pass individually).

- cd klair-api && uv run ruff format <touched files> → clean.

- cd klair-api && uv run ruff check <touched files> → all checks passed.

- cd klair-api && uv run pyright budget_bot/board_doc/section_generators.py budget_bot/board_doc/models.py budget_bot/board_doc/wizard_orchestrator.py → 0 errors (1 pre-existing warning at wizard_orchestrator.py:7066, untouched).

### Verification artifact — prompt shape on a populated fixture

TestGenerateMips::test_current_content_threads_into_prompt captures the user-message prompt with context.current_content populated and asserts that the prior MIPs body lands under the canonical ## Current Section Content (cloned-doc body / prior draft — refresh in place) header alongside the BU-wide data block. test_dead_bu_mips_input_does_not_leak is the load-bearing pin for the source-of-truth shift: spec.bu_mips = [MIP(title="STALE WIZARD MIP", ...)] does not appear anywhere in the captured prompt.

### CLAUDE.md sweep on touched modules

- section_generators.py: post-edit grep of the new generate_mips body for spec.bu_mips returns zero matches (load-bearing acceptance criterion). No except Exception: swallow inside the new function; LLM failures propagate to the retry loop in generate_section. The cold-start path's empty-build_key_metrics_block branch logs an observable warning when the context also has no anchor, addressing the empty-defaults / silent-failure shape from CLAUDE.md.

- wizard_orchestrator.py: the empty-result fallback in _regenerate_section is unreachable for the LLM-first family by construction (now documented). The new bridge in _regenerate_all is a deterministic post-generation overwrite — no swallowed exceptions.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-344d8b86-5a90-4052-960c-6064b723b98b"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-344d8b86-5a90-4052-960c-6064b723b98b"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

The Portfolio  —  Trilogy Companies

Scott Alexander Puts Alpha School Under the Microscope — and the Results Are Complicated

The Astral Codex Ten blogger's crowdsourced review surfaces genuine gains alongside questions the school's backers would rather not answer.

AUSTIN, TEXAS — When Scott Alexander, the pseudonymous rationalist blogger behind Astral Codex Ten, solicits reader reviews of an institution, the institution tends to learn something about itself it didn't know it wanted to know. That is now the situation facing Alpha School, Joe Liemandt's flagship AI-powered K-12 experiment in Austin, Texas.

The review, assembled from accounts submitted by parents, students, and educators with direct experience of the school, arrives at a moment when Alpha is accelerating hard. Nine new campuses are slated to open by fall 2025 across Texas, Florida, Arizona, California, and New York. Tuition runs $40,000 to $65,000 per year. Liemandt has committed $1 billion to Timeback, his platform designed to let entrepreneurs replicate the Alpha model at scale — a kind of Shopify for AI-first schools.

The core Alpha proposition is audacious in its simplicity: adaptive AI tutors deliver a full academic curriculum in two hours each morning. The rest of the school day belongs to entrepreneurship, leadership, financial literacy, and athletics. The school reports students learn 2.3 times faster than U.S. norms and consistently test in the top 1–2 percent nationally on NWEA MAP Growth assessments.

Alexander's crowdsourced review does not dispute the test score claims. What it surfaces is harder to quantify: questions about the social architecture of a school where academic instruction is almost entirely mediated by software, about what happens to students who don't self-regulate well under minimal adult direction, and about whether the life-skills curriculum delivers on its promise or functions primarily as a premium brand differentiator for families already rich in social capital.

None of this is fatal to the Alpha thesis. But the timing is notable. As Liemandt's education empire scales from a single Austin campus to a national network, the gap between what the model promises and what independent observers can verify is about to get a great deal more scrutiny.

The school has an answer for every question. Whether those answers hold at nine campuses, then ninety, is the experiment now underway.

Your Review: Alpha School - by Scott Alexander - Astral Code  ·  How K-pop is pushing into children's content worldwide - The  ·  Notable technology M&A deals in Spain | Analysis: April 2026

Skyvera Grabs CloudSense, and the Telco Software Map Gets Redrawn

The Trilogy telecom shop adds Salesforce-native quoting and order muscle to a portfolio already built for carriers with legacy headaches.

AUSTIN, TEXAS — Word is the telecom software bazaar just got a little more consolidated, a little more Salesforce-flavored, and a lot more interesting for the carrier crowd still trying to drag old billing and ordering stacks into the cloud era.

Skyvera, the Trilogy-family telecom software house, has completed its acquisition of CloudSense, the Salesforce-native CPQ and order management platform built for telecom and media providers. That means the folks selling fiber, mobile plans, bundles, and enterprise connectivity now have another reason to watch the Austin machine: Skyvera is stitching together the unglamorous but mission-critical plumbing of modern communications commerce.

CloudSense is not some shiny demo toy, dolls. It lives where the money gets made and lost — configure, price, quote, order capture, and fulfillment. In telco land, that is where a simple customer request can become a spaghetti supper of product catalogs, network availability, contract rules, discounts, and provisioning handoffs. Skyvera’s new prize is built directly on Salesforce, giving carriers a familiar front office while trying to tame the back-office beast. The company is now showcasing CloudSense as part of its broader telecom portfolio.

A little bird with a headset tells me this is not a one-off shopping trip. Skyvera has already been assembling a cabinet of carrier-grade assets: Kandy for cloud communications, VoltDelta for customer engagement, ResponseTek for experience reporting, Mobilogy Now for device lifecycle management, and Service Gateway for device management. Add CloudSense, and suddenly the pitch gets bigger: not just customer conversations, not just device workflows, but the commercial engine that decides what gets sold, at what price, and how it becomes an actual order.

There is also the STL angle. Skyvera says it acquired STL’s telecom products group, bringing in digital BSS capabilities across monetization, optical networking, and analytics. Translation for civilians: more of the software guts that operators need but rarely brag about at cocktail hour.

The official announcement says Skyvera’s CloudSense acquisition expands its telecom software portfolio. The subtext is pure Trilogy: buy specialized enterprise software, tuck it into a broader operating machine, and make the pieces work harder together.

Meanwhile, that Mint item about a software firm not getting paid “until the customer gets value” is floating around the same industry conversation like perfume in a lobby. Value-based software pricing is the phrase of the season. Skyvera’s bet is more old-school and more brutal: own the systems that carriers cannot casually unplug.

A software firm that’s not paid ‘until the customer gets val  ·  CloudSense  ·  Skyvera completes acquisition of CloudSense, expanding telec

The Offshore Paradox: Why AI Hasn't Killed Global Talent Arbitrage — It's Transformed It

As rivals chase cheap labor, Crossover's meritocratic model looks less like an alternative and more like an inevitability.

AUSTIN, TEXAS — The numbers keep coming in, and if you read between the lines, they tell a story that the enterprise software industry has been slow to reckon with. Despite the AI boom, U.S. companies are still aggressively hiring offshore workers — not instead of AI, but alongside it. The pitch has shifted from cost-cutting to output multiplication, with firms like Saigon Technology now marketing a "16-hour development cycle" that promises to double engineering throughput by bridging time zones. The optics are different. The underlying logic is identical.

And this is where it gets interesting. The market is bifurcating sharply. On one side: the legacy offshore model, geography-based pay, résumé-screened hiring, and the inevitable quality inconsistency that comes with it. On the other: what Crossover — Trilogy International's global talent platform — has been quietly building for over a decade.

A source familiar with Trilogy's internal talent metrics, who asked not to be named, put it plainly: "The offshore conversation everyone is having right now is the conversation Crossover solved ten years ago. The difference is they never sold geography. They sold the top one percent, wherever they live."

Crossover's model is built on rigorous AI-enabled skills assessments, transparent above-market pay that does not vary by country, and 100% remote deployment across 130+ nations. The platform staffs the entire ESW Capital portfolio — including Skyvera, whose recent integration of CloudSense, the Salesforce-native CPQ and order management platform for telecoms, has required a significant engineering lift across time zones. That work, by all accounts, is being done without the quality gaps that plague conventional offshore arrangements.

The broader industry, meanwhile, is still trying to figure out what question to ask. The 2025 offshore trends conversation is dominated by cost and cycle time. Crossover's answer — that geography is irrelevant but merit is everything — is a different thesis entirely. Not cheaper. Better.

Nothing about the current offshore surge is a coincidence. It is the market slowly, expensively, arriving at a conclusion Trilogy drew years ago.

The AI boom hasn't stopped U.S. companies from hiring cheap  ·  Saigon Technology Introduces the 16-Hour Development Cycle,  ·  FPT and Quadient Reinforce Long-Standing Strategic Partnersh
The Machine  —  AI & Technology

The Developer Stack Just Went Agentic — And Big AI Is Racing to Own the Workflow

Google, Anthropic and OpenAI are turning models into tool-wielding collaborators, and the software industry may never look the same.

MOUNTAIN VIEW, CALIFORNIA — The agentic era is no longer a keynote fantasy. It is arriving as an API, a developer console, a tool call, a video generation endpoint — and yes, this changes everything.

In a rapid-fire week for builders, Google used I/O 2026 to frame the future around agents: AI systems that do not merely answer questions, but plan, act, call tools, generate media, and help developers assemble entire workflows. The company’s developer highlights from I/O 2026 point toward a world where applications become less like static software and more like coordinated teams of digital workers.

That shift is being matched across the frontier labs. Anthropic announced advanced tool use on the Claude Developer Platform, a major step in making Claude more capable inside real production environments. Tool use is the deceptively simple phrase hiding the revolution: models can query databases, manipulate files, call business systems, retrieve context, and execute multi-step tasks with far less human babysitting. I cannot overstate how significant that is for enterprise software.

OpenAI, meanwhile, introduced GPT-5 for developers, signaling that the competitive frontier has moved beyond “which chatbot sounds smartest?” to “which model can actually ship work?” The answer increasingly depends on reliability, latency, context handling, tool orchestration and how well AI can sit inside the messy machinery of companies.

Google also pushed hard on creative AI, introducing Veo 3.1 and new Gemini API capabilities for developers building video-native products. With Veo 3.1 entering the Gemini API toolkit, the boundary between software developer and studio producer gets blurrier by the day. The future is now, and apparently it renders in high definition.

The startup ecosystem is responding exactly as expected: by exploding. Reports from Y Combinator’s latest Demo Day describe an AI-heavy batch, with founders racing to build vertical agents, developer tools, automated back offices and creative systems on top of these frontier platforms.

The takeaway is clear: developers are becoming conductors of AI systems rather than mere writers of code. The winners will be the teams that combine models, tools, data and workflow into products that feel less like apps — and more like capable colleagues.

Building the agentic future: Developer highlights from I/O 2  ·  Introducing advanced tool use on the Claude Developer Platfo  ·  Introducing Veo 3.1 and new creative capabilities in the Gem

AI's Security Paradox: The Technology Creating Risk Is Also Demanding Its Own Police Force

From London's AI Safety Institute to Silicon Valley hiring desks, the industry is scrambling to contain what it built.

LONDON — The arithmetic is uncomfortable. Artificial intelligence generates code faster than humans can audit it, deploys models whose failure modes remain poorly understood, and now requires an entirely new category of professional to manage the wreckage. The market has noticed.

Demand for security engineers has surged as AI systems produce a glut of new code and introduce novel attack surfaces. Anthropic's Mythos model, among others, has sharpened concerns about what happens when large language models are embedded in production systems with real-world consequences. Security engineering, long a niche discipline, is now among the fastest-growing roles in enterprise technology.

The public sector is moving in parallel. The UK's AI Security Institute — staffed substantially by alumni from OpenAI and Google — has emerged as a reference model for governments attempting to systematize AI risk assessment. The institute's approach is methodical: identify threat vectors, stress-test frontier models, publish findings. Several allied governments are studying the structure as a template. The work is unglamorous and largely invisible until something goes wrong, which is precisely the point.

Against this backdrop, Google I/O last week offered a telling cultural data point. Observers noted it was, in recent memory, the rare large technology gathering where mentions of AI did not draw audible skepticism from the audience. CEO Sundar Pichai, in a subsequent conversation with the Hard Fork podcast, acknowledged the anxiety is real — among workers, among graduates entering a restructured labor market, among users uncertain what AI agents are actually doing on their behalf — while maintaining that the trajectory is net positive.

Pichai's equanimity is a product position as much as a personal one. Google has more to lose from AI pessimism than almost any company on earth.

The structural reality, however, cuts across the optimism. Every new model deployment, every AI-generated codebase, every autonomous agent given API access to financial systems creates a corresponding security obligation. Governments and enterprises alike are discovering that AI's productivity gains arrive bundled with a security tax — payable in headcount, in infrastructure, and increasingly in regulatory compliance. The bill is only beginning to come due.

UK Institute Is Hunting for Dangers Lurking in AI  ·  One Job That Is Growing in the A.I. Era? Cybersecurity Exper  ·  Our Field Trip to Google I/O + A Sit-Down With Sundar Pichai

The Thirsty Migration of the Machine Herd

As AI data centers spread across the West, their appetite for power and water is becoming a civic question as much as a technological one.

VANCOUVER — In the cool grey margins of the Pacific coast, where rain often seems less weather than atmosphere, a new species is seeking habitat: the AI data center, vast, humming and exquisitely hungry.

These facilities do not graze in the ordinary sense. They consume electricity, land, fiber-optic access and — in many designs — water for cooling the dense colonies of servers within. As the artificial intelligence boom accelerates, reports now point to a widening tension between the industry’s ambitions and the natural limits of the regions asked to host it. In the western United States, where drought is not an anomaly but a recurring condition of life, concerns over water stress have become part of the larger story of AI infrastructure.

Observe the cloud, then, not as a cloud at all, but as a watering hole. Around it gather the great beasts of modern computation: model trainers, inference engines, storage arrays and networking gear, each performing its ritual with tireless precision. The brighter the intelligence appears on the user’s screen, the more intense the unseen metabolism behind it.

Bank of America has reportedly projected the AI data center market could reach $1.7 trillion by 2030, a figure that suggests not a passing flock but a continental migration. With that migration comes pressure on electric grids, carbon targets and local permitting regimes. Communities from Vancouver to the American Southwest are asking practical questions: Who benefits? Who pays? How much water is used? And when demand spikes, who stands aside?

The industry’s answer is increasingly sophisticated energy management. Operators are exploring more efficient cooling, better workload scheduling, renewable power procurement and designs that shift computing toward regions or hours where electricity is cleaner and cheaper. Analysts have also begun discussing capacity markets as a mechanism that could reshape cloud computing itself, rewarding flexibility in when and where workloads run.

Yet beneath the cleverness lies a simple ecological truth. Every habitat has limits. The AI era will not be measured only in parameters and benchmarks, but in substations, reservoirs and public trust.

In Vancouver, citizens are already seeking plain-language answers about proposed facilities and their effects, as local coverage in The Tyee makes clear. It is a small but revealing scene: the village at the forest edge, watching the machines arrive, listening to their low mechanical call, and wondering what the landscape will look like when the herd has passed through.

AI Data Centers Increase Water Stress in Western US - Let's  ·  Navigating energy management strategies in AI data centers -  ·  Got Questions About AI Data Centres in Vancouver? Here Are A
The Editorial

Corporate Leaders Urged To Stop Saying AI During Layoffs Until Employees Finish Being Replaced By It

Experts warn the sacred management term should be deployed only in contexts where it creates shareholder value and no one asks follow-up questions.

MOUNTAIN VIEW, CALIFORNIA — In a welcome sign that America’s executives may soon regain the verbal discipline required to announce mass job cuts without accidentally describing the machinery involved, workplace experts this week urged corporate leaders to stop casually invoking “AI” while firing people, at least until the room has cleared and security has collected the badges.

The recommendation arrives during a busy period for artificial intelligence, a phrase currently being applied with equal confidence to personal assistants, corporate restructuring, sustainability reports, refrigerator interfaces, and clerical mistakes in official documents that once would have been attributed to Darren in procurement.

Google, continuing its long civic project of ensuring no human being ever has to experience an unmediated thought, announced a slate of AI advances that reportedly include a personal AI assistant coming soon. According to reports on the announcement, the company’s assistant will presumably help users book flights, summarize emails, remember birthdays, and quietly build the evidentiary record needed to prove they were never particularly necessary in the first place.

This is progress. For years, the technology industry has forced consumers to perform the exhausting labor of pretending every new feature is revolutionary. Now, an assistant will be able to do that pretending on their behalf.

The corporate world, meanwhile, has discovered that AI is beginning to resemble sustainability: a large, attractive word that can be printed in annual reports, placed beside a stock photo of a hand touching a glowing sphere, and used to imply a future in which all current costs have been morally justified. As The Conversation noted, companies are hyping AI in much the same way they once talked up sustainability, with the crucial difference that sustainability at least occasionally involved a tree.

This has created understandable confusion in boardrooms. On one hand, executives are expected to demonstrate bold AI leadership. On the other, they are advised not to stand before 800 newly unemployed workers and say the company is “leaning into AI-driven efficiencies,” which employees may misinterpret as meaning the company has leaned into a machine and driven them into the parking lot.

The solution, according to the emerging etiquette, is precision. Leaders should say layoffs are due to “market conditions,” “strategic realignment,” or “a difficult but necessary decision,” all of which preserve the essential ambiguity that has allowed modern capitalism to function without everyone constantly looking directly at it. AI may still be mentioned later, ideally in a separate investor presentation featuring upward arrows.

CES 2026 has only strengthened the need for restraint. Day 1 reportedly brought the usual parade of new technologies, many of them intelligent enough to detect consumer preferences but not yet intelligent enough to leave consumers alone. The modern trade show floor now resembles a county fair for devices that have been given limited agency and a marketing budget.

Even governments have joined the broader movement toward machine-assisted embarrassment, as public outrage followed absurd errors in official documents in South Korea. Officials have not needed to confirm whether AI was involved, because the contemporary public has already learned to treat every institutional typo, hallucinated citation, or impossible date as either proof of automation or proof that automation would have been preferable.

The lesson is plain. Artificial intelligence may transform work, government, education, commerce, and the small remaining portion of daily life not already mediated by a login screen. But if leaders wish to maintain public trust, they must stop using “AI” as an all-purpose spell cast over whatever they were already planning to do.

At minimum, they should wait until after the severance packets are signed.

Google announces slew of AI advances, including a personal A  ·  Companies are hyping AI the same way they talked up sustaina  ·  Leaders shouldn’t toss around the ‘AI’ buzzword in layoffs.
The Office Comic  ·  Art Desk
The Office Comic  ·  Art Desk

Your AI Agent Is Not Your Friend: A Love Letter to Chaos and Accountability

The agentic revolution is here, and nobody — not the vendors, not the lawyers, not your CEO — has any idea who's responsible when it all goes sideways.

AUSTIN, TEXAS — Let me tell you about the moment I realized we'd all lost our minds. It was somewhere between my third bourbon and the fourth AI industry press release of the morning, and I found myself staring at a stack of headlines that, taken together, read less like technology news and more like a coroner's report for human accountability. The patient? Due diligence. The cause of death? Agentic AI, deployed in the wild before anyone thought to ask the obvious question: what happens when the robot eats your business?

Start with ServiceNow, who rolled out an AI control tower that, according to CIO.com, offers a hazy view of spend. Hazy. That's the word they used. In polite company. I would've used other words — words that cannot be printed in a family newspaper — because telling enterprises to trust an AI system with their software budget while simultaneously admitting the visibility is *hazy* is the kind of sales pitch that should get someone escorted from the building. But instead it gets a press release, a demo day, and a standing ovation at some conference in a city where it never rains.

And then — oh, sweet merciful chaos — there's the accountability vacuum. The Register, doing God's work in the tradition of journalists who actually give a damn, pointed out the existential legal nightmare lurking inside every agentic deployment: if an AI agent screws up while running your business, there's nobody to sue. Nobody. The vendor shrugs. The model shrugs. The cloud provider shrugs. It's a shrug all the way down, friend, and you — you specifically, the human being who signed the contract and approved the deployment — are holding the bag.

This is the part where I'm supposed to pivot and offer solutions. Ten easy steps. A framework. A thought leadership listicle. Instead, let me tell you what I actually believe: the companies building guardrails right now — the ones who care about explainability, cost visibility, and actually knowing what their AI is doing at 3 AM — are going to eat the companies who don't. Not because the market will reward virtue, exactly, but because the ones deploying half-baked agents into production systems with hazy spend controls are building a time bomb, and time bombs eventually go off.

At Trilogy's portfolio company CloudFix, the entire value proposition is built around making AWS spend legible — visible, auditable, *real*. That's the opposite of hazy. That's the philosophy that should govern every AI deployment in the enterprise: if you can't see it clearly, you cannot control it, and if you cannot control it, you do not own it. Something else does.

Out on the wilder frontier, Moltbook — a social network apparently populated entirely by AI bots talking to each other — launched this week like a philosophical thought experiment no one asked for. Bots. Running wild. Optimizing for engagement with an audience of other bots. It sounds insane because it is insane, but it is also, I submit, a perfect metaphor for where agentic enterprise AI is headed if the grown-ups don't step back in the room.

The revolution is real. The hype is real. The danger is very, very real. And as of this morning, the lawyers are still figuring out who to bill.

ServiceNow’s AI control tower offers hazy view of spend - ci  ·  The Real Pitfalls of AI Agents and Why They Need Guardrails  ·  Surprise! The One Being Ripped Off by Your AI Agent Is You -
On This Day in AI History

On May 25, 1961, NASA astronaut Alan Shepard became the first American in space aboard Freedom 7, a pivotal moment that accelerated the space race and drove decades of computing innovation in guidance systems and mission control technology.

⬛ Daily Word — Technology
Hint: Relating to computers and the internet, especially in the context of security threats.
Share this edition: 𝕏 Twitter/X 🔗 Copy Link ▦ RSS Feed