Keystone App Guide
Keystone is the personal knowledge management (PKM) system for LFIQ. It provides daily briefings, task management, automation orchestration, and a dashboard for personal workflows.
What It Does
Keystone is a personal operating system for knowledge workers:
- Daily briefing: Auto-generated daily summary of portfolio activity, market movements, and observations
- Tasks: Personal task management, synced across devices
- Automation: Python scripts for ETL, data enrichment, and integrations
- Dashboard: Real-time metrics, portfolio summary, market pulse
- Connections: OAuth connectors to M365, Slack, GitHub, Pinecone, and other services
- MCP server: Provides brick_* tools for Claude Code and Anthropic agents
Primary features:
- Daily briefing markdown (regenerated each morning)
- Task list (database-backed)
- Automation runner (local launchd + Cloud Run)
- Connectors UI (OAuth token management)
- Metrics dashboard
Deployment
| Environment | URL | Status | Platform |
|-------------|-----|--------|----------|
| Production | https://keystone.lfiq.app | Live | Vercel |
| Preview | https://keystone-branch.lfiq.app | Auto-deploy on PR | Vercel |
| Local Dev | http://localhost:3003 | Via npm run dev | Local machine |
| MCP Server | keystone-mcp.lfiq.app:3457 | Cloud Run | Remote |
Tech Stack
| Component | Tech | Notes | |-----------|------|-------| | Frontend | Next.js 15 | React 19, dashboard, task UI | | Language | TypeScript | Full type coverage | | Auth | Auth.js | Session-based, database RLS | | Database | Neon (public schema) | Tasks, daily briefing, automation state | | Backend | Python (launchd + Cloud Run) | ETL, briefing generation, automation | | MCP Server | Python (Fly.io or Cloud Run) | Provides brick_* tools | | Secrets | Keychain + GCP Secret Manager | OAuth tokens, API keys | | Deployment | Vercel (frontend) + Cloud Run (backend) | Auto-deploy on main |
Local Development
Start the Frontend
cd /path/to/02-brick.apps/apps/keystone
npm run dev
# Runs on http://localhost:3003
Start the Backend (Python Automation)
Keystone's backend runs Python automation scripts. There are two ways to run them:
Option 1: Local launchd (for scheduled jobs)
# Automation scripts are managed by launchd plists
# View current automation jobs:
launchctl list | grep pkm
# Start automation daemon
launchctl load ~/Library/LaunchAgents/com.leftfieldinv.keystone-automation.plist
# View logs
tail -f ~/Library/Logs/keystone-automation.log
Option 2: Cloud Run (for testing)
# Deploy Python automation to Cloud Run
gcloud run deploy keystone-automation \
--source=/path/to/02-brick.apps/apps/keystone \
--runtime=python311 \
--project=brickston-v2
# Invoke manually
gcloud run jobs execute keystone-automation --project=brickston-v2
Environment Variables
| Variable | Required? | Purpose |
|----------|-----------|---------|
| DATABASE_URL | Yes | Neon connection (public schema, pkm role) |
| NEXTAUTH_SECRET | Yes | Session encryption |
| ANTHROPIC_API_KEY | Yes | Claude for briefing generation |
| PINECONE_API_KEY | No | For embeddings and search |
| M365_CLIENT_ID | No | Microsoft OAuth |
| M365_CLIENT_SECRET | No | Microsoft OAuth secret |
| SLACK_BOT_TOKEN | No | Slack notifications |
| GITHUB_TOKEN | No | GitHub API access |
| PKM_AUTOMATION_SECRET | Yes | Shared secret for automation requests |
Pull from Vercel:
vercel env pull
Daily Briefing Generation
Each morning (usually 6 AM PT), Keystone generates a daily briefing markdown file. The briefing includes:
- Portfolio overview (occupancy, rent, revenue)
- Market movements (rent trends, competitor activity)
- Observations from Intel (high-priority items)
- Lease expirations (next 30 days)
- Maintenance summary (recent work orders)
- Task summary (overdue and due-today items)
Briefing location: PKM daily folder (organized by date)
Files are named: briefing-YYYY-MM-DD.md
How Briefing is Generated
-
Automated Cloud Run job (nightly, 18:00 UTC or on-demand)
gcloud run jobs execute keystone-briefing-generator --project=brickston-v2 -
Python script runs queries against Neon:
- Properties and occupancy (portfolio schema)
- Recent observations (items schema)
- Market trends (market schema)
- Tasks (public schema)
-
Claude enrichment (optional):
- Observations are summarized by Claude
- Insights are generated (risk alerts, opportunities)
- Markdown is formatted with headers and tables
-
File saved to
public.daily_briefingtable -
Notification sent (Slack, iMessage, email) with link to briefing
Key Flows
Flow 1: Generate Daily Briefing
- Cloud Run job (keystone-briefing-generator) triggers at scheduled time
- Python script queries Neon: properties, leases, observations, tasks
- Markdown template is rendered with data
- Claude API enriches insights (optional)
- Markdown saved to public.daily_briefing table
- User notified via Slack or iMessage with link to briefing
Flow 2: Create Task (via Frontend)
- User opens keystone.lfiq.app/tasks
- User clicks "New Task"
- Form: title, description, due date, priority
- Submit → INSERT into public.tasks
- Task appears in task list and daily briefing
- If due today, notification sent
Flow 3: Run Automation Script
-
User or schedule triggers automation:
# Via launchd (automatic) # Or manual trigger: gcloud run jobs execute my-automation-job --project=brickston-v2 -
Python script runs (ETL, data enrichment, etc.)
-
Results logged to Cloud Logging
-
Output saved to database or external service
-
Notification sent if errors occur
Automation Scripts
Keystone's automation directory contains reusable Python scripts:
apps/keystone/automation/
├── jobs/
│ ├── briefing_generator.py # Daily briefing
│ ├── task_syncer.py # M365 task sync
│ ├── property_enricher.py # Enrich properties with market data
│ └── ...
├── connectors/
│ ├── m365.py # Microsoft Graph API
│ ├── slack.py # Slack notifications
│ ├── pinecone.py # Vector embeddings
│ └── ...
├── mcp_server.py # Anthropic MCP server
└── requirements.txt # Python dependencies
Running an Automation Script Locally
# Install Python dependencies
cd /path/to/02-brick.apps/apps/keystone
pip install -r automation/requirements.txt
# Run a script
python automation/jobs/briefing_generator.py
# Or with environment variables (from GCP secrets)
export DATABASE_URL=$(gcloud secrets versions access latest --secret=items-hub-database-url --project=brickston-v2)
export ANTHROPIC_API_KEY=$(gcloud secrets versions access latest --secret=anthropic-api-key --project=brickston-v2)
python automation/jobs/briefing_generator.py
MCP Server (keystone-mcp)
Keystone provides an MCP server that exposes brick_* tools for Claude Code and Anthropic agents. This allows agents to:
- Query tasks and projects
- Create and update tasks
- Query portfolio data
- Access briefing data
- Run automations
Accessing MCP Tools
In Claude Code or an agent:
/brick pkm_get_dashboard_state
This calls the Keystone MCP server and returns current PKM state.
Server Location
- Production: keystone-mcp.lfiq.app (Fly.io)
- Local dev: localhost:3457 (if running locally)
Troubleshooting
Issue 1: "Daily briefing not generated"
Symptom: Briefing folder is empty or very old
Cause: Cloud Run job failed, Neon query error, or schedule not running
Fix:
# Check job status
gcloud run jobs describe keystone-briefing-generator --project=brickston-v2
# View recent logs
gcloud run logs read keystone-briefing-generator \
--project=brickston-v2 --limit=50
# Manually trigger
gcloud run jobs execute keystone-briefing-generator --project=brickston-v2
Issue 2: "Tasks not syncing from M365"
Symptom: New M365 tasks don't appear in Keystone
Cause: M365 OAuth token expired or task_syncer job failed
Fix:
# Re-authenticate M365
# Visit keystone.lfiq.app/connectors
# Click "Reconnect" on Microsoft 365
# Authorize in browser
# Then manually run syncer
gcloud run jobs execute keystone-task-syncer --project=brickston-v2
Issue 3: "MCP server returns 'connection refused'"
Symptom: Claude Code can't connect to brick_* tools
Cause: keystone-mcp.lfiq.app is down or unreachable
Fix:
# Check service status
gcloud run services describe keystone-mcp --project=brickston-v2
# Check if service is running
curl https://keystone-mcp.lfiq.app/health
# Redeploy if needed
cd /path/to/02-brick.apps/apps/keystone
gcloud run deploy keystone-mcp \
--source=. --runtime=python311 --project=brickston-v2
Issue 4: "Database role 'pkm' has no permissions"
Symptom: 403 error when creating tasks
Cause: User role doesn't have write access to public schema
Fix:
# Verify pkm role permissions
psql -U neondb_owner -h ep-tiny-lab-akrddwgy.us-west-2.neon.tech neondb \
-c "GRANT SELECT, INSERT, UPDATE ON public.tasks TO pkm;"
# Restart app
npm run dev
Common Tasks
Task 1: Generate Briefing On-Demand
gcloud run jobs execute keystone-briefing-generator --project=brickston-v2
# Or from CLI:
./apps/keystone/automation/jobs/briefing_generator.py
Task 2: Query Task List
SELECT id, title, due_date, status, priority
FROM public.tasks
WHERE status = 'open' AND due_date <= now() + interval '7 days'
ORDER BY due_date ASC;
Task 3: Sync M365 Tasks to Keystone
# Manual trigger
gcloud run jobs execute keystone-task-syncer --project=brickston-v2
# Or in Python:
python automation/jobs/task_syncer.py
Related Documentation
- Architecture: PKM data topology, MCP server, daily briefing workflow
- Getting Started: Setup, Logins, Install Tools
- Brick MCP: Using brick_* tools in Claude Code
- Cloud Run: Job execution, monitoring, logging