Skip to content

Commit 45872b5

Browse files
committed
Merge main and bundle Sentry configuration
2 parents 92149d2 + 7e3e2a2 commit 45872b5

25 files changed

Lines changed: 1898 additions & 361 deletions

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ OPENAI_MODEL=gpt-4o-mini
1414
# ROUTER_OLLAMA_BASE_URL=
1515
# ROUTER_LMSTUDIO_MODEL=
1616
# ROUTER_TEMPERATURE=0.0
17+
# ROUTER_TIMEOUT_SECONDS=10.0
1718

1819
# Ollama settings (required if LLM_PROVIDER=ollama)
1920
OLLAMA_BASE_URL=http://localhost:11434

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
--cov-report=xml:coverage.xml
4141
- name: Upload backend coverage
4242
if: always()
43-
uses: actions/upload-artifact@v6
43+
uses: actions/upload-artifact@v7.0.1
4444
with:
4545
name: backend-coverage
4646
path: coverage.xml
@@ -50,7 +50,7 @@ jobs:
5050
run: uv run python -m src.evals.regression_gate --output reports/ai-regression-${{ github.sha }}.json
5151
- name: Upload AI regression score
5252
if: always()
53-
uses: actions/upload-artifact@v6
53+
uses: actions/upload-artifact@v7.0.1
5454
with:
5555
name: ai-regression-${{ github.sha }}
5656
path: reports/ai-regression-${{ github.sha }}.json
@@ -64,7 +64,7 @@ jobs:
6464
working-directory: ui
6565
steps:
6666
- uses: actions/checkout@v5
67-
- uses: actions/setup-node@v5
67+
- uses: actions/setup-node@v7
6868
with:
6969
node-version: 24
7070
cache: npm
@@ -79,7 +79,7 @@ jobs:
7979
run: npm run test:coverage
8080
- name: Upload UI coverage
8181
if: always()
82-
uses: actions/upload-artifact@v6
82+
uses: actions/upload-artifact@v7.0.1
8383
with:
8484
name: ui-coverage
8585
path: ui/coverage/
@@ -90,7 +90,7 @@ jobs:
9090
run: npx playwright test
9191
- name: Upload Playwright failure artifacts
9292
if: failure()
93-
uses: actions/upload-artifact@v6
93+
uses: actions/upload-artifact@v7.0.1
9494
with:
9595
name: playwright-failure-artifacts
9696
path: |
@@ -115,7 +115,7 @@ jobs:
115115
output: cortex-sbom.cdx.json
116116
scanners: vuln
117117
- name: Upload container SBOM
118-
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
118+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
119119
with:
120120
name: cortex-sbom-${{ github.sha }}
121121
path: cortex-sbom.cdx.json

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@
88

99
Cortex combines LangGraph orchestration, GraphRAG retrieval, durable background jobs, and end-to-end observability in a FastAPI and React application.
1010

11-
<img width="1722" height="898" alt="Cortex research workspace showing a generated report and source-grounded chat" src="https://github.com/user-attachments/assets/1f01409f-bc58-43ef-bc09-5ec2649acf00" />
11+
<video src="https://github.com/user-attachments/assets/fe5c280c-bbdc-4d49-8e9c-655a256b6b6f" controls></video>
12+
1213

13-
## Promo video
1414

15-
Watch Cortex move from workspace chat to a live research run and grounded follow-up questions:
16-
[MP4](assets/promo/cortex-research-promo.mp4) · [WebM](assets/promo/cortex-research-promo.webm)
1715

1816
## Why Cortex
1917

