Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,14 @@ QDRANT_PORT=6333

# ── Redis (hot cache) ────────────────────────────────────────────────────────
# Docker Compose overrides REDIS_HOST to `redis` for api/pipeline-worker.
# Cloud (Upstash): set REDIS_URL instead — takes precedence over HOST/PORT.
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=cortex_local
# REDIS_URL=rediss://default:token@host.upstash.io:6379

# Production API: seed demo graph on container start (true = first deploy only).
CORTEX_SEED_DEMO=false

# ── Kafka ────────────────────────────────────────────────────────────────────
# External listener on host :9092. Compose sets KAFKA_BOOTSTRAP_SERVERS=kafka:29092 for workers.
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/production-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Optional production uptime smoke — hits live Railway + Vercel proxy.
name: Production smoke

on:
schedule:
# Every 6 hours — keeps Railway warm-ish and catches regressions.
- cron: "0 */6 * * *"
workflow_dispatch:

permissions:
contents: read

jobs:
production-api:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Smoke Railway API
env:
CORTEX_PRODUCTION_URL: ${{ secrets.CORTEX_PRODUCTION_URL }}
run: |
URL="${CORTEX_PRODUCTION_URL:-https://cortex-api-production-fbd5.up.railway.app}"
python scripts/staging_smoke.py --url "$URL" --query "Why CockroachDB for payments?"

production-dashboard:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Smoke Vercel dashboard proxy
env:
CORTEX_DASHBOARD_URL: ${{ secrets.CORTEX_DASHBOARD_URL }}
run: |
URL="${CORTEX_DASHBOARD_URL:-https://frontend-ten-rouge-99.vercel.app}"
python scripts/staging_smoke.py --url "$URL" --query "Why CockroachDB for payments?"
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ htmlcov/
coverage.xml
*.cover

# Environment
.env
# Vercel local project link (never commit credentials)
.vercel/
.env.*
!.env.example

Expand Down
27 changes: 27 additions & 0 deletions .vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Cortex monorepo — Vercel hosts the Vite dashboard only.
# Whitelist: only frontend + root deploy config are uploaded (keeps bundle < 500 MB).
# Use leading paths where noted so frontend/scripts/ is never excluded by mistake.

