fix(selfhost): align Docker + smoke CI with flat repo layout - #8
Merged
Conversation
The docker/ skeleton and selfhost-smoke.yml were drafted assuming an apps/web monorepo layout, but the extracted repo is flat (web app at the root, packages/mcp the only workspace). That mismatch made the self-host smoke matrix unrunnable. This fixes the whole path end-to-end: - Dockerfile.web: build at repo root (no apps/web / packages/core), use `npm install` (repo ships no lockfile), `npx prisma generate`, and a full-copy `next start` runner (robust vs. standalone tracing). Copy prisma.config.ts so `db push` resolves schema + datasource at runtime. - entrypoint.sh: schema path -> root; switch `migrate deploy` -> `db push` (no migrations/ folder ships); POSIX while-loop; no --accept-data-loss so a destructive diff fails loudly instead of dropping self-hoster data. - New GET /api/healthz liveness probe (server up + Postgres reachable), distinct from /api/health readiness which 503s on optional integrations. Both the compose healthcheck and the CI probe already point at it. - selfhost-smoke.yml: flat-repo path filters (src/prisma/public/configs), and COMPOSE_FILE=docker/docker-compose.yml so every `docker compose` step finds the file without -f. - .gitattributes: pin Dockerfile* to LF (Alpine build safety). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
actions/setup-node@v4 with `cache: 'npm'` requires a lockfile to compute its cache key. The repo intentionally ships no package-lock.json, so every CI job was failing at the setup-node step before install even ran. Drop the cache directive across all five jobs (type-check / lint / build / test / mcp-pack) so installs run via `npm install`. Follow-up (not this PR): commit a package-lock.json to restore cacheable, reproducible installs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Prisma client uses the new prisma-client generator with a custom output (src/generated/prisma), imported as @/generated/prisma/client. The type-check and test jobs never generated it, so prisma.* resolved to `any` — the root of the bulk of the strict-mode TS7006/TS2339 cascade. The build job + Docker already run generate; add it to these two jobs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The gmail/outreach + google-sc modules import googleapis, but the dep was dropped during the OSS package.json restructuring, causing 7 TS2307 "Cannot find module 'googleapis'" errors that cascade. Restore the exact version the app uses (^144.0.0). Note: this operator growth/outreach tooling (outreach crons, Search Console SEO automation, Slack summaries) is surplus to the self-host product and is tracked for removal as a follow-up; restoring the dep unblocks the build now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rift) The last 2 of 149 type errors: stripe@^22 caret floats (no lockfile) and its pinned-version literal type drifted to 2026-04-22.dahlia, rejecting our deliberately-pinned 2026-03-25.dahlia. Cast to Stripe.LatestApiVersion to keep the intended runtime value type-stable across SDK minors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cast to Stripe.LatestApiVersion failed (TS2694 — not an exported member in stripe@22). Omitting apiVersion entirely is always type-valid and uses the Stripe account's default pinned version — the correct default for a self-host deployment, and immune to the lockfile-free SDK caret drift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Docker) GitHub arm64 macOS runners lack nested virtualization, so Colima/docker compose fails at VM start (exit 125) — macos-14 could never pass the compose smoke. Split the matrix: docker-compose smoke runs on ubuntu-22.04 + macos-13 (full boot + DB + healthz), and a new native-build job proves Apple-Silicon parity by running prisma generate + next build on arm64 macOS (exercises the arch-sensitive Prisma engine / bcryptjs / lightningcss). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tsc includes tests/, so the e2e spec's hardcoded apiVersion literal was the last type error (the bootstrap script mirrors it for consistency). Same fix as the client: omit apiVersion to stay type-stable across stripe@22 minors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The class extends the built-in Error; transpilation breaks `instanceof` for genuine instances unless the prototype is reset. Add Object.setPrototypeOf in the constructor — fixes the lone failing vitest assertion (token-budget.test.ts > assertBudget — trial plan). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`next build` of this heavy app (pdfjs/exceljs/pptxgenjs/tiptap/react-pdf) exceeded the default V8 old-space heap on the 7 GB ubuntu runner — "Ineffective mark-compacts near heap limit". Set NODE_OPTIONS --max-old-space-size=6144 in the CI build job and the Docker builder stage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Colima on the Intel macos-13 runner is too memory-starved (4 GB VM) to build this app and is slow/flaky; arm64 macos-14 can't run Docker at all. Drop Docker-on-macOS entirely: keep the full docker-compose smoke on ubuntu-22.04 (the canonical Linux self-host target), and prove macOS self-host parity (both Intel + Apple Silicon) via a native `next build` matrix [macos-13, macos-14] with the same heap bump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The real cause of the failing assertion: FREE_TRIAL.trialTotalTokens was bumped 200K → 700K on 2026-05-15, but the test still fed 200K usage and expected a trial_exceeded throw. 200K < 700K → no throw → expect.fail() threw the AssertionError we saw. Reference FREE_TRIAL.trialTotalTokens directly so the test tracks the constant and can't go stale again. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…protocol/sdk The Docker builder copied only the root package.json before npm install, so the packages/mcp workspace deps were never installed. `next build` type-checks the whole project (incl. packages/mcp, which imports @modelcontextprotocol/sdk) and failed: "Cannot find module '@modelcontextprotocol/sdk/server/stdio.js'". Copy packages/mcp/package.json before install so npm (workspaces) resolves it. (CI Build + native macOS passed because their npm install ran against the full checkout.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hip client Two runtime issues that kept the web container from going healthy: 1. entrypoint ran `prisma db push --skip-generate`, but Prisma 7 removed that flag — it errored, printed help, and crashed the container (set -e). Use a plain `prisma db push`. 2. The Prisma client uses a custom output (src/generated/prisma, not node_modules/.prisma) that the runner stage never copied, so the server + db push couldn't resolve @/generated/prisma/client. Copy it explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…macos-13 Two things kept the ubuntu docker-compose smoke from going healthy / the run from completing: 1. The schema has a vector(1024) column but docker-compose used postgres:16-alpine (no pgvector) and nothing created the extension, so `prisma db push` failed: ERROR: type "vector" does not exist. Switch to pgvector/pgvector:pg16 + a first-boot init that runs CREATE EXTENSION vector. This is a real self-host correctness fix, not just CI. 2. macos-13 native build sat queued 45+ min (GitHub Intel-mac runner unavailability) and wedged the whole workflow from ever completing. Drop it from the matrix; macos-14 (Apple Silicon) covers native build parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The container went "unhealthy" (compose --wait failed) even though the app boots fine (Postgres reachable, db push synced, Next.js Ready). Decouple: `docker compose up -d --build` (no --wait), and let the explicit probe step gate — now printing the HTTP code + body each attempt so a 503 reveals the actual healthz error instead of an opaque "unhealthy". Either goes green or surfaces the precise runtime cause. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Operator decision: merge now, track the Docker HTTP-serving gap. The ubuntu job now asserts the core self-host proof as fatal — image builds, container boots, Postgres reachable, schema synced via prisma db push — and treats the in-container HTTP route probe as non-blocking, because the hand-rolled `next start` runtime doesn't serve built routes (404; needs output:standalone, tracked as a follow-up issue). Makes selfhost-smoke green on what genuinely works instead of red on the one packaging gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seungdo-keum
added a commit
that referenced
this pull request
May 28, 2026
…, skill promotion
Three new autonomous agents that close the AI-Native Agent loop's
"proactive preparation + intelligent intervention" principles:
- P2-02 src/lib/agents/anomaly-detector.ts — scans each client's
ClientContext + recent ConversationMessages for out-of-pattern
signals (threshold breaks, contradicting facts, unusual cadence).
Writes ApprovalItem of type "anomaly_alert" with severity
high/medium/low → priority 80/50/25. Confidence < 0.65 outputs
drop silently so the queue doesn't fill with noise. Pulls active
pattern-learner rules into the system prompt so previously-
dismissed pattern shapes get downweighted automatically.
- P2-03 src/lib/agents/comms-drafter.ts — once-daily (17:00 local),
drafts client-facing reminder emails for each pending action item
that needs a recipient nudge. Tone-aware via client preferences +
pattern-learner. Each draft becomes an ApprovalItem of type
"email_draft" with priority weighted by nudgeWithinDays so soonest-
needed nudges sort to the top of the queue.
- P2-05 src/lib/skill-generator.ts — nightly batch that promotes
AgentRule rows from "candidate pattern" to "promoted skill" once
appliedCount ≥ 5, confidence ≥ 0.85, and recent verdictWeight
average ≥ 0.85. Promoted rules surface in the system prompt with
stronger language ("APPLY this template" vs "consider this
pattern"). Demotion is just a flag flip — no row deletes if the
operator loses confidence later. Implemented inside the existing
AgentRule schema rather than introducing a new Skill table; the
promoted flag lives in the action JSON.
Lovable-mark gate touched: #8 (background agent producing real value
— nightly briefing + anomaly detector + comms drafter now stack
multiple ApprovalItems per active client per day).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seungdo-keum
added a commit
that referenced
this pull request
May 28, 2026
…03) + self-paging wired (P1-07) Closes lovable-mark gate #2 (memory loop) for the *whole product*, not just daily-briefing. Replaces the flat `prisma.clientContext. findMany({ take: 50 })` block scattered across runner/daily-briefing/ anomaly-detector/comms-drafter/chat with a single 5-tier composer that aggregates: - **T0 ClientProfile** (250 tokens) — always-inline core memory a-la Letta. Identity + industry + relationship length + operator-set tone / formats / note. Built from Client.preferences. - **T1 RollingDigest** (500 tokens) — nightly-compacted 30-day rolling summary stored in ClientContext.category="digest". Falls back to top-3 pinned facts when the compactor hasn't run yet. Zep "Community subgraph" plays this role. - **T2 VectorHits** (600 tokens) — hybridSearchKnowledgeBase (0.4×trigram + 0.6×cosine over content_embedding) + loadActiveFacts (bitemporal ClientFact rows). Only fires when the caller passes `query`. Mem0g-style structured beliefs + paragraph hits for grounding. - **T3 EpisodicTimeline** (300 tokens) — last 5 AgentTask summaries + last 3 ApprovalItem decisions, newest-first. Zep "Episode subgraph" — the model finally sees "we already sent that reminder Tuesday" as a tier rather than reconstructing from titles. - **T4 FirmPatterns** (250 tokens) — promoted vs candidate AgentRule entries. Surfaces the operator's repeated decisions to ALL agents now, not just daily-briefing. Promoted rules get "APPLY this template" framing per Hermes/skill-promotion lit. Composer (`src/lib/memory/loader.ts`) does proportional scaling when budget < 2000, greedy fit in T0→T1→T4→T3→T2 order, emit in stable T0→T1→T4→T3→T2 sequence for prompt-cache stability. Returns full per-tier observability map. Migrations: - daily-briefing.ts → 1800-token budget, no query (full scan). - anomaly-detector.ts → 1700-token budget, query="anomaly out-of- pattern unusual transaction threshold". - comms-drafter.ts → 1700-token budget, query="pending action items requiring outbound nudge reminder". - chat/route.ts → 1500-token budget (tighter — tool-use loops compound the system prompt across rounds), query=last user message. renderSystemPrompt drops the legacy raw-contexts rendering and embeds `memory.prompt` directly under "Quick reference". P1-03 Digest Compactor: `src/lib/memory/digest-compactor.ts` + nightly cron `/api/cron/digest-compactor`. Every active client (any ConversationMessage / AgentTask / ApprovalItem in last 30d) gets one 400-word past-tense plain-English digest written by Haiku 4 (~$0.30 per night for an entire firm). Idempotent — prior digest rows flip to `category: "digest_archive"` in the same tx. SLACK_WEBHOOK_URL receives an error ping when ≥1 client digest fails. CRON_SECRET-gated; `DIGEST_COMPACTOR_DISABLED=1` is the emergency kill. P1-07 Self-paging: `recall_archival` tool was already wired into chat tools by sub-agent A. Confirmed runner-managed agents see the same tool dispatch path so background agents can self-page mid-run. Hard 800-token cap injected into next-turn user message prevents runaway recall. Operator probe: `GET /api/dev-test/memory-snapshot?clientId=...& query=...&budget=...` — auth-gated, returns the rendered prompt + per-tier breakdown so an operator can see exactly what their agents are loading. Token economics: median chat turn dropped from ~3.5K input tokens to ~1.7K (-52%) on seeded fixture client. With 200 active clients × 12 chat turns/day × Anthropic Sonnet 4.5 input pricing, that's ~$1.50/day saved per active firm — or ~$45/month per firm of useless context cost. Validation: - `npm run type-check` clean. - `npm run lint` clean (only pre-existing warnings in unrelated files). - `vitest src/lib/memory/loader.test.ts` 19/19 pass — token counter, T0 profile renderer, composer skip + budget bounds + preloadedClient short-circuit + missing-client fallback. - New `tests/e2e/memory-tiering.spec.ts` covers prod auth-gated probe (skips when E2E_TEST_EMAIL unset). - All 4 migrated agents + chat type-check + lint clean. Research artifact: `.cycle/research/2026-04-28-memory-deep-dive.md` documents the external pattern survey (Letta MemGPT, Zep/Graphiti, Mem0/Mem0g, Cognee, MemKraft), brutal evaluation of the prior flat-list path, and the design rationale for each tier choice. Lovable-mark gates touched: #2 (memory loop closes — pattern learner + temporal facts + vector + digest + episodic ALL demonstrably influence every agent + chat output now), #8 (background agents producing real value — composer makes their prompts coherent rather than wall-of-text). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seungdo-keum
added a commit
that referenced
this pull request
May 28, 2026
…efresh crons Two new daily/weekly crons that close audit gap #8 (FactEdge writes absent) and AEO research priority #6 (freshness refresh boosts ~2.5× citation rate). 1. **FactEdge inference** (`/api/cron/factedge-inference`, 04:00 UTC daily) - `inferEdgesForClient(clientId)` walks up to 30 active ClientFact rows per client, asks Claude (tool_use schema) to propose directed edges with one of 5 relations: causes / contradicts / refines / depends_on / follows. - Cost-bounded: skips clients with < 5 active facts. One Claude call per client. ~$0.50/day across ~10 firms × 5 clients. - Idempotent: writes via the existing `@@unique([fromFactId, toFactId, relation])` constraint; on conflict refreshes weight if higher. Re-running is safe. - Edge weight floor 0.55 — proposed edges below that drop. Match to the corpus's exact fact ids (no invented ids). - Slack agent_cron_summary / _warning emits aggregate counters at the end. Failure-per-client doesn't kill the cron. 2. **30-day freshness refresh** (`/api/cron/freshness-refresh`, 09:00 UTC Tuesday) - Picks up to 6 oldest blog posts whose `dateModified` is > 30 days ago. Per post, asks Claude to spot ONE stale claim + propose a replacement (verbatim phrase to update + 1-3 sentence proposed update + rationale + confidence). - Persists proposals to `AuditLog` with `action="content_freshness_proposal"`. Operator reviews via the audit table or a future admin page. **No automated PR creation** — the operator must validate + manually apply, which keeps the cron Vercel-runtime-safe (no gh credentials required). - 14-day cooldown per slug so a previously-proposed post doesn't get a duplicate proposal next Tuesday. - Confidence < 0.6 → skip. Most posts on most weeks have nothing to refresh; empty answer is correct most of the time. - Cost: ~$0.12/week (6 posts × ~$0.02 each at Sonnet 4.5). Safety guarantees: - Both crons are READ-mostly. Only writes are FactEdge rows (existing schema + unique constraint) and AuditLog rows (existing model). - Both use the existing CRON_SECRET / SEO_DEPLOY_SECRET / x-vercel-cron auth path that all other crons share. - Both fail closed on per-item errors so one bad client / post doesn't cascade. - vercel.json gets 2 new entries (Hobby-compliant: daily + weekly, not hourly). 233/233 vitest pass · type-check ✅. New crons additive — zero behavior change to existing daily-briefing / anomaly-detector / comms-drafter / digest-compactor flows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
seungdo-keum
added a commit
that referenced
this pull request
May 28, 2026
* fix(selfhost): align Docker + smoke CI with flat repo layout The docker/ skeleton and selfhost-smoke.yml were drafted assuming an apps/web monorepo layout, but the extracted repo is flat (web app at the root, packages/mcp the only workspace). That mismatch made the self-host smoke matrix unrunnable. This fixes the whole path end-to-end: - Dockerfile.web: build at repo root (no apps/web / packages/core), use `npm install` (repo ships no lockfile), `npx prisma generate`, and a full-copy `next start` runner (robust vs. standalone tracing). Copy prisma.config.ts so `db push` resolves schema + datasource at runtime. - entrypoint.sh: schema path -> root; switch `migrate deploy` -> `db push` (no migrations/ folder ships); POSIX while-loop; no --accept-data-loss so a destructive diff fails loudly instead of dropping self-hoster data. - New GET /api/healthz liveness probe (server up + Postgres reachable), distinct from /api/health readiness which 503s on optional integrations. Both the compose healthcheck and the CI probe already point at it. - selfhost-smoke.yml: flat-repo path filters (src/prisma/public/configs), and COMPOSE_FILE=docker/docker-compose.yml so every `docker compose` step finds the file without -f. - .gitattributes: pin Dockerfile* to LF (Alpine build safety). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): drop npm cache from setup-node (repo ships no lockfile) actions/setup-node@v4 with `cache: 'npm'` requires a lockfile to compute its cache key. The repo intentionally ships no package-lock.json, so every CI job was failing at the setup-node step before install even ran. Drop the cache directive across all five jobs (type-check / lint / build / test / mcp-pack) so installs run via `npm install`. Follow-up (not this PR): commit a package-lock.json to restore cacheable, reproducible installs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): run prisma generate before type-check and test The Prisma client uses the new prisma-client generator with a custom output (src/generated/prisma), imported as @/generated/prisma/client. The type-check and test jobs never generated it, so prisma.* resolved to `any` — the root of the bulk of the strict-mode TS7006/TS2339 cascade. The build job + Docker already run generate; add it to these two jobs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(deps): restore googleapis (dropped in extraction, still imported) The gmail/outreach + google-sc modules import googleapis, but the dep was dropped during the OSS package.json restructuring, causing 7 TS2307 "Cannot find module 'googleapis'" errors that cascade. Restore the exact version the app uses (^144.0.0). Note: this operator growth/outreach tooling (outreach crons, Search Console SEO automation, Slack summaries) is surplus to the self-host product and is tracked for removal as a follow-up; restoring the dep unblocks the build now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(stripe): cast apiVersion to LatestApiVersion (lockfile-free SDK drift) The last 2 of 149 type errors: stripe@^22 caret floats (no lockfile) and its pinned-version literal type drifted to 2026-04-22.dahlia, rejecting our deliberately-pinned 2026-03-25.dahlia. Cast to Stripe.LatestApiVersion to keep the intended runtime value type-stable across SDK minors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(stripe): omit apiVersion (Stripe.LatestApiVersion type not exported) The cast to Stripe.LatestApiVersion failed (TS2694 — not an exported member in stripe@22). Omitting apiVersion entirely is always type-valid and uses the Stripe account's default pinned version — the correct default for a self-host deployment, and immune to the lockfile-free SDK caret drift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(selfhost): native build job for macos-14 (Apple Silicon can't run Docker) GitHub arm64 macOS runners lack nested virtualization, so Colima/docker compose fails at VM start (exit 125) — macos-14 could never pass the compose smoke. Split the matrix: docker-compose smoke runs on ubuntu-22.04 + macos-13 (full boot + DB + healthz), and a new native-build job proves Apple-Silicon parity by running prisma generate + next build on arm64 macOS (exercises the arch-sensitive Prisma engine / bcryptjs / lightningcss). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(stripe): omit apiVersion in e2e test + bootstrap script too tsc includes tests/, so the e2e spec's hardcoded apiVersion literal was the last type error (the bootstrap script mirrors it for consistency). Same fix as the client: omit apiVersion to stay type-stable across stripe@22 minors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(token-budget): restore prototype chain on BudgetExceededError The class extends the built-in Error; transpilation breaks `instanceof` for genuine instances unless the prototype is reset. Add Object.setPrototypeOf in the constructor — fixes the lone failing vitest assertion (token-budget.test.ts > assertBudget — trial plan). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(build): raise V8 heap for next build (was OOMing on CI runner) `next build` of this heavy app (pdfjs/exceljs/pptxgenjs/tiptap/react-pdf) exceeded the default V8 old-space heap on the 7 GB ubuntu runner — "Ineffective mark-compacts near heap limit". Set NODE_OPTIONS --max-old-space-size=6144 in the CI build job and the Docker builder stage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(selfhost): ubuntu-only docker + native build matrix for both Macs Colima on the Intel macos-13 runner is too memory-starved (4 GB VM) to build this app and is slow/flaky; arm64 macos-14 can't run Docker at all. Drop Docker-on-macOS entirely: keep the full docker-compose smoke on ubuntu-22.04 (the canonical Linux self-host target), and prove macOS self-host parity (both Intel + Apple Silicon) via a native `next build` matrix [macos-13, macos-14] with the same heap bump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(token-budget): update stale trial-allowance test (200K → constant) The real cause of the failing assertion: FREE_TRIAL.trialTotalTokens was bumped 200K → 700K on 2026-05-15, but the test still fed 200K usage and expected a trial_exceeded throw. 200K < 700K → no throw → expect.fail() threw the AssertionError we saw. Reference FREE_TRIAL.trialTotalTokens directly so the test tracks the constant and can't go stale again. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(docker): install workspace deps so next build finds @modelcontextprotocol/sdk The Docker builder copied only the root package.json before npm install, so the packages/mcp workspace deps were never installed. `next build` type-checks the whole project (incl. packages/mcp, which imports @modelcontextprotocol/sdk) and failed: "Cannot find module '@modelcontextprotocol/sdk/server/stdio.js'". Copy packages/mcp/package.json before install so npm (workspaces) resolves it. (CI Build + native macOS passed because their npm install ran against the full checkout.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(docker): runtime boot — drop Prisma 7-invalid --skip-generate + ship client Two runtime issues that kept the web container from going healthy: 1. entrypoint ran `prisma db push --skip-generate`, but Prisma 7 removed that flag — it errored, printed help, and crashed the container (set -e). Use a plain `prisma db push`. 2. The Prisma client uses a custom output (src/generated/prisma, not node_modules/.prisma) that the runner stage never copied, so the server + db push couldn't resolve @/generated/prisma/client. Copy it explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(selfhost): pgvector Postgres image + create extension; drop dead macos-13 Two things kept the ubuntu docker-compose smoke from going healthy / the run from completing: 1. The schema has a vector(1024) column but docker-compose used postgres:16-alpine (no pgvector) and nothing created the extension, so `prisma db push` failed: ERROR: type "vector" does not exist. Switch to pgvector/pgvector:pg16 + a first-boot init that runs CREATE EXTENSION vector. This is a real self-host correctness fix, not just CI. 2. macos-13 native build sat queued 45+ min (GitHub Intel-mac runner unavailability) and wedged the whole workflow from ever completing. Drop it from the matrix; macos-14 (Apple Silicon) covers native build parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(selfhost): gate on explicit verbose healthz probe, not compose --wait The container went "unhealthy" (compose --wait failed) even though the app boots fine (Postgres reachable, db push synced, Next.js Ready). Decouple: `docker compose up -d --build` (no --wait), and let the explicit probe step gate — now printing the HTTP code + body each attempt so a 503 reveals the actual healthz error instead of an opaque "unhealthy". Either goes green or surfaces the precise runtime cause. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(selfhost): assert build+boot+db-sync (fatal); HTTP serve = known-gap Operator decision: merge now, track the Docker HTTP-serving gap. The ubuntu job now asserts the core self-host proof as fatal — image builds, container boots, Postgres reachable, schema synced via prisma db push — and treats the in-container HTTP route probe as non-blocking, because the hand-rolled `next start` runtime doesn't serve built routes (404; needs output:standalone, tracked as a follow-up issue). Makes selfhost-smoke green on what genuinely works instead of red on the one packaging gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seungdo-keum
added a commit
that referenced
this pull request
May 29, 2026
…, skill promotion
Three new autonomous agents that close the AI-Native Agent loop's
"proactive preparation + intelligent intervention" principles:
- P2-02 src/lib/agents/anomaly-detector.ts — scans each client's
ClientContext + recent ConversationMessages for out-of-pattern
signals (threshold breaks, contradicting facts, unusual cadence).
Writes ApprovalItem of type "anomaly_alert" with severity
high/medium/low → priority 80/50/25. Confidence < 0.65 outputs
drop silently so the queue doesn't fill with noise. Pulls active
pattern-learner rules into the system prompt so previously-
dismissed pattern shapes get downweighted automatically.
- P2-03 src/lib/agents/comms-drafter.ts — once-daily (17:00 local),
drafts client-facing reminder emails for each pending action item
that needs a recipient nudge. Tone-aware via client preferences +
pattern-learner. Each draft becomes an ApprovalItem of type
"email_draft" with priority weighted by nudgeWithinDays so soonest-
needed nudges sort to the top of the queue.
- P2-05 src/lib/skill-generator.ts — nightly batch that promotes
AgentRule rows from "candidate pattern" to "promoted skill" once
appliedCount ≥ 5, confidence ≥ 0.85, and recent verdictWeight
average ≥ 0.85. Promoted rules surface in the system prompt with
stronger language ("APPLY this template" vs "consider this
pattern"). Demotion is just a flag flip — no row deletes if the
operator loses confidence later. Implemented inside the existing
AgentRule schema rather than introducing a new Skill table; the
promoted flag lives in the action JSON.
Lovable-mark gate touched: #8 (background agent producing real value
— nightly briefing + anomaly detector + comms drafter now stack
multiple ApprovalItems per active client per day).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seungdo-keum
added a commit
that referenced
this pull request
May 29, 2026
…03) + self-paging wired (P1-07) Closes lovable-mark gate #2 (memory loop) for the *whole product*, not just daily-briefing. Replaces the flat `prisma.clientContext. findMany({ take: 50 })` block scattered across runner/daily-briefing/ anomaly-detector/comms-drafter/chat with a single 5-tier composer that aggregates: - **T0 ClientProfile** (250 tokens) — always-inline core memory a-la Letta. Identity + industry + relationship length + operator-set tone / formats / note. Built from Client.preferences. - **T1 RollingDigest** (500 tokens) — nightly-compacted 30-day rolling summary stored in ClientContext.category="digest". Falls back to top-3 pinned facts when the compactor hasn't run yet. Zep "Community subgraph" plays this role. - **T2 VectorHits** (600 tokens) — hybridSearchKnowledgeBase (0.4×trigram + 0.6×cosine over content_embedding) + loadActiveFacts (bitemporal ClientFact rows). Only fires when the caller passes `query`. Mem0g-style structured beliefs + paragraph hits for grounding. - **T3 EpisodicTimeline** (300 tokens) — last 5 AgentTask summaries + last 3 ApprovalItem decisions, newest-first. Zep "Episode subgraph" — the model finally sees "we already sent that reminder Tuesday" as a tier rather than reconstructing from titles. - **T4 FirmPatterns** (250 tokens) — promoted vs candidate AgentRule entries. Surfaces the operator's repeated decisions to ALL agents now, not just daily-briefing. Promoted rules get "APPLY this template" framing per Hermes/skill-promotion lit. Composer (`src/lib/memory/loader.ts`) does proportional scaling when budget < 2000, greedy fit in T0→T1→T4→T3→T2 order, emit in stable T0→T1→T4→T3→T2 sequence for prompt-cache stability. Returns full per-tier observability map. Migrations: - daily-briefing.ts → 1800-token budget, no query (full scan). - anomaly-detector.ts → 1700-token budget, query="anomaly out-of- pattern unusual transaction threshold". - comms-drafter.ts → 1700-token budget, query="pending action items requiring outbound nudge reminder". - chat/route.ts → 1500-token budget (tighter — tool-use loops compound the system prompt across rounds), query=last user message. renderSystemPrompt drops the legacy raw-contexts rendering and embeds `memory.prompt` directly under "Quick reference". P1-03 Digest Compactor: `src/lib/memory/digest-compactor.ts` + nightly cron `/api/cron/digest-compactor`. Every active client (any ConversationMessage / AgentTask / ApprovalItem in last 30d) gets one 400-word past-tense plain-English digest written by Haiku 4 (~$0.30 per night for an entire firm). Idempotent — prior digest rows flip to `category: "digest_archive"` in the same tx. SLACK_WEBHOOK_URL receives an error ping when ≥1 client digest fails. CRON_SECRET-gated; `DIGEST_COMPACTOR_DISABLED=1` is the emergency kill. P1-07 Self-paging: `recall_archival` tool was already wired into chat tools by sub-agent A. Confirmed runner-managed agents see the same tool dispatch path so background agents can self-page mid-run. Hard 800-token cap injected into next-turn user message prevents runaway recall. Operator probe: `GET /api/dev-test/memory-snapshot?clientId=...& query=...&budget=...` — auth-gated, returns the rendered prompt + per-tier breakdown so an operator can see exactly what their agents are loading. Token economics: median chat turn dropped from ~3.5K input tokens to ~1.7K (-52%) on seeded fixture client. With 200 active clients × 12 chat turns/day × Anthropic Sonnet 4.5 input pricing, that's ~$1.50/day saved per active firm — or ~$45/month per firm of useless context cost. Validation: - `npm run type-check` clean. - `npm run lint` clean (only pre-existing warnings in unrelated files). - `vitest src/lib/memory/loader.test.ts` 19/19 pass — token counter, T0 profile renderer, composer skip + budget bounds + preloadedClient short-circuit + missing-client fallback. - New `tests/e2e/memory-tiering.spec.ts` covers prod auth-gated probe (skips when E2E_TEST_EMAIL unset). - All 4 migrated agents + chat type-check + lint clean. Research artifact: `.cycle/research/2026-04-28-memory-deep-dive.md` documents the external pattern survey (Letta MemGPT, Zep/Graphiti, Mem0/Mem0g, Cognee, MemKraft), brutal evaluation of the prior flat-list path, and the design rationale for each tier choice. Lovable-mark gates touched: #2 (memory loop closes — pattern learner + temporal facts + vector + digest + episodic ALL demonstrably influence every agent + chat output now), #8 (background agents producing real value — composer makes their prompts coherent rather than wall-of-text). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seungdo-keum
added a commit
that referenced
this pull request
May 29, 2026
…efresh crons Two new daily/weekly crons that close audit gap #8 (FactEdge writes absent) and AEO research priority #6 (freshness refresh boosts ~2.5× citation rate). 1. **FactEdge inference** (`/api/cron/factedge-inference`, 04:00 UTC daily) - `inferEdgesForClient(clientId)` walks up to 30 active ClientFact rows per client, asks Claude (tool_use schema) to propose directed edges with one of 5 relations: causes / contradicts / refines / depends_on / follows. - Cost-bounded: skips clients with < 5 active facts. One Claude call per client. ~$0.50/day across ~10 firms × 5 clients. - Idempotent: writes via the existing `@@unique([fromFactId, toFactId, relation])` constraint; on conflict refreshes weight if higher. Re-running is safe. - Edge weight floor 0.55 — proposed edges below that drop. Match to the corpus's exact fact ids (no invented ids). - Slack agent_cron_summary / _warning emits aggregate counters at the end. Failure-per-client doesn't kill the cron. 2. **30-day freshness refresh** (`/api/cron/freshness-refresh`, 09:00 UTC Tuesday) - Picks up to 6 oldest blog posts whose `dateModified` is > 30 days ago. Per post, asks Claude to spot ONE stale claim + propose a replacement (verbatim phrase to update + 1-3 sentence proposed update + rationale + confidence). - Persists proposals to `AuditLog` with `action="content_freshness_proposal"`. Operator reviews via the audit table or a future admin page. **No automated PR creation** — the operator must validate + manually apply, which keeps the cron Vercel-runtime-safe (no gh credentials required). - 14-day cooldown per slug so a previously-proposed post doesn't get a duplicate proposal next Tuesday. - Confidence < 0.6 → skip. Most posts on most weeks have nothing to refresh; empty answer is correct most of the time. - Cost: ~$0.12/week (6 posts × ~$0.02 each at Sonnet 4.5). Safety guarantees: - Both crons are READ-mostly. Only writes are FactEdge rows (existing schema + unique constraint) and AuditLog rows (existing model). - Both use the existing CRON_SECRET / SEO_DEPLOY_SECRET / x-vercel-cron auth path that all other crons share. - Both fail closed on per-item errors so one bad client / post doesn't cascade. - vercel.json gets 2 new entries (Hobby-compliant: daily + weekly, not hourly). 233/233 vitest pass · type-check ✅. New crons additive — zero behavior change to existing daily-briefing / anomaly-detector / comms-drafter / digest-compactor flows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
seungdo-keum
added a commit
that referenced
this pull request
May 29, 2026
* fix(selfhost): align Docker + smoke CI with flat repo layout The docker/ skeleton and selfhost-smoke.yml were drafted assuming an apps/web monorepo layout, but the extracted repo is flat (web app at the root, packages/mcp the only workspace). That mismatch made the self-host smoke matrix unrunnable. This fixes the whole path end-to-end: - Dockerfile.web: build at repo root (no apps/web / packages/core), use `npm install` (repo ships no lockfile), `npx prisma generate`, and a full-copy `next start` runner (robust vs. standalone tracing). Copy prisma.config.ts so `db push` resolves schema + datasource at runtime. - entrypoint.sh: schema path -> root; switch `migrate deploy` -> `db push` (no migrations/ folder ships); POSIX while-loop; no --accept-data-loss so a destructive diff fails loudly instead of dropping self-hoster data. - New GET /api/healthz liveness probe (server up + Postgres reachable), distinct from /api/health readiness which 503s on optional integrations. Both the compose healthcheck and the CI probe already point at it. - selfhost-smoke.yml: flat-repo path filters (src/prisma/public/configs), and COMPOSE_FILE=docker/docker-compose.yml so every `docker compose` step finds the file without -f. - .gitattributes: pin Dockerfile* to LF (Alpine build safety). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): drop npm cache from setup-node (repo ships no lockfile) actions/setup-node@v4 with `cache: 'npm'` requires a lockfile to compute its cache key. The repo intentionally ships no package-lock.json, so every CI job was failing at the setup-node step before install even ran. Drop the cache directive across all five jobs (type-check / lint / build / test / mcp-pack) so installs run via `npm install`. Follow-up (not this PR): commit a package-lock.json to restore cacheable, reproducible installs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): run prisma generate before type-check and test The Prisma client uses the new prisma-client generator with a custom output (src/generated/prisma), imported as @/generated/prisma/client. The type-check and test jobs never generated it, so prisma.* resolved to `any` — the root of the bulk of the strict-mode TS7006/TS2339 cascade. The build job + Docker already run generate; add it to these two jobs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(deps): restore googleapis (dropped in extraction, still imported) The gmail/outreach + google-sc modules import googleapis, but the dep was dropped during the OSS package.json restructuring, causing 7 TS2307 "Cannot find module 'googleapis'" errors that cascade. Restore the exact version the app uses (^144.0.0). Note: this operator growth/outreach tooling (outreach crons, Search Console SEO automation, Slack summaries) is surplus to the self-host product and is tracked for removal as a follow-up; restoring the dep unblocks the build now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(stripe): cast apiVersion to LatestApiVersion (lockfile-free SDK drift) The last 2 of 149 type errors: stripe@^22 caret floats (no lockfile) and its pinned-version literal type drifted to 2026-04-22.dahlia, rejecting our deliberately-pinned 2026-03-25.dahlia. Cast to Stripe.LatestApiVersion to keep the intended runtime value type-stable across SDK minors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(stripe): omit apiVersion (Stripe.LatestApiVersion type not exported) The cast to Stripe.LatestApiVersion failed (TS2694 — not an exported member in stripe@22). Omitting apiVersion entirely is always type-valid and uses the Stripe account's default pinned version — the correct default for a self-host deployment, and immune to the lockfile-free SDK caret drift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(selfhost): native build job for macos-14 (Apple Silicon can't run Docker) GitHub arm64 macOS runners lack nested virtualization, so Colima/docker compose fails at VM start (exit 125) — macos-14 could never pass the compose smoke. Split the matrix: docker-compose smoke runs on ubuntu-22.04 + macos-13 (full boot + DB + healthz), and a new native-build job proves Apple-Silicon parity by running prisma generate + next build on arm64 macOS (exercises the arch-sensitive Prisma engine / bcryptjs / lightningcss). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(stripe): omit apiVersion in e2e test + bootstrap script too tsc includes tests/, so the e2e spec's hardcoded apiVersion literal was the last type error (the bootstrap script mirrors it for consistency). Same fix as the client: omit apiVersion to stay type-stable across stripe@22 minors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(token-budget): restore prototype chain on BudgetExceededError The class extends the built-in Error; transpilation breaks `instanceof` for genuine instances unless the prototype is reset. Add Object.setPrototypeOf in the constructor — fixes the lone failing vitest assertion (token-budget.test.ts > assertBudget — trial plan). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(build): raise V8 heap for next build (was OOMing on CI runner) `next build` of this heavy app (pdfjs/exceljs/pptxgenjs/tiptap/react-pdf) exceeded the default V8 old-space heap on the 7 GB ubuntu runner — "Ineffective mark-compacts near heap limit". Set NODE_OPTIONS --max-old-space-size=6144 in the CI build job and the Docker builder stage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(selfhost): ubuntu-only docker + native build matrix for both Macs Colima on the Intel macos-13 runner is too memory-starved (4 GB VM) to build this app and is slow/flaky; arm64 macos-14 can't run Docker at all. Drop Docker-on-macOS entirely: keep the full docker-compose smoke on ubuntu-22.04 (the canonical Linux self-host target), and prove macOS self-host parity (both Intel + Apple Silicon) via a native `next build` matrix [macos-13, macos-14] with the same heap bump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(token-budget): update stale trial-allowance test (200K → constant) The real cause of the failing assertion: FREE_TRIAL.trialTotalTokens was bumped 200K → 700K on 2026-05-15, but the test still fed 200K usage and expected a trial_exceeded throw. 200K < 700K → no throw → expect.fail() threw the AssertionError we saw. Reference FREE_TRIAL.trialTotalTokens directly so the test tracks the constant and can't go stale again. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(docker): install workspace deps so next build finds @modelcontextprotocol/sdk The Docker builder copied only the root package.json before npm install, so the packages/mcp workspace deps were never installed. `next build` type-checks the whole project (incl. packages/mcp, which imports @modelcontextprotocol/sdk) and failed: "Cannot find module '@modelcontextprotocol/sdk/server/stdio.js'". Copy packages/mcp/package.json before install so npm (workspaces) resolves it. (CI Build + native macOS passed because their npm install ran against the full checkout.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(docker): runtime boot — drop Prisma 7-invalid --skip-generate + ship client Two runtime issues that kept the web container from going healthy: 1. entrypoint ran `prisma db push --skip-generate`, but Prisma 7 removed that flag — it errored, printed help, and crashed the container (set -e). Use a plain `prisma db push`. 2. The Prisma client uses a custom output (src/generated/prisma, not node_modules/.prisma) that the runner stage never copied, so the server + db push couldn't resolve @/generated/prisma/client. Copy it explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(selfhost): pgvector Postgres image + create extension; drop dead macos-13 Two things kept the ubuntu docker-compose smoke from going healthy / the run from completing: 1. The schema has a vector(1024) column but docker-compose used postgres:16-alpine (no pgvector) and nothing created the extension, so `prisma db push` failed: ERROR: type "vector" does not exist. Switch to pgvector/pgvector:pg16 + a first-boot init that runs CREATE EXTENSION vector. This is a real self-host correctness fix, not just CI. 2. macos-13 native build sat queued 45+ min (GitHub Intel-mac runner unavailability) and wedged the whole workflow from ever completing. Drop it from the matrix; macos-14 (Apple Silicon) covers native build parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(selfhost): gate on explicit verbose healthz probe, not compose --wait The container went "unhealthy" (compose --wait failed) even though the app boots fine (Postgres reachable, db push synced, Next.js Ready). Decouple: `docker compose up -d --build` (no --wait), and let the explicit probe step gate — now printing the HTTP code + body each attempt so a 503 reveals the actual healthz error instead of an opaque "unhealthy". Either goes green or surfaces the precise runtime cause. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(selfhost): assert build+boot+db-sync (fatal); HTTP serve = known-gap Operator decision: merge now, track the Docker HTTP-serving gap. The ubuntu job now asserts the core self-host proof as fatal — image builds, container boots, Postgres reachable, schema synced via prisma db push — and treats the in-container HTTP route probe as non-blocking, because the hand-rolled `next start` runtime doesn't serve built routes (404; needs output:standalone, tracked as a follow-up issue). Makes selfhost-smoke green on what genuinely works instead of red on the one packaging gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Why
The
docker/skeleton andselfhost-smoke.ymlwere drafted assuming anapps/webmonorepo layout. The extracted repo is flat — the Next.js web app is at the root, andpackages/mcpis the only workspace. That mismatch made the self-host smoke matrix structurally unrunnable (everydocker compose/ build path was wrong), which blocks DoD #3 — self-host 3-OS PASS evidence.What
docker/Dockerfile.web— build at repo root (dropapps/web+ nonexistentpackages/core);npm install(repo ships no lockfile, sonpm cican't work);npx prisma generate; full-copynext startrunner (robust vs.output: standalonetracing, which isn't enabled and is finicky with Prisma). Copiesprisma.config.tsso runtimedb pushresolves schema + datasource.docker/entrypoint.sh— schema path → root;migrate deploy→db push(noprisma/migrations/folder ships); POSIXwhileloop; no--accept-data-loss(a destructive diff fails loudly instead of dropping a self-hoster's data; fresh DBs push cleanly).src/app/api/healthz/route.ts(new) — liveness probe: 200 iff server up + Postgres reachable. Distinct from/api/health(readiness — 503s when optional Stripe/Resend/OpenRouter are unconfigured, which is wrong fordocker compose up --waiton a minimal install). Both the compose healthcheck and the CI probe already point at/api/healthz..github/workflows/selfhost-smoke.yml— flat-repo path filters (src/**,prisma/**,public/**, root configs);COMPOSE_FILE=docker/docker-compose.ymljob-env so everydocker composestep finds the file without-f..gitattributes— pinDockerfile*to LF (Alpine build safety on Windows checkouts).Test plan
selfhost-smokematrix green onubuntu-22.04selfhost-smokematrix green onmacos-14selfhost-smokematrix green onmacos-13ci(type-check / lint / build / test / mcp-pack) greenCloses the structural blocker on #5.
🤖 Generated with Claude Code