cloudrun/service.yaml

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,20 @@ spec:
4646
value: "openai"
4747
- name: OPENAI_MODEL
4848
value: "gpt-4o-mini"
49+
- name: ROUTER_TIMEOUT_SECONDS
50+
value: "10.0"
4951
- name: EMBEDDING_PROVIDER
5052
value: "openai"
5153
- name: EMBEDDING_MODEL
5254
value: "text-embedding-3-small"
5355
- name: EMBEDDING_DIMENSIONS
5456
value: "1536"
55-
- name: OPENAI_API_KEY
57+
- name: PROVIDER_CONFIG_JSON
5658
valueFrom:
5759
secretKeyRef:
58-
name: openai-api-key
60+
name: provider-config
5961
key: latest
6062
# --- Search ---
61-
- name: TAVILY_API_KEY
62-
valueFrom:
63-
secretKeyRef:
64-
name: tavily-api-key
65-
key: latest
6663
# --- Graph store (Neo4j Aura) ---
6764
# URI and username are not sensitive; only the password lives in Secret Manager.
6865
- name: NEO4J_URI
@@ -91,16 +88,28 @@ spec:
9188
- name: READINESS_REQUIRE_NEO4J
9289
value: "true"
9390
# --- Inngest ---
94-
- name: INNGEST_EVENT_KEY
91+
- name: INNGEST_CONFIG_JSON
9592
valueFrom:
9693
secretKeyRef:
97-
name: inngest-event-key
94+
name: inngest-config
9895
key: latest
99-
- name: INNGEST_SIGNING_KEY
96+
# --- Billing (Stripe) ---
97+
- name: BILLING_CONFIG_JSON
10098
valueFrom:
10199
secretKeyRef:
102-
name: inngest-signing-key
100+
name: billing-config
103101
key: latest
102+
# --- Redis cache ---
103+
# REDIS_URL is bundled into PROVIDER_CONFIG_JSON to stay within the
104+
# Secret Manager free active-version allowance.
105+
- name: REDIS_CACHE_TTL_DB_LIST_SECONDS
106+
value: "30"
107+
- name: STRIPE_SUCCESS_URL
108+
value: "https://cortex-ai-prod.vercel.app/billing/success"
109+
- name: STRIPE_CANCEL_URL
110+
value: "https://cortex-ai-prod.vercel.app/billing/cancel"
111+
- name: STRIPE_PORTAL_RETURN_URL
112+
value: "https://cortex-ai-prod.vercel.app"
104113
# --- CORS ---
105114
- name: CORS_ORIGINS
106115
value: '["https://cortex-ai-prod.vercel.app"]'
@@ -111,14 +120,9 @@ spec:
111120
name: internal-dispatch-secret
112121
key: latest
113122
# --- Observability ---
114-
# ponytail: observability off for the free-tier deploy; re-add
115-
# langsmith-api-key / langfuse-* secrets when tracing matters.
116-
- name: SENTRY_DSN
117-
valueFrom:
118-
secretKeyRef:
119-
name: sentry-dsn
120-
key: latest
121123
- name: SENTRY_ENVIRONMENT
122124
value: "production"
123125
- name: LANGSMITH_TRACING
124126
value: "false"
127+
- name: LANGFUSE_ENABLED
128+
value: "true"

docs/architecture.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ Routing policy:
6666

6767
- Greetings and other social turns normally resolve directly.
6868
- Weak or empty RAG context does not automatically trigger web search.
69+
- Missing pre-retrieved RAG context does not mean resources are absent; explicit requests about uploaded documents route to `answer_from_rag`, then retrieval runs.
70+
- Custom-agent routing receives the agent name, description, and linked/attached resource counts so short domain questions can still select `answer_from_rag` and retain citations.
6971
- Web search is selected for external, fresh, or otherwise web-dependent information.
7072
- A URL in the message or history is available context, not an automatic fetch instruction.
7173
- Direct URL fetching happens only when inspecting the resource is necessary.
7274
- Agent chat, workspace chat, and streaming/non-streaming endpoints use the same policy.
7375

74-
Workspace and agent document collections are deny-by-default: Cortex retrieves them only when the router explicitly selects `answer_from_rag`. Session attachments remain available as explicitly scoped resources. A routing failure after one structured-output repair returns `router_error` and does not retrieve documents.
76+
The workspace-wide document collection is deny-by-default: Cortex retrieves it only when the router explicitly selects `answer_from_rag`. A custom agent's linked resources and session attachments are explicitly scoped resources, so they remain available on every turn. A routing failure after one structured-output repair returns `router_error` and does not retrieve documents.
7577