*
!frontend
!frontend/**
!vercel.json
!middleware.ts
!.vercelignore

# Belt-and-suspenders: never upload Python backend even if patterns change
/api/
/pipeline/
/connectors/
/extraction/
/graph/
/intelligence/
/memory/
/scoring/
/shared/
/mcp/
/tests/
pyproject.toml
uv.lock
.python-version
*.py
40 changes: 39 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
.PHONY: demo demo-dry-run test ci stack init-kafka pipeline-restart pipeline-local verify-pipeline verify-github verify-jira verify-linear verify-connectors
.PHONY: demo demo-dry-run test ci stack init-kafka pipeline-restart pipeline-local verify-pipeline verify-github verify-jira verify-linear verify-connectors portfolio-demo seed-dev import-oss import-oss-graph seed-cloud verify-dual verify-dual-production verify-production

# Production API URL for smoke tests (override on CLI).
CORTEX_PRODUCTION_URL ?= https://cortex-api-production-fbd5.up.railway.app

# Local portfolio demo: Docker infra + migrations + seed + API + worker + frontend.
demo:
Expand Down Expand Up @@ -45,3 +48,38 @@ verify-linear:
python scripts/verify_slack_pipeline.py --source linear --timeout 120

verify-connectors: verify-pipeline verify-github verify-jira verify-linear

# Dual-workspace: synthetic dev seed vs OSS import smoke.
seed-dev:
uv run python scripts/seed_demo.py --workspace local-dev

import-oss:
uv run python scripts/import_github_org.py --org tiangolo --repo fastapi --dry-run

# Direct Neo4j import (no Kafka) — use for cloud / portfolio backends.
import-oss-graph:
uv run python scripts/import_github_graph.py --org tiangolo --repo fastapi --workspace oss-tiangolo-fastapi --limit 30

# Synthetic OSS workspace seed when GitHub import is unavailable.
seed-oss-fastapi:
uv run python scripts/seed_oss_fastapi_demo.py --workspace oss-tiangolo-fastapi

# Seed + OSS import against remote Neo4j (set NEO4J_URI / credentials in env).
seed-cloud: seed-dev seed-oss-fastapi

verify-dual:
uv run python scripts/dual_workspace_smoke.py --workspaces local-dev,oss-tiangolo-fastapi

verify-production:
uv run python scripts/staging_smoke.py --url $(CORTEX_PRODUCTION_URL) --query "Why CockroachDB for payments?"

verify-dual-production:
uv run python scripts/dual_workspace_smoke.py --url $(CORTEX_PRODUCTION_URL) --workspaces local-dev,oss-tiangolo-fastapi

# Vercel frontend build smoke (injects API rewrites when CORTEX_API_ORIGIN is set).
verify-vercel-build:
cd frontend && npm run build

# Portfolio demo: Docker API must be up; starts cloudflared and prints Vercel env instructions.
portfolio-demo:
@./scripts/start_portfolio_demo.sh
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@

| In 3 minutes | Command |
|---|---|
| **Run the demo** | `make demo` → [localhost:3000](http://localhost:3000) |
| **Ask a question** | Workspace `local-dev` → *Why CockroachDB for payments?* |
| **Wire an agent** | Add the MCP block below — `cortex_query`, `cortex_inject`, `cortex_remember` |
| **Live demo** | **[frontend-ten-rouge-99.vercel.app](https://frontend-ten-rouge-99.vercel.app)** — workspace `local-dev`, ask *Why CockroachDB for payments?* |
| **Run locally** | `make demo` → [localhost:3000](http://localhost:3000) |
| **Wire an agent** | [MCP setup in 60s](docs/MCP_SETUP.md) — `cortex_query`, `cortex_inject`, `cortex_remember` |

> **Portfolio / LinkedIn:** Share the [live demo link](https://frontend-ten-rouge-99.vercel.app). For a 24/7 backend (no laptop required), deploy the API to Render/Railway — [docs/PORTFOLIO_DEMO.md](docs/PORTFOLIO_DEMO.md).

---

Expand Down Expand Up @@ -217,6 +219,25 @@ This brings up Kafka, Neo4j, Redis, Postgres, applies graph migrations, writes t

**Recording a video or GIF for the README:** see [docs/DEMO_RECORDING.md](docs/DEMO_RECORDING.md). **Validating real webhooks (Slack / GitHub / Jira):** [docs/CONNECTOR_VALIDATION.md](docs/CONNECTOR_VALIDATION.md).

### Deploy preview (Vercel dashboard)

Host the **dashboard only** on Vercel (`frontend/` as root directory). The API and pipeline worker run on Docker, Railway, or similar — do not deploy the repo root as FastAPI on Vercel (full `uv.lock` exceeds Lambda size limits).

| Step | Action |
|------|--------|
| 1 | Deploy `frontend/` with `CORTEX_API_ORIGIN=https://your-api.example.com` |
| 2 | Edge `middleware.ts` proxies same-origin `/query` to the API at runtime |
| 3 | Avoid `VITE_API_URL` pointing at tunnel URLs (511 errors) |
| 4 | API on Railway/Render via `railway.toml` or `render.yaml` — see [docs/DEPLOY.md](docs/DEPLOY.md) |

Full guide: [docs/DEPLOY.md](docs/DEPLOY.md). **Dual workspaces** (synthetic `local-dev` vs real OSS imports): [docs/DATA_SOURCES.md](docs/DATA_SOURCES.md).

```bash
make seed-dev # local-dev synthetic decisions
make import-oss # dry-run GitHub PR import preview
make verify-dual # compare queries across workspaces
```

### Context API (REST)

| Method | Path | Purpose |
Expand Down
45 changes: 45 additions & 0 deletions SESSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,3 +528,48 @@
1. Merge PR #15 after CI green
2. Phase 6: dashboard polish + demo video
3. Staging smoke with `CORTEX_CMVK_BACKEND=openai`

---

## Session — 2026-06-11 — Vercel v1 deployment + dual-workspace restore
**Duration:** ~45m
**Phase:** Phase 6 — deploy preview + dual-workspace tooling

### Built
- Restored deleted dual-workspace files from HEAD (`import_github_org.py`, `DEPLOY.md`, `vercel.json`, tests)
- **`frontend/middleware.ts`** — runtime Edge proxy via `CORTEX_API_ORIGIN` (fixes build-time rewrite limitation on Vercel)
- **`railway.toml`**, **`render.yaml`** — API v1 deploy blueprints
- **`api/memory.py`** — `REDIS_URL` support for Upstash cloud Redis
- **`docs/DEPLOY.md`**, **`README.md`** — Railway/Render + middleware deploy guide
- Vercel production redeploy: **https://frontend-ten-rouge-99.vercel.app** — `/health` and `/query` verified end-to-end

### State at end
- Branch **`feature/dual-workspace-import`** — uncommitted changes (middleware, railway.toml, render.yaml, REDIS_URL)
- **370 passed** (full suite, `--no-cov`)
- `CORTEX_API_ORIGIN` on Vercel points at ephemeral localtunnel — replace with Railway URL for stable demo

### Next session starts with
1. `railway login` + deploy API; seed Neo4j Aura; update `CORTEX_API_ORIGIN`
2. Commit + PR for dual-workspace + Vercel middleware
3. Optional: `import_github_org.py` live run for `oss-tiangolo-fastapi`

---

## Session — 2026-06-11 — Portfolio demo link live
**Duration:** ~20m
**Phase:** Phase 6 — public demo for portfolio / LinkedIn

### Built
- **`docs/PORTFOLIO_DEMO.md`** — shareable demo URL, recruiter walkthrough, 24/7 Render/Railway path
- **`scripts/start_portfolio_demo.sh`** + **`make portfolio-demo`** — cloudflared tunnel helper
- **README** — Live demo row points to Vercel production URL
- Vercel `CORTEX_API_ORIGIN` updated; production verified (`/health` 200, `/query` 3 hits)

### State at end
- **Portfolio URL:** https://frontend-ten-rouge-99.vercel.app
- Demo works while Docker API + `cloudflared` run on Mac; Render/Railway needed for 24/7 LinkedIn traffic
- Uncommitted: middleware, railway.toml, render.yaml, portfolio docs

### Next session starts with
1. Deploy API to Render/Railway + Aura for always-on demo
2. Commit + push deployment slice
2 changes: 1 addition & 1 deletion api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ RUN pip install --no-cache-dir --upgrade pip \

EXPOSE 8000

CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["sh", "-c", "uvicorn api.main:app --host 0.0.0.0 --port ${PORT:-8000}"]
30 changes: 30 additions & 0 deletions api/Dockerfile.query
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Query-only Cortex API for cloud deploy (Render/Railway portfolio demo).
# Semantic search off (CORTEX_SEMANTIC_ENABLED=false) — no torch/spacy/mlflow.
FROM python:3.11-slim

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV CORTEX_SEMANTIC_ENABLED=false

COPY api/requirements-query.txt ./requirements-query.txt
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements-query.txt

COPY api ./api
COPY connectors ./connectors
COPY extraction ./extraction
COPY graph ./graph
COPY scoring ./scoring
COPY shared ./shared
COPY pipeline ./pipeline
COPY memory ./memory
COPY intelligence ./intelligence
COPY scripts ./scripts

EXPOSE 8000

RUN chmod +x scripts/start_api_production.sh

CMD ["scripts/start_api_production.sh"]
15 changes: 13 additions & 2 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,15 +245,26 @@ async def query(
workspace_id=request.workspace_id,
)

try:
coverage_score = await memory().workspace_coverage(request.workspace_id)
except Exception:
coverage_score = 0.0

return QueryResponse(
query=request.query,
workspace_id=request.workspace_id,
results=results,
total=len(results),
latency_ms=latency_ms,
coverage_score=coverage_score,
)


def _inject_result_limit(max_tokens: int) -> int:
"""Map agent token budget to decision count (~400 tokens each), always ≥1."""
return max(1, min(max_tokens // 400, 10))


@app.post(
"/inject",
response_model=InjectResponse,
Expand All @@ -266,7 +277,7 @@ async def inject(
) -> InjectResponse:
"""Inject relevant organizational memory into an AI agent's context window.

Called by the MCP server (mcp/server.ts) on every agent tool invocation.
Called by the MCP server (mcp/server.js) on every agent tool invocation.
Returns the most relevant decisions ranked by importance × trust × recency.

Ranks injectable decisions by importance × trust (see scoring.trust_scorer).
Expand All @@ -285,7 +296,7 @@ async def inject(
context=request.context,
workspace_id=request.workspace_id,
caller_roles=roles,
limit=min(request.max_tokens // 400, 10),
limit=_inject_result_limit(request.max_tokens),
)
except Exception as exc:
log.error("inject.failed", error=str(exc), agent_id=request.agent_id)
Expand Down
26 changes: 19 additions & 7 deletions api/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,22 @@ def _build_redis_client() -> Any | None:
except ImportError:
return None

redis_url = os.environ.get("REDIS_URL", "").strip()
try:
client = redis.Redis(
host=os.environ.get("REDIS_HOST", "localhost"),
port=int(os.environ.get("REDIS_PORT", "6379")),
password=os.environ.get("REDIS_PASSWORD"),
socket_connect_timeout=1,
decode_responses=True,
)
if redis_url:
client = redis.from_url(
redis_url,
socket_connect_timeout=1,
decode_responses=True,
)
else:
client = redis.Redis(
host=os.environ.get("REDIS_HOST", "localhost"),
port=int(os.environ.get("REDIS_PORT", "6379")),
password=os.environ.get("REDIS_PASSWORD"),
socket_connect_timeout=1,
decode_responses=True,
)
client.ping()
return client
except Exception:
Expand Down Expand Up @@ -136,6 +144,10 @@ async def query_decisions(

return results

async def workspace_coverage(self, workspace_id: str) -> float:
"""Return heuristic completeness score for a workspace."""
return await self._graph.workspace_coverage_score(workspace_id)

async def inject_decisions(
self,
*,
Expand Down
22 changes: 22 additions & 0 deletions api/requirements-query.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Minimal deps for query-only Cortex API (portfolio demo on Render/Railway).
# Skips torch, spacy, mlflow, sentence-transformers — semantic search disabled.
fastapi>=0.111.0
uvicorn[standard]>=0.29.0
pydantic>=2.7.0
pydantic-settings>=2.3.0
python-dotenv>=1.0.1
neo4j>=5.20.0
redis>=5.0.4
structlog>=24.2.0
httpx>=0.27.0
tenacity>=8.3.0
python-multipart>=0.0.9
prometheus-client>=0.20.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4
opentelemetry-api>=1.42.0
opentelemetry-sdk>=1.42.0
opentelemetry-exporter-otlp-proto-http>=1.42.0
opentelemetry-instrumentation-fastapi>=0.51b0
confluent-kafka>=2.4.0
openai>=1.30.0
6 changes: 6 additions & 0 deletions api/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ class QueryResponse(BaseModel):
results: list[DecisionResult]
total: int
latency_ms: float
coverage_score: float = Field(
default=0.0,
ge=0.0,
le=1.0,
description="Heuristic workspace memory completeness (0–1)",
)


class InjectRequest(BaseModel):
Expand Down
Loading
Loading