feat: Genie One managed-memory agent panel - #7
Open
zuckerberg-db wants to merge 123 commits into
Open
Conversation
…rcel-native SPN proxy - Vendor databricks/app-templates as a sparse submodule (agent-openai-agents-sdk, e2e-chatbot-app-next) with an agent/ overlay + assemble_agent.sh. - Add a slide-out Agent panel (store + trigger) to the sso-spn org view. - Embed the managed-memory Databricks App via a same-origin Vercel route (api/agent-proxy) that mints the current user's SPN token (guest/BYOD) and injects it as a bearer, streaming responses (chat SSE). No Go proxy / Cloud Run needed for the agent. - Chat UI overlay patches: base:"./" + a mount-prefix fetch shim so assets and /api/ calls resolve under the proxy subpath.
- proxy: buffer request body with Content-Length (chunked/duplex POSTs were rejected by the Databricks Apps front door with "Proxy error:") - proxy: inject <base href> + force next-themes light so the embedded chat UI resolves relative assets under the mount and matches Firefly's light UI - proxy: serve the injected HTML no-store and drop conditional request headers so per-request rewrites always reach the browser (app ETag never changes) - panel: point iframe at /api/agent-proxy (no trailing slash) to skip the 308 - build: exclude agent/, agent-build/, vendor/ from the Next TS build and add .vercelignore so the frontend build ignores the agent overlay + submodule
Add a dedicated /docs/solutions/agent page (with architecture sequence diagram) and Solutions-nav entry for the managed-memory agent panel, and document the agent panel + Genie One usage across README, CLAUDE, AGENTS, and .env.example. - New docs-site page src/app/docs/solutions/agent/page.tsx + public/solutions/agent/architecture.mermaid - Proxy implementation detail lives under Backend Configuration, not the overview - README: Agent Panel section, "How the agent uses Genie One", agent env vars - CLAUDE/AGENTS: Vercel-native proxy + submodule/overlay notes - .env.example: NEXT_PUBLIC_AGENT_ENABLED, DATABRICKS_AGENT_APP_URL
The parent repo gitignores agent-build/, and `databricks bundle deploy`
respects the enclosing repo's ignore rules, so sync found zero files
("no files to sync") and would ship an empty app. assemble_agent.sh now
runs a local-only `git init` on agent-build/ to scope the bundle to it.
Verified: `databricks bundle validate` now reports 0 "no files to sync"
warnings.
The Build & deploy section was happy-path only. Add the generic operational detail so anyone cloning the fork can deploy without tribal knowledge: bundle validate + the "0 files to sync" health check, the post-run HTTP 503 poll (container builds uv/npm/vite before serving), the Python 3.12 pin for the PyO3 cap, overlay-applied sanity checks, and why assemble_agent.sh git-inits agent-build.
The agent panel's /api/agent-proxy returns 400/401 unless the user's active org has a workspaceUrl and a userSpns mapping for the user's email. Spell this out in the "Enable it" section so it isn't a silent failure.
- add prerequisite section for provisioning the agent's Databricks resources (MLflow experiment + Lakebase via quickstart, wheels volume) - document the post-deploy Lakebase permission grant required for memory - add the required `bundle run` app resource key and readiness check via `databricks apps get ... RUNNING` - list the Agent-panel SSO->SPN + guest env vars for Vercel deployment - flag still-unverified claims (empty wheels volume sufficiency, lakebase name rules) inline for validation Flagged UNVERIFIED items are inferred, not yet proven by a negative deploy test.
Genie One is queried as the app's service principal, so it only returns UC data that SP can access. Without grants, data questions return empty. Document the required USE CATALOG/USE SCHEMA/SELECT (+ warehouse access), flagged pending verification of the exact minimal grant on a clean workspace.
… ordering Replace hardcodes with bundle variables/derivation and fix the deploy flow based on end-to-end cleanroom proof on a fresh workspace: - Parameterize wheels-volume + memory-store namespace as bundle vars defaulting to workspace.default (works on Default-Storage workspaces with no `main`); derive HOST/WORKSPACE_ID/GENIE_ONE_URL at runtime instead of storing them. - Sync pyproject.toml (not uv.lock) and install build deps offline from pre-vendored cp311 wheels via UV_FIND_LINKS (scripts/vendor_wheels.sh), avoiding the dead .dev proxy, the lagging .cloud mirror, and the 10-min startup wall. - Frontend Drizzle migrations need no manual Lakebase grant: the Postgres resource binding is CAN_CONNECT_AND_CREATE, which auto-provisions the app SP's Postgres role at deploy. Drop the pre-build grant step. - Add scripts/setup_memory_store.py and wire it into scripts/deploy_agent.sh: the durable memory is a UC memory store (not Lakebase) that must be created and granted READ/WRITE to the app SP — otherwise memory silently no-ops. - start_app.py builds the frontend before binding the port so failures are honest in the live app_status; deploy state goes green at container-start.
- GAP-19: okta plugin in auth-dynamic.ts was unconditionally constructed with process.env.SPN_AUTH_OKTA_CLIENT_ID!, crashing Next.js prerendering when the env var is absent. Now guarded: plugin is only registered when all three SPN_AUTH_OKTA_* vars are present. - GAP-20: NEXT_PUBLIC_BETTER_AUTH_URL baked at build time causes CORS on Vercel preview deployments. Updated .env.example with an explicit warning (commented out by default); updated README to say do not set this var unless using a stable custom domain on all deployments. - GAP-21: Guest login was documented as "Coming Soon." Added a Guest User Provisioning section to README with the correct 3-step API sequence (workspaces → spns → users), required fields, and accurate response shapes. - GAP-22: Added a Vercel deployment step documenting how to disable SSO protection on preview deployments. Corrected CLI command to the real `vercel project protection disable <name> --sso` (v54+ syntax).
BOOTSTRAP.md covers all 22 documented gaps with corrected commands, a full interactive-auth flow (browser OAuth for Databricks, Neon, Vercel, GitHub — no manual key entry), and a proven vs. inferred gap reference table. scripts/bootstrap.sh implements the runbook as an executable shell script with --dry-run and --stop-after=N flags for iterative testing.
Fixes missing step for creating a Databricks service principal with M2M OAuth credentials for the guest login flow. Phase 9 now reads client ID and secret from keyring instead of using a placeholder. Also adds DATABRICKS_ACCOUNT_ID to Phase 0 inputs.
…9 keyring reads - Phase 8b split into Tier 1 (guest login, required) and Tier 2 (admin Databricks OAuth, placeholder-safe for guest-only verification) - Documents why U2M vars don't crash the build (plain object vs okta() call) - Phase 8d stores PREVIEW_URL and GUEST_API_SECRET in keyring - Phase 9 loads all vars from keyring — no implicit shell carry-over
- NG-1: workspace list requires PATH arg (workspace list / --profile)
- NG-2: delete stale HOST/WORKSPACE_ID/GENIE_ONE_URL yml-edit instructions;
replace with note that these are runtime-injected; add catalog/schema
override guidance only when non-default
- NG-3: AGENT_APP_NAME default → firefly-openai-managed-mem-v2 (bundle hardcodes this)
- NG-4: remove stale re-assemble step from Phase 4 (wipes quickstart .env + wheels)
- NG-5/6: run bundle deploy/run from agent-build/; add resource key
(agent_openai_agents_sdk) to bundle run command
- NG-7: pass --memory-store to setup_memory_store.py
- NG-8: UC permissions → PATCH /api/2.1/unity-catalog/permissions/catalog/{name}
(POST is not a valid method)
- NG-9: warehouses list -o json returns bare array (not {"warehouses":[]}); use
/api/2.0/permissions/warehouses (not preview/sql endpoint, which returns 405)
- NG-10: SP create output uses SCIM camelCase applicationId (not application_id)
- NG-11/14: replace account-API/curl/UI-fallback block with
service-principal-secrets-proxy create (workspace-level, no account admin needed)
- NG-12: DB_URL already stored in keyring; ensure drizzle step reads it
- NG-13: add pnpm install before drizzle-kit push (node_modules absent by default)
Also fix unclosed code block fences in Phases 3a, 3d, 4, 5.
GitHub strips cursor:// links from README markdown, so the badge opened the shields.io image instead of launching Cursor. Route through cursor.com/redirect so the deeplink handoff works from github.com.
…adge cursor.com/redirect does not exist (404). GitHub also strips cursor:// links, so use Cursor's documented HTTPS prompt handoff to open the genie-agent bootstrap workflow.
Add an explicit STOP gate so agents must confirm every required input before Phase 1 (including read-only probes). Move [ASK] items to a checklist with [ASK — REQUIRED, BLOCKING] markers. Default clone directory is now the current working directory instead of ~/Projects/firefly.
- Phase 6c: check UC tables only; defer seeding to end-of-runbook guidance - Phase 6b: grant guest SP CAN_USE on warehouse and agent app - End sections: next steps when no UC data; agent drafts and files GitHub issues
Centralize solution metadata, add /docs/solutions hub, and link Agent Panel from architecture pages, cross-links, nav, homepage, and README so it is no longer missing from the lakehouse-apps-proxy documentation grid.
neonctl projects create --output json returns {"project":{"id":...}}, but
Phase 7 read json['id'] and got an empty/wrong value, breaking downstream
DATABASE_URL setup. Read project.id with a flat id fallback for older
neonctl versions.
Fixes #16
Phase 8d captured the preview URL with `grep -E 'https://' | tail -1`, which matched Vercel's trailing "To change the domain… https://vercel.com/…" help line instead of the deployment URL. Phase 9 guest API calls then hit a malformed PREVIEW_URL. Anchor the match to the .vercel.app host. Fixes #15
…al edit
Phase 3b only warned the user to hand-edit agent-build/databricks.yml when
UC_CATALOG/UC_SCHEMA differed from the workspace/default bundle defaults;
skipping the edit broke the deploy or misrouted Genie context. The bundle
already exposes catalog/schema as variables (agent/databricks.yml header),
and DATABRICKS_MEMORY_STORE is the template
"${var.catalog}.${var.schema}.${var.memory_store_name}". Thread the Phase 0
inputs through `bundle deploy/run --var` so no file mutation is needed and
the memory store resolves automatically.
Fixes #12
Phase 1c called `vercel login` without checking the CLI was present, so a fresh machine failed at the OAuth step. Add an install check (npm i -g vercel) mirroring the neonctl/pnpm pattern, so no CLI is assumed pre-installed. Fixes #8
vercel link (Phase 8) needs a repo the deployer can write to, but the clone points at databrickslabs/firefly (no external write access). After cloning, create or reuse a user-owned fork and push genie-agent to it, so Vercel's Git integration links to that remote. Honors SKIP_GITHUB_PUSH and GITHUB_FORK. Fixes #17
On machines behind a corporate MITM proxy (Zscaler etc.), quickstart.py and the Databricks SDK fail TLS verification and hang until timeout. Phase 0 now accepts a combined PEM (via prompt, or preset TLS_PEM_PATH for CI) and exports REQUESTS_CA_BUNDLE / SSL_CERT_FILE / NODE_EXTRA_CA_CERTS for the session. Fixes #13
… via npm (#9) Reorders Phase 1 so pnpm is installed before the CLIs and frontend build that depend on it, then Vercel, GitHub, Neon, Databricks. - pnpm moved to 1a (was 1d) - GitHub CLI: auto-install from the official release archive into $HOME/bin when absent (no Homebrew dependency), then auth. Deviates from the issue's `brew install gh` suggestion because the target audience is not guaranteed to have Homebrew. - Neon CLI: `npm install -g neonctl` instead of `brew install neonctl` for the same reason. - Install steps are DRY_RUN-guarded so --dry-run never hits the network.
The first genuinely fresh target (no pre-installed CLIs, no pre-existing Neon
project) reached Phase 9 with six phases failed. Root causes, and what changed:
BOOTSTRAP.md called store_secret / read_secret and defined neither. The only
implementation lived in bootstrap.sh and took (VARNAME, _, KEY) while every
runbook call site used $(read_secret KEY), so copying it across did not work
either. An agent following the runbook wrote its own with bash-only ${!key} and
got "bad substitution" under the macOS default shell.
Phase 1b/1e "installed" the Databricks and GitHub CLIs with a comment and `: ;`.
Invisible on any machine that already had them.
Phase 7 parsed `neonctl projects create` output for a top-level id; the id is
nested under project. bootstrap.sh was fixed on 2026-07-21; the markdown was
rewritten that evening and kept the broken accessor. Because create succeeds
server-side before the parse fails, the retry orphaned a second project.
Phase 3 died on a 503 from public PyPI with no index configured, cascading to
Phases 4-6 and 9, and surfacing only as a raw uv stack trace.
Changes:
- Add scripts/lib/runbook.sh: state helpers, CLI installers, CLI-output parsers,
and the Phase 3e/4 assertions. One implementation, sourced by both the runbook
and bootstrap.sh, so they cannot drift. read_secret is one-argument and writes
to stdout, matching the documented contract; no ${!key}, so it works in zsh.
- Add firefly_preflight_pypi_index: name the cause at Phase 0a and gate Phase 3a
on it. It reports the two knobs and picks neither -- hardcoding a mirror would
also stamp that host into any regenerated uv.lock.
- Resolve the Neon project id by re-listing after create instead of parsing the
create response, and fail closed on an empty id.
- Refuse to deploy a bundle still holding the placeholder experiment id, so the
failure names Phase 3a instead of returning "Node ID ... does not exist".
- Replace Phase 3e's greps with check_sync_exclude_rules. The old greps matched
the explanatory comment, so bootstrap.sh failed a correct config.
- Move the constraints.txt export from assemble_agent.sh to vendor_wheels.sh.
assemble deletes agent-build/ and the template ships no uv.lock, so on a fresh
clone that branch could only ever warn; vendor_wheels.sh is where uv lock runs.
- Generalise check-runbook-invariants.sh from string matches to structure: every
sourced lib is bash+zsh-checked, every helper the runbook calls must resolve,
every bash block must parse in both shells, and no brace block may be a stub.
All three new checks are negative-tested.
- Add scripts/test-runbook-lib.sh (14 assertions, hermetic).
- Drop the 1a-1f labels from bootstrap.sh Phase 1: its install order differs from
the runbook's, so "1b" named a different tool in each file.
The 2026-07-25 validation run surfaced a second way to reach Phase 4 with an unpatched bundle, distinct from quickstart failing outright: the agent's shell wrapper backgrounded quickstart at exactly 30.0s and returned its own exit 0. The agent read that 0, treated the phase as done, and moved on while Lakebase provisioning was still in flight. Phase 4's guard caught it, but the failure belongs to the phase that owns it. Assert completion at the end of 3a too, and say plainly in the runbook that a zero exit from a wrapper is not evidence quickstart finished -- the last line before the long pause is "Creating new Lakebase", which is the start of provisioning, not the end.
…-20260725 fix(bootstrap): close the fresh-install defects found on 2026-07-25
Vercel token sourcing had drifted. scripts/bootstrap.sh has preferred
$VERCEL_TOKEN over the CLI's auth.json since someone noted that "a
single hardcoded path made it a silent 404 or a hard stop for anyone
storing auth elsewhere", but BOOTSTRAP.md kept the hardcoded lookup, so
the fix never reached anyone following the doc. auth.json only exists
after an interactive `vercel login`, which makes the documented path fail
outright in CI and for token-based setups.
GUEST_API_SECRET had no recovery story. read_secret only works inside the
session that minted it, and `vercel env pull` looks like the obvious
fallback but returns a redacted ~11-character placeholder. The runbook
stored that as if it were the value and every guest-login call then
failed 401 with nothing explaining why. openssl rand -hex 64 is always
128 characters, so a length check turns the silent 401 into a remint.
The repo's own guards could not run on a stock Mac. Eleven `rg` calls in
check-runbook-invariants.sh and one in test-bootstrap-phase1a.sh made
both abort where ripgrep is absent, so the checks silently validated
nothing in exactly the clean-room they exist to protect. Converted to
grep -E and sed -nE; verified all six guards pass with and without
ripgrep on PATH.
Fixes two guard defects found while doing it: the stub detector read
${#VAR} as a comment-only brace block and rejected correct shell, and
the new GUEST_API_SECRET check matched its own diagnostic echo rather
than the comparison, so it passed with the validation deleted. Both new
checks are verified to fail when their fix is reverted.
…his app (#81) Closes #78 When a workspace enforces an IP access list, the deployed frontend is a third party to it: Vercel calls Databricks from its own egress addresses, which are not the customer's office or VPN ranges. Databricks refuses with a bare 403 and the dashboard renders "Failed to load SQL warehouses - Databricks API error: Forbidden", which reads as a broken deployment. Both wrappers built that message from response.statusText alone and threw away the header where Databricks explains itself: X-Databricks-Reason-Phrase: Source IP address: 18.209.31.242 is blocked by Databricks IP ACL for workspace: 7474652207956472 Now the reason is surfaced, and the IP-ACL case is named explicitly so nobody debugs application code for an enterprise control. Judged on the user-facing `error` field: `details` already carried the reason, but the UI shows `error`. BOOTSTRAP.md gains a pre-flight warning, because the useful moment to learn this is before deploying, not while staring at a 403. Verified against a live blocked deployment: the pre-fix response scores "unattributed", the post-fix shape scores "attributed". Invariant 10 guards it and is verified to fail when the header read is removed - it initially passed on its own comment, which is why it now matches the actual headers.get call.
Closes #79 Guest links are single-use and expire ~10 minutes after minting; a refresh or back-navigation also consumes one. Phase 9 mints one roughly 20 minutes into a run, so it is usually dead before a human clicks it - and at that point the operator has a working deployment and no way in. The summary previously offered only prose: re-run the three "Guest login" POSTs above. That is not a recovery path. /api/guest/users needs an spnId, which needs a workspace record first, so following it means scrolling back and re-deriving two ids from intermediate responses. scripts/new-guest-link.sh replays exactly those three calls from state.env and prints a fresh URL in about two seconds. --open matters: copy-pasting into a tab that already has the app loaded can consume the link before it is read. It also rejects a short GUEST_API_SECRET up front, since the redacted placeholder from `vercel env pull` 401s every call. Follow-on to #59, which fixed the message on a dead link. This provides a live one. Verified against a live deployment: the script mints a link and that link verifies HTTP 200. Invariant 11 fails both when the script is removed and when the runbook stops referencing it.
…run bug (#19) (#76) The hermetic suite added in #75 checks the shape of Phase 8 — origin read not constructed, no alias pinned, deploy explicitly --prod. It cannot catch a behavioural change on Vercel's side, and it is not what found the re-run regression. This is. It deploys for real and reads BETTER_AUTH_URL back out of the running deployment via a minimal Next.js fixture, then does it a second time against the SAME project. That second run is the whole point: a bare `vercel deploy` is production only on a project's FIRST deploy, so a create-and-destroy test — every run a first run — structurally cannot see the bug where run two points BETTER_AUTH_URL at a per-deployment preview host. Both of #19's earlier fixes passed suites with exactly that blind spot. The first was validated with a unique project name, so the collision never happened; the second with a fresh project per run, so the re-run never happened. Self-contained: the three SSH helpers are inlined, so it carries no dependency on out-of-tree scratch tooling, and HOST_REPO defaults to the checkout it lives in. Preconditions gate on behaviour (deploys --prod, pins no alias) rather than on a particular implementation's step wording, so a competing fix can still be measured instead of rejected. Cannot run in Actions — needs a macOS host, Tart and a Vercel login — so it lands under tests/manual with a README covering setup, the knobs, and how to investigate a failure. Note that repository Actions are currently disabled, so the hermetic suite is not running automatically either; both are manual gates today. Verified: passes both stages against merged genie-agent (next-demo -> next-demo-livid-alpha.vercel.app, Vercel CLI 56.3.1), and the precondition correctly refuses the pre-#71 alias-pinning implementation.
…hase 0 (#83) (#84) * feat(bootstrap): offer sample-data seeding and Genie space setup at Phase 0 (#83) A fresh workspace finished bootstrap "successfully" with an empty $UC_CATALOG.$UC_SCHEMA, so Genie could not answer a single data question. Phase 6c detected that and the runbook ended with homework. The user was never offered a way to fix it during the run: Phase 6c said "do not create tables on the user's behalf" and the closing section said "unless the user explicitly asks", but no Phase 0 input mentioned seeding, so that escape hatch was unreachable. Phase 0 now asks. SEED_SAMPLE_DATA (default yes) copies samples.wanderbricks into an empty schema. GENIE_SPACE_IDS (default None) lets a user point the agent at spaces they already curated, and when they do, GRANT_GUEST_SPACE_ACCESS offers the guest SP CAN_RUN on those spaces plus SELECT on the tables they reference. CREATE_GENIE_SPACE (default yes) otherwise builds one over the seeded data. Phase 6c acts on those answers via scripts/genie-data-setup.sh, which scripts/bootstrap.sh calls identically — the runner had no Phase 6c at all, so the automated path could finish with no data. Both surfaces now call one implementation, for the same reason scripts/lib/runbook.sh exists. Safety rules, all guarded: * CREATE TABLE IF NOT EXISTS only. A schema holding tables we did not create reports already-populated and is left alone. * A Genie space is "ours" only on an exact title match, so a re-run reuses it and a foreign space covering the same schema is never patched or trashed. User-supplied ids are the sole exception. * GENIE_MCP_MODE and GENIE_SPACE_ID move together or not at all: agent.py raises ValueError on space + empty id and the app fails to boot. Space mode is only claimed after the space is confirmed readable. Also fixes the attribution link, which pointed at Genie One even when the agent was scoped to a space — the destination never saw the question. It now resolves to /genie/rooms/<id> and the UI labels it from the path. Invariants 12-14 cover the mode/id coupling, seeding staying a real user choice in both surfaces, and Phase 6c parity. Each was verified to fail when its fix is reverted. test-bootstrap-phase1a.sh gains the three new prompts, which it caught itself by mis-assigning a later answer. * fix(bundle): an empty GENIE_SPACE_ID default breaks the Phase 4 deploy (#83) With `genie_space_id: ""`, the bundle renders the app env entry as `{"name": "GENIE_SPACE_ID"}` with no `value` key, and the Apps API refuses the whole deploy: Must specify environment variable source using either value or valueFrom That breaks Phase 4 on the DEFAULT path — Genie One, no space — which is what most deployments do. It hit six of nine E2E runs and every one still scored clean, because the agent following the runbook diagnosed it, passed a placeholder, and the app ended up RUNNING. E2E-HEALTH only asserts the end state, so an agent that repairs the runbook hides the defect from it. The variable now defaults to the non-empty sentinel "none", which the Apps API accepts, and agent.py treats "none"/"null"/"" as unset so GENIE_MCP_MODE=space still raises rather than querying a bogus space. The coupling guarantee is unchanged; only where emptiness is detected has moved. Invariant 12 now rejects an empty default, verified to fail when reintroduced. * fix(bootstrap): repair the six runbook gaps the E2E agent kept reporting Every one of these was reported by the agent, in its own RUNBOOK GAPS section, run after run. Nothing read them. Frequencies are across 12 E2E transcripts. Phase 5 Managed Memory preflight (9 runs). The preview is enabled per-workspace by Databricks; without it the phase failed with NotImplemented and no explanation. It now checks first, says so plainly, and lets the rest of the bootstrap finish — only cross-session memory is lost. CA bundle written to /proxy-ca-bundle.pem (5 runs). init_inputs_dir read INPUTS_DIR at call time while the default was applied only at source time, so a caller that exported it empty got an unwritable root path — which then broke curl, the installers, and uv's CPython download with UnknownIssuer. assert_bundle_quickstart_ran false-failed (3 runs). Same root cause: an empty FIREFLY_PLACEHOLDER_EXPERIMENT_ID makes the check `grep -q ""`, which matches every line, so a bundle quickstart had already rewritten correctly was declared broken and the reader was sent back to Phase 3a. The UC grants could not be run as written (2 runs). They were commented-out SQL telling the reader to open a warehouse session; a backquoted principal inside `--json "..."` is command substitution, so nobody could paste them and the grants were quietly skipped. They now execute through firefly_sql. A panicking deploy read as success (2 runs). CLI v1.9.0 can nil-deref on bundle deploy and still exit 0, so later phases failed opaquely on JSON-parsing an error string. Phase 4 now asserts the app exists, and names stale bundle state as a cause. Phase 3c assumed the schema existed, and Phase 8 accepted an empty DATABRICKS_AGENT_APP_URL — deploying a frontend pointed at nothing, which reads as a frontend bug rather than a failed Phase 4. Invariants 15-19 guard all of it, each verified to fail when its fix is reverted. 15 exists because this suite reported "All runbook invariants hold" while bootstrap.sh had an unterminated `if`: the one file guaranteed to be executed was the one file never parsed. * fix(bootstrap): close the last two shell-state gaps GUEST_API_SECRET (and the other Phase 8 secrets) were minted at the top of the phase but only written to state.env at 8d. A shell that died in between left Vercel holding a value the local side no longer knew — and `vercel env pull` returns an 11-character redacted placeholder, not the secret, so the only recovery was a remint. They are now stored at mint time. V_TOKEN / V_ORG / V_PROJ were derived in Phase 8a and reused in 8e. Run 8e in a fresh shell and the curl sends "Authorization: Bearer " with nothing after it, so the step reports that production does not serve $APP_ORIGIN for a deployment that is entirely correct — a false failure on the exact check added for #19. Both phases now call firefly_vercel_context, so there is one derivation and a new shell cannot change the answer. Invariant 8 was checking for the $VERCEL_TOKEN preference as literal text in BOOTSTRAP.md; it now follows the logic into the library, and still fails if the preference is dropped there.
…xisted for Caught by the agent on the very run that shipped it: "Phase 5's preflight reported the memory preview as `on`, but setup returned NotImplemented." The probe called `/api/2.0/memory-stores`, which returns `Error: Not Found` on this workspace — that matches none of `not enabled|NotImplemented|501`, so the check fell through to "on" every time. The path is wrong, so the probe could never have worked; it only ever added a confident wrong answer in front of the same failure. Phase 5 now runs setup_memory_store.py and classifies what comes back: success, a missing preview (skip with an explanation, bootstrap continues), or a real error (surface it). The operation is the only reliable signal for whether the operation will work. Invariant 18 was checking for the preflight variable, so it happily passed a probe that never functioned. It now requires both surfaces to classify the failure and forbids the broken probe from returning — verified to fail when it is reintroduced.
…roblem Reported from a real fresh-workspace run. Phase 6c said samples.wanderbricks was "not readable from this workspace", which reads as a permanent entitlement problem. It was a race: 12:04:46Z Phase 4 finished ~12:05:00Z Phase 6c probed samples.wanderbricks -> empty 12:05:39Z the samples.wanderbricks schema was created 12:05:41Z its first tables appeared A re-run eight minutes later seeded all 16 tables. The samples catalog is provisioned asynchronously on a new workspace and this phase arrived ~40 seconds early. Two defects made that unreadable. The probe used `2>/dev/null`, discarding the server's own message, and then reported ONE status for four different causes: schema absent, schema empty, real permission denial, and warehouse/SQL error. So the output could not distinguish "wait a minute" from "you lack SELECT" — and it picked the wording that sends people hunting for entitlements they already have. Phase 6c now keeps stderr and classifies: source-not-ready, source-empty, source-denied, source-error, each with the server's message. It also waits up to FIREFLY_SEED_SOURCE_WAIT (180s) for the source to appear, which would have made the reported run succeed outright — while breaking out immediately on a denial, since that will never resolve by waiting. "Next steps — no UC data" no longer applies to the not-ready cases: sending someone to fix grants when the data is merely late is the same wrong turn in document form. Invariant 20 requires the classification and the wait, and fails if the discarded-stderr probe returns.
The link was wrong in two independent ways, and each is enough on its own. Its audience is GUEST users. They reach the app through a tokenised one-time login and have no Databricks workspace access at all, so a workspace URL is dead for every one of them — it renders as an inviting external link and goes nowhere they can follow. And it said "Genie One" while GENIE_MCP_MODE now defaults to a space, so it named a backend that never saw the question. Labelling it from the URL path fixed the wording without touching the real problem: a dead link with the right label is still a dead link. Attribution is plain text now — "Powered by Genie", no href. Removed with it: genieOneUrl through /api/config, the context field, four call sites in message.tsx and chat.tsx, buildGenieOneUrl, and the GENIE_ONE_URL env var, which is gone from the bundle comments, the runbook, the runner's note, and the user-facing env table in src/app/docs/solutions/agent/page.tsx. Genie's own citation links are untouched — those are answer content, not attribution, though they share the guest-access problem and are worth a separate look. Invariant 21 fails if genieOneUrl, a hand-rebuilt /one?o= link, or the env var returns.
…d earlier Found by the E2E agent on the first run after the gap prompt started asking about detours and misleading output, not just workarounds. It reported: uv install skips checksum verification on macOS: installer prints "skipping sha256 checksum verification (it requires the 'sha256sum' command)" — macOS has shasum That is a supply-chain check quietly declining to run. The astral.sh installer verifies its download with sha256sum; macOS 14.8.7 (the clean test VM, and plenty of user machines) has no sha256sum anywhere, only shasum. So the installer says it is skipping verification and installs the binary anyway, with the message buried mid-install. This is the DEFAULT path, not an edge case. Worth recording why it took a VM to find: this laptop is on a newer macOS that ships /sbin/sha256sum, so the problem is invisible here. The first fix I wrote appeared to work locally because the helper correctly no-opped — it never exercised the shim at all. Confirmed against the VM (macOS 14.8.7: absent) and then re-tested with /sbin excluded from PATH. firefly_ensure_sha256sum installs a two-line shim delegating to `shasum -a 256`, which is byte-identical for both hashing and -c check mode (verified), and no-ops where sha256sum already exists. It restores the installer's own verification rather than working around it. Invariant 22 requires the shim to precede the install in both surfaces — after it would change nothing — and fails if it is moved or removed.
…reated Passing --app-name for an app that already exists makes quickstart bind Lakebase from that app's existing configuration and ignore --lakebase-create-new. The requested project is never created, Phase 3a still reports PASS, and every later summary prints $LAKEBASE_NAME — a resource that does not exist. Caught concretely across two passes of one run: pass 1 created firefly-lb-0727083127, pass 2 asked for firefly-lb-0727090103, quickstart bound the first, and the summary named the second. Anyone going to look for the name they were given finds nothing there. The bound project is discoverable — quickstart writes agent-build/.env and patches databricks.yml, and both carry the endpoint path projects/<name>/branches/<name>-branch/... So Phase 3a now reads the name that was actually bound, warns when it differs from the request, explains why (an existing app wins), and reports the bound name from then on. Reality wins over the request, out loud. Same class as the "not readable from this workspace" message: output that is confident and points at the wrong thing. Invariant 23 requires the reconcile in both surfaces and requires it to run AFTER quickstart, where there is something to read.
Both found by the E2E agent on iteration 1, and both are the class that only shows up when you ask "did any message point you at the wrong cause". pnpm advertises `Update available! 10.34.5 -> 12.0.0-alpha.16` after every install — the EXACT alpha the pin exists to avoid, because it ignores onlyBuiltDependencies and fails with ERR_PNPM_IGNORED_BUILDS. A reader who follows the tool's own advice breaks their build. That is worse than noise: the advice is the failure. NPM_CONFIG_UPDATE_NOTIFIER=false is now exported alongside the pin (one place, since the pin is the single source of truth), and the runbook says to ignore the banner if it appears in a shell that skipped Phase 0a. `neonctl me` reports `Projects Limit: 0`, which reads as "you cannot create projects". It is an unset quota field; create and reuse both work with it at 0, as Phase 7 goes on to prove. Documented so nobody goes hunting for a plan upgrade that is not needed. Invariant 24 requires the notifier suppression and the runbook note. Iteration 1 also showed 0 RECURRED gaps: all 13 fixes marked fixed in the harness registry held on a live run, including the uv checksum shim and the Lakebase reconcile.
…in advance Iteration 1 pass 2 found both holes in the fix I shipped an hour earlier, which is the loop working as intended. The reconciled name was only `export`ed in the current shell. Non-secret answers are re-sourced from inputs.env on a resumed run or a fresh shell, so the requested-but-never-created name came straight back into the summary. It is now persisted via firefly_store_input — added to the shared library because store_input lived only in bootstrap.sh, so the runbook had no way to persist anything. That asymmetry is the same drift the library exists to prevent. And the create path still read as though it would provision the named instance, right up until the post-hoc warning. If the app already exists, its binding is going to win and that is knowable BEFORE quickstart runs, so firefly_warn_existing_app_wins says so up front. Detecting a surprise after the fact is better than not detecting it; not being surprised is better than both. Invariant 23 now requires the reconcile, the persistence, and the advance warning. Each verified to fail when removed.
Seven separate E2E agents flagged this, each rediscovering it, because the runbook never mentioned it once: Error: Failed to get package info: Error: Failed to fetch dist-tags from npm It is the Vercel CLI's own background update check reaching public npm, which a corporate proxy blocks. The command then succeeds. But it goes to stderr, carries an "Error:" prefix, and prints BEFORE the real output — so it reads like an auth or install failure, and it has stopped readers mid-phase. I had it marked "accepted" in the harness triage registry, which quietly meant *I* had accepted it while the reader was still being ambushed by it every run. That is not the same thing, and seven reports were the evidence. Now documented at Phase 1d where vercel is first used, with the operative rule: judge vercel by its exit code and its output, never by that line. Also exports VERCEL_TELEMETRY_DISABLED=1 as best-effort suppression, marked UNVERIFIED in the code — the failure only reproduces on a proxied network, so whether it silences the message is not something I have confirmed. The note is the part to rely on. Invariant 25 fails if the explanation disappears.
…ing LAKEBASE_NAME as settled Iteration 2 pass 2. The agent confirmed the Lakebase reconcile now "warned and reconciled correctly", and pointed at what was left. The Phase 0 input table still presented LAKEBASE_NAME as the canonical value, so a reader carries the requested name in their head until 3a quietly replaces it. It is now labelled a request that an existing app's binding overrides — in both the ask and the defaults table. And the IP allowlist was only reported at Phase 9. Phases 1-8 all succeed against a workspace that has one enabled: the app deploys, the frontend deploys, guest login works, and then every Databricks data call from Vercel 403s. Meeting that after everything looks fine is how it gets read as an application bug — which is the defect #78 already exists to prevent. New Phase 0b checks it in one command, before any of it is built.
…there Iteration 3 caught a defect I introduced in iteration 2. I moved the IP-allowlist check to Phase 0 so a reader would meet it before building anything — and it calls `databricks`, which Phase 1b installs. It could not run as written; the E2E agent had to defer it past firefly_install_databricks_cli itself. Early placement is worthless if the step cannot execute there. It now sits at the end of Phase 1b, the first point where the CLI exists AND is authenticated, which is still before every phase that builds anything. Invariant 26 fails if any runbook step invokes the databricks CLI before Phase 1b installs it — the general form of the mistake, not just this instance.
The agent's fuller diagnosis of the Phase 0b ordering bug named a second flaw that survived my fix: databricks api get … 2>/dev/null yields empty stdin; the Python one-liner raises JSONDecodeError: Expecting value. That reads like a bad/empty API response or auth failure; the actual cause is simply that the CLI is not installed. That shape — discard stderr, pipe into a parser that assumes success — converts every failure into the same misleading message. I wrote it three separate times today: the seed-source probe, the Phase 5 preflight, and this allowlist check. The allowlist check now keeps stderr and says which of "no response", "the API said X", or a real answer it got. Invariant 27 forbids the shape in general, and on its first run it found TWO more pre-existing instances I had not touched — the Phase 6b service-principal lookup and the Phase 7 Neon org id — where a failed call became a traceback instead of "no match". Both hardened. Written the invariant before, so the pattern cannot come back a fourth time.
… ran
A trial workspace answers the ip-access-lists API with "IP access list is not
available in the pricing tier of this workspace". Phase 1b surfaced that
correctly. Phase 9 discarded stderr, failed to parse, and printed
ok: no enabled IP allowlist on this workspace - the app can reach it
Same API, opposite conclusion, and the wrong one was a confident all-clear on
the control that decides whether the data plane works at all -- printed in the
phase people read when something has already gone wrong.
Both phases now call one helper, firefly_ip_allowlist_status, which returns
three outcomes rather than two: none, enabled:<labels>, or unknown:<reason>.
"Could not determine" is a distinct answer from "there is none", so Phase 9
now says so and points at itself as the first thing to re-check on a later 403.
The existing invariant missed this in two ways, both now covered: the discard
and the parse were separate statements, and `except: raise SystemExit` counted
as a guard while printing nothing -- which is what produced the empty string
the caller read as "none". A handler that exits silently does not prevent a
wrong answer, it manufactures one. Empty fallbacks that feed a self-correcting
action rather than a claim are still allowed, but must now declare themselves
with SAFE-EMPTY and say why.
Found by the first fresh-trial run; a pricing tier is a condition the reusable
test workspace cannot produce.
…failure Follow-up to the previous commit, from the same trial workspace. Collapsing the pricing-tier response into "could not check the IP allowlist. The API said: Error: ..." is accurate but reads like expired auth, and sends the reader after a problem that does not exist. The tier simply has no IP-allowlist feature, so none can be enabled and the data-plane risk the check exists for cannot apply. firefly_ip_allowlist_status now distinguishes that from a real failure with a fourth outcome, unavailable:<reason>, and both phases report it as reassurance. A genuine failure (expired token, no response) still reads as unknown and still refuses to imply safety. Also: Phase 0a's confirmation greps the two CA vars it omitted. The bridge sets CURL_CA_BUNDLE and REQUESTS_CA_BUNDLE and Phase 9's smoke tests depend on the first, but the documented `env | grep` listed neither, so a reader could not verify the trust var the later phases rely on. Invariant 28 now derives the expected set from corp-network.sh rather than restating it, so adding an export cannot silently outrun the confirmation step.
…e 0 claims to BOOTSTRAP.md told readers those dirs "are added to PATH in Phase 0", the comment inside firefly_bridge_corp_network said "on PATH from Phase 0 onward", and Phase 0a promised that re-sourcing the helpers is enough to restore a fresh shell. All three were false. The function only mkdir'd the directories; the export lived in scripts/bootstrap.sh, which a reader following the runbook by hand never runs. The cost landed after Phase 1: databricks, uv and gh were not findable in any new shell, and the reader had to invent `export PATH="$HOME/bin:$HOME/.local/bin:$PATH"` at the top of each one. Two independent runs reported it, the second calling it an undocumented workaround. The export now lives in the sourced helper, prepended idempotently because the file is re-sourced once per shell across ten phases. Invariant 29 proves the claim against the helper in a fresh bash and zsh, so a claim that only holds for the runner cannot pass again.
… deleted"
With --app-name, quickstart.py answers an app that has not been created yet with
Could not fetch app details: App with name '...' does not exist or is deleted
and then suggests `databricks bundle deployment bind` / `bundle deploy`. On a first
run nothing is wrong: Phase 4 creates the app and no bind is needed. But the message
reads as a recovery path for an app someone deleted, and a run reported following it.
firefly_warn_existing_app_wins already pre-empted the opposite case, where an existing
app's Lakebase binding silently wins. It now covers both directions, so the reader is
told which way the run will go before quickstart's own output has to be interpreted.
Invariant 30 guards the pre-empt and specifically its absent-app branch, since a
warning that only fires when the app exists would leave this exactly as it was.
…as fixing
Self-inflicted, and worse than the bug it replaced. firefly_ip_allowlist_status did
raw="$(databricks api get /api/2.0/ip-access-lists --profile "$prof" 2>&1)"
On a tier without the feature the CLI exits 1, and an assignment from a failing
command substitution aborts the shell under `set -e` -- in bash as well as zsh, not
just zsh as first reported. So the branch written to handle that tier gracefully
instead stopped Phase 1b and Phase 9 dead on exactly the workspaces that reach it.
A run had to restart Phase 1 mid-phase (1a-1b done, 1c-1f re-run), take a second
pass at Phase 9, and wrap the call in an undocumented `set +e` both times. Printing
a wrong "ok" was less costly than this.
firefly_sql had the same shape: `out="$(...)"; rc=$?` with a full rc check below
that errexit never reached, so its own diagnostic was unreachable in precisely the
case it exists for. Four detection probes in corp-network.sh -- where a non-zero
exit is the normal "not configured" answer -- had it too.
Invariant 31 asserts each helper still EMITS its diagnostic under `set -e` in both
shells. Asserting mere survival cannot fail: calling it as `fn || true` suppresses
errexit for the whole function body, which is how the first version of this
invariant passed against code I had deliberately broken.
…loys
Phase 6c redeploys the app with genie_mcp_mode=space immediately after Phase 4
deployed it, and the Apps API refuses the overlap:
400 Cannot update app ... as there is a pending deployment in progress for
less than 20 minutes
The run recovered because `bundle run` waits, but the error arrived mid-phase next
to an unrelated "WAL recovery failed" and read as a hard failure in a phase that had
otherwise worked. firefly_wait_app_deploy_settled polls until the active deployment
is terminal, then the deploy proceeds; on timeout it still attempts the deploy,
because a stuck poll must not be the thing that stops the phase. Invariant 32
requires the wait to appear BEFORE the space-mode deploy in both the runbook and the
runner, so the race cannot come back for whichever one was missed.
store_secret dropped only `^export KEY=`, so a legacy `KEY=...` line or one with
leading whitespace survived and state.env held two assignments. Sourcing takes the
last, which is why Phase 9's summary printed a stale GENIE_SPACE_ID while the
deployment had used the correct one. It now drops every form, and read_secret says
so when it still finds duplicates instead of returning one of two answers silently.
That warning goes straight to stderr rather than through warn(), which echoes to
stdout: every caller reads VALUE="$(read_secret KEY)", so routing it through warn
folded the warning text into the value. Invariant 33 catches that, and did -- it
failed against this change before the printf was made explicit.
Also documents that a partly populated schema is normal and triggers incremental
completion rather than a seed from empty.
… a shell trap
The runbook only ever said that bootstrap.sh saves answers to inputs.env, and showed
nothing to anyone working the phases by hand. So a run invented the loop, and what it
reached for was
for k in ...; do firefly_store_input "$k" "${!k}"; done
${!k} is bash-only indirect expansion and raises `bad substitution` under zsh, the
default shell on macOS. That exact hazard was already documented on read_secret, which
is the point: leaving the safe form unwritten is what let it recur somewhere new.
Phase 0 now shows firefly_store_inputs, which persists every [ASK] key (or a named
subset) in either shell, and says plainly not to reach for ${!k}. Invariant 34 keeps
the helper's list in step with the [ASK] rows and fails on any phase that teaches
${!k} without the warning.
The helper's own first version had a portability bug of the same family: `for k in
$keys` stores nothing under zsh, which does not word-split unquoted parameters. It
uses positional parameters now. Testing both shells is what caught it -- bash alone
reported all 15 keys stored.
Phase 6c created space 01f18a78…, round-tripped 16 tables through it and granted
CAN_RUN on it -- then a single GET failed, and it printed
space … is not readable — staying on Genie One
cleared GENIE_SPACE_ID and shipped the app on Genie One, which guest users cannot
use. The same GET succeeded minutes later. Reads after a create are eventually
consistent, so one failure means "not yet", not "not there".
space_readable retries for up to 90s and keeps stderr, so a real cause can be named;
the old check discarded it and could only ever say "not readable" whatever went
wrong. When it does fall back it now says which space it gave up and where that space
came from, because losing a curated space is a real cost and must not read as "no
space was wanted". Invariant 35 requires the retry, the kept stderr, and the
explanation, so renaming the old single-GET check cannot pass.
Also: quickstart prints its `bundle deployment bind` suggestion whether or not the
app exists, and the earlier pre-empt only defused the app-absent path, so on the
app-exists path it still read as an actionable next step. Both paths say to ignore it.
Invariant 30 now requires both branches to PRINT that, not merely contain the words:
its first version counted any matching line, so an explanatory comment satisfied it
while the reader saw nothing -- an assertion measuring source text instead of output.
Phases 6, 6b and 6c read WAREHOUSE_ID, SP_CLIENT_ID, GUEST_SP_CLIENT_ID,
GENIE_MCP_MODE and GENIE_SPACE_ID straight out of the shell. A second pass in a fresh
terminal arrived with them empty, and not one of the resulting errors named the empty
variable:
WAREHOUSE_ID="" -> "No API found for 'PATCH /permissions/warehouses/'"
i.e. apparently a wrong API route
GUEST_SP_CLIENT_ID="" -> "Principal: ServicePrincipalName() does not exist"
i.e. apparently a SCIM problem
GENIE_MCP_MODE="" -> the space-mode gate silently skipped, so the app never
received genie_space_id at all
The silent skip is the worst of the three: nothing failed, so nothing got investigated,
and the app shipped on Genie One where guest users cannot query it.
firefly_restore_phase6_context rederives each value from state.env and then from the
workspace; firefly_require refuses to build a request from an empty variable so the
message names the variable rather than the symptom. Phase 6 persists WAREHOUSE_ID for
6b and 6c instead of assuming the shell survived, and the space-mode gate now has an
else branch that states what was given up and why.
Invariant 36 checks position, not presence: BOOTSTRAP.md has several restore call sites,
so requiring merely one passed with a phase boundary still unguarded.
Also fixes invariant 33, which wrote its probe lines into the repo's real
.firefly-bootstrap/state.env on every run -- init_state_dir derives STATE_FILE from
${1:-${REPO_DIR:-$PWD}} and ignored the variable the test was setting. 45 lines of test
data had accumulated there. A test must not write where the thing it tests writes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an optional Agent panel to the SSO-SPN org view: a slide-out chat assistant with Genie One (workspace-wide Genie via MCP) and durable managed memory (Unity Catalog memory store).
Unlike the VSCode/notebook editors (Go proxy), the agent embeds through a Vercel-native reverse proxy at
/api/agent-proxythat mints a workspace bearer from the current user's mapped service principal — so guest users never hit the Databricks OAuth wall.The deployable Databricks App is assembled from the
vendor/app-templatessubmodule (agent-openai-agents-sdk+e2e-chatbot-app-next) plus a local overlay inagent/.Architecture
sequenceDiagram participant User participant Browser as Browser (Agent panel iframe) participant NextJS as Next.js (/api/agent-proxy) participant DB as Database participant OIDC as Databricks OIDC participant App as Agent App (Databricks) participant Genie as Genie One MCP participant Memory as UC memory store User->>Browser: Open Agent panel Browser->>NextJS: GET /api/agent-proxy NextJS->>DB: Resolve session, org, SPN mapping NextJS->>OIDC: client_credentials token NextJS->>App: Proxy with Bearer + HTML rewrite Browser->>NextJS: POST /api/agent-proxy/api/chat (SSE) NextJS->>App: Forward chat stream App->>Genie: ask_genie_one App->>Memory: read/write long-term memory App-->>Browser: Streamed answerSee also:
src/app/docs/solutions/agent/page.tsxandpublic/solutions/agent/architecture.mermaid.What's new
Frontend (Vercel)
src/components/agent/agent-panel.tsx— slide-out panel (feature-flagged)src/app/api/agent-proxy/[[...path]]/route.ts— SPN token minting, SSE proxy, HTML rewrite (<base>, light theme)src/stores/agent-panel-store.ts— panel open/minimize statesrc/app/sso-spn/[orgId]/layout.tsxAgent app (Databricks)
agent/overlay:agent_server/(Genie tools, managed memory), chat UI patches,databricks.ymlscripts/assemble_agent.sh— merges submodule + overlay →agent-build/agent/scripts/deploy_agent.sh,vendor_wheels.sh,setup_memory_store.py,start_app.pyAuth / guest fixes
src/lib/auth-dynamic.ts— Okta plugin only initializes when env vars are set (fixes Vercel build prerender crash)Docs / bootstrap
BOOTSTRAP.md+scripts/bootstrap.sh— end-to-end fresh-workspace runbook (Phases 0–9)Enable the panel
Frontend (
.env.local/ Vercel):Runtime prerequisites (or
/api/agent-proxyreturns 400/401):workspaceUrlsetuserSpns) for that orgCAN_USEon the agent app (documented in BOOTSTRAP Phase 6b)Agent app (bundle in
agent/databricks.yml, not frontend env):GENIE_MCP_MODE=one— workspace-wide Genie OneDATABRICKS_MEMORY_STORE— UC memory store FQN (must be created + granted post-deploy)Deploy path (happy path)
Or use
bash scripts/deploy_agent.sh <profile>(deploy → run → memory store).Full fresh-workspace walkthrough:
BOOTSTRAP.md.Known limitations
uv sync,npm install, and a Next.js production build before serving. Expect transient 503s; judge health onapp_status=RUNNING, not deployment status alone.setup_memory_store.pymust run after deploy or memory silently no-ops.USE CATALOG/USE SCHEMA/SELECT+ warehouseCAN_USEon schemas with tables.NEXT_PUBLIC_BETTER_AUTH_URL(causes CORS on hash-suffixed preview URLs). OmitSPN_AUTH_OKTA_*unless using Okta.Reviewer guide
Suggested read order:
src/app/docs/solutions/agent/page.tsxsrc/app/api/agent-proxy/[[...path]]/route.ts— token + proxyagent/agent_server/genie_tools.py— Genie One MCPagent/agent_server/utils_memory.py— UC memory store REST APIscripts/assemble_agent.sh+agent/databricks.yml— packagingIntentional design choice: editors use the Go proxy; the agent uses the Vercel-native proxy (no Cloud Run / Go proxy required for the panel).
Test plan
Verified on a fresh workspace via
BOOTSTRAP.mdPhases 0–9:assemble_agent.sh+ submodule initapp_status=RUNNING(vendor wheels + bundle deploy)setup_memory_store.pycreates UC memory store and grants app SPNEXT_PUBLIC_BETTER_AUTH_URL)