7678
## Reliable ingestion
7779

docs/billing.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Cortex uses Stripe Hosted Checkout, signed webhooks, and the customer portal for
44

55
## Configuration
66

7-
Required variables:
7+
Required local-development variables:
88

99
- `STRIPE_SECRET_KEY`
1010
- `STRIPE_WEBHOOK_SECRET`
@@ -13,6 +13,11 @@ Required variables:
1313
- `STRIPE_CANCEL_URL`
1414
- `STRIPE_PORTAL_RETURN_URL`
1515

16+
In production, Cloud Run uses one `BILLING_CONFIG_JSON` Secret Manager value containing
17+
`stripe_secret_key`, `stripe_webhook_secret`, and `stripe_pro_price_id`; the three redirect
18+
URLs remain ordinary environment variables. This keeps the Stripe credentials within one
19+
active secret version.
20+
1621
Store production secret values in Google Secret Manager. See [Production configuration](env-vars-production.md).
1722

1823
## Webhook setup
@@ -46,4 +51,3 @@ flowchart LR
4651
```
4752

4853
Webhook events are the authoritative source for subscription state. The backend verifies the Stripe signature before updating `user_subscriptions`; research and chat quota guards then read the synchronized subscription and usage state.
49-

docs/env-vars-production.md

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ All secrets should be stored in **Google Secret Manager** and referenced via `va
88

99
| Env Var | Secret Manager | Notes |
1010
|---|---|---|
11-
| `OPENAI_API_KEY` | yes | Required when `LLM_PROVIDER=openai` |
11+
| `PROVIDER_CONFIG_JSON` | yes | Secret JSON with provider, Redis, Langfuse, LangSmith, and Sentry settings: `openai_api_key`, `tavily_api_key`, `redis_url`, `langfuse_public_key`, `langfuse_secret_key`, `langfuse_base_url`, `langsmith_api_key`, `langsmith_project`, `langsmith_endpoint`, `langsmith_redaction_mode`, `langsmith_sampling_rate`, `langsmith_tracing`, and `sentry_dsn`. Required when `LLM_PROVIDER=openai`. |
1212
| `OPENROUTER_API_KEY` | yes | Required when `LLM_PROVIDER=openrouter` |
1313
| `LLM_PROVIDER` | no | `openai` or `openrouter` |
1414
| `OPENAI_MODEL` | no | e.g. `gpt-4o-mini` |
15+
| `ROUTER_TIMEOUT_SECONDS` | no | Maximum duration of each router LLM request; production uses `10.0` |
1516
| `EMBEDDING_PROVIDER` | no | `openai` |
1617
| `EMBEDDING_MODEL` | no | `text-embedding-3-small` |
1718
| `EMBEDDING_DIMENSIONS` | no | `1536` |
18-
| `TAVILY_API_KEY` | yes | |
1919
| `ALPHA_VANTAGE_API_KEY` | yes | Required when `ASSET_PRICE_PROVIDER=alphavantage_mcp` |
2020
| `ASSET_PRICE_PROVIDER` | no | `alphavantage_mcp` |
2121
| `ALPHA_VANTAGE_MCP_URL` | no | Optional full remote MCP URL override |
@@ -31,12 +31,9 @@ All secrets should be stored in **Google Secret Manager** and referenced via `va
3131
| `READINESS_REQUIRE_SUPABASE` | no | Set to `true` in production; `/ready` returns `503` if Supabase is missing or unavailable |
3232
| `READINESS_REQUIRE_NEO4J` | no | Set to `true` in production; `/ready` returns `503` if Neo4j is missing or unavailable |
3333
| `READINESS_TIMEOUT_SECONDS` | no | Per-dependency probe timeout; defaults to `2.0` seconds |
34-
| `INNGEST_EVENT_KEY` | yes | From Inngest dashboard → Keys |
35-
| `INNGEST_SIGNING_KEY` | yes | From Inngest dashboard → Keys. **Must not be empty in prod.** |
36-
| `REDIS_URL` | yes | Upstash `rediss://` URL |
37-
| `STRIPE_SECRET_KEY` | yes | |
38-
| `STRIPE_WEBHOOK_SECRET` | yes | Update Stripe webhook URL to `https://<cloud-run-url>/api/billing/webhook` after first deploy |
39-
| `STRIPE_PRO_PRICE_ID` | no | |
34+
| `INNGEST_CONFIG_JSON` | yes | Secret JSON with `inngest_event_key` and `inngest_signing_key` from the Inngest dashboard. |
35+
| `REDIS_CACHE_TTL_DB_LIST_SECONDS` | no | Cache TTL for DB-heavy list/read-all responses; production uses `30`. `REDIS_URL` is supplied through `PROVIDER_CONFIG_JSON`. |
36+
| `BILLING_CONFIG_JSON` | yes | Secret JSON with `stripe_secret_key`, `stripe_webhook_secret`, and `stripe_pro_price_id`. This bundles Stripe credentials into one Secret Manager version. |
4037
| `STRIPE_SUCCESS_URL` | no | `https://<frontend>/billing/success` |
4138
| `STRIPE_CANCEL_URL` | no | `https://<frontend>/billing/cancel` |
4239
| `STRIPE_PORTAL_RETURN_URL` | no | `https://<frontend>` |
@@ -48,11 +45,11 @@ All secrets should be stored in **Google Secret Manager** and referenced via `va
4845
| Env Var | Secret Manager | Notes |
4946
|---|---|---|
5047
| `COHERE_API_KEY` | yes | Enables cross-encoder reranking |
51-
| `LANGFUSE_PUBLIC_KEY` | yes | |
52-
| `LANGFUSE_SECRET_KEY` | yes | |
48+
| `LANGFUSE_ENABLED` | no | Production sets this to `true`; Langfuse credentials and base URL are supplied through `PROVIDER_CONFIG_JSON`. |
49+
| `LANGSMITH_*` | no | LangSmith tracing, API key, project, endpoint, redaction mode, and sampling rate are supplied through `PROVIDER_CONFIG_JSON`. |
5350
| `LANGFUSE_ENV` | no | `prod` |
5451
| `LANGFUSE_RELEASE` | no | Git SHA or semver, e.g. `v1.2.3` |
55-
| `SENTRY_DSN` | yes | Error tracking; unset disables it entirely |
52+
| `SENTRY_DSN` | no | Local/split-variable fallback. Production stores `sentry_dsn` inside `PROVIDER_CONFIG_JSON`; unset disables Sentry entirely. |
5653
| `SENTRY_ENVIRONMENT` | no | Sentry issue environment; production deploys set this to `production` |
5754
| `RATE_LIMIT_DEFAULT` | no | Per-IP request limit, e.g. `60/minute` (default) |
5855

@@ -90,19 +87,16 @@ PROJECT=cortex-496709
9087
REGION=your-region
9188

9289
secrets=(
93-
openai-api-key
94-
tavily-api-key
90+
provider-config
9591
cohere-api-key
9692
neo4j-uri
9793
neo4j-username
9894
neo4j-password
9995
supabase-secret-key
10096
supabase-jwt-secret
101-
inngest-event-key
102-
inngest-signing-key
97+
inngest-config
10398
redis-url
104-
stripe-secret-key
105-
stripe-webhook-secret
99+
billing-config
106100
internal-dispatch-secret
107101
langfuse-public-key
108102
langfuse-secret-key
@@ -137,7 +131,7 @@ Configure email alerts for terminal failures in every registered function and fo
137131

138132
## Alerting post-deploy wiring
139133

140-
After creating a Sentry Python/FastAPI project, add its DSN to `.env.prod` as `SENTRY_DSN` or populate the `sentry-dsn` Secret Manager secret manually. Then deploy and configure a Sentry email issue alert for new or regressed production issues with a 30-minute per-issue cooldown.
134+
After creating a Sentry Python/FastAPI project, add its DSN as the `sentry_dsn` field in the existing `provider-config` JSON secret. This keeps production at six active Secret Manager versions. Then deploy and configure a Sentry email issue alert for new or regressed production issues with a 30-minute per-issue cooldown.
141135

142136
Provision Google Cloud alerts with:
143137

docs/observability.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Which signal fires for which failure class:
8585

8686
### Error tracking
8787

88-
Setting `SENTRY_DSN` enables Sentry for unhandled and selected handled-exception capture (error-capture only — tracing stays with LangSmith/LangFuse, PII and stack-trace locals are not sent). Production uses the Cloud Run revision as the Sentry release. Configure one Sentry issue alert for a new or regressed issue in `production`, delivered by email with a 30-minute per-issue cooldown. See [Production configuration](env-vars-production.md).
88+
Setting `SENTRY_DSN` locally, or `sentry_dsn` inside the production `PROVIDER_CONFIG_JSON` secret, enables Sentry for unhandled and selected handled-exception capture (error-capture only — tracing stays with LangSmith/LangFuse, PII and stack-trace locals are not sent). Production uses the Cloud Run revision as the Sentry release. Configure one Sentry issue alert for a new or regressed issue in `production`, delivered by email with a 30-minute per-issue cooldown. See [Production configuration](env-vars-production.md).
8989

9090
Handled-exception metadata is allowlisted to operational identifiers such as run, session, job, and outbox event IDs. Prompts, chat messages, documents, tokens, and authorization values are never attached.
9191

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ requires-python = ">=3.11"
77
dependencies = [
88
"langgraph>=0.2.0",
99
"langchain>=0.3.0",
10-
"langchain-community>=0.3.0",
10+
"langchain-community>=0.4.2",
1111
"langchain-openai>=0.2.0",
1212
"langchain-ollama>=0.1.0",
1313
"tavily-python>=0.5.0",
1414
"openai>=1.0.0",
15-
"fastapi>=0.115.0",
15+
"fastapi>=0.139.1",
1616
"uvicorn[standard]>=0.30.0",
1717
"slowapi>=0.1.9",
1818
"sentry-sdk[fastapi]>=2.0",
@@ -31,7 +31,7 @@ dependencies = [
3131
"inngest>=0.5.18",
3232
"llama-index-core>=0.12.0",
3333
"llama-index-embeddings-openai>=0.3.0",
34-
"llama-index-embeddings-ollama>=0.3.0",
34+
"llama-index-embeddings-ollama>=0.9.0",
3535
"stripe>=11.0.0",
3636
"redis>=5.0.0",
3737
"neo4j>=5.26.0",
@@ -40,7 +40,7 @@ dependencies = [
4040
"composio-langchain>=0.6.0",
4141
"langchain-tavily>=0.2.18",
4242
"arxiv-mcp-server>=0.5.0",
43-
"langchain-mcp-adapters>=0.2.2",
43+
"langchain-mcp-adapters>=0.3.0",
4444
]
4545

4646
[project.optional-dependencies]

scripts/deploy.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ gcloud container images add-tag "$TAG" "$LATEST" --quiet
5353
# ── Deploy ────────────────────────────────────────────────────────────────────
5454

5555
# Inject the SHA-tagged image (and project id) so Cloud Run sees a spec change
56-
TMP_YAML=$(mktemp /tmp/service-XXXXXX.yaml)
56+
TMP_YAML=$(mktemp /tmp/service-XXXXXX)
5757
sed "s|gcr.io/PROJECT_ID/cortex:latest|$TAG|g" "$SERVICE_YAML" > "$TMP_YAML"
5858

5959
if ! grep -q "$TAG" "$TMP_YAML"; then

0 commit comments

Comments
 (0)