Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx
SLACK_APP_TOKEN=xapp-x-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
SLACK_WORKSPACE=your_workspace

# Todoist API token (for tasks and overdue items in briefings)
# Create token at: https://app.todoist.com/app/settings/integrations/developer
TODOIST_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Authorized Slack User IDs (comma-separated)
# Find your user ID: Click profile -> More -> Copy member ID
SLACK_AUTHORIZED_USERS=U01234567,U89012345
Expand All @@ -41,6 +45,8 @@ BOT_MODE=agent

# Enable streaming responses (agent and multi_agent modes)
ENABLE_STREAMING=true
# Slack message edits redraw the full message; 1.0-1.5s usually feels smoother than token-level updates.
STREAMING_UPDATE_INTERVAL=1.25

# Direct email send behavior
# false (default): tool can only create drafts
Expand Down
28 changes: 28 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Agent Instructions

This repo is a personal/local assistant system. Treat local credentials, indexed data, and logs as sensitive. Do not expose the bot publicly or broaden access controls without an explicit security review.

## Orientation

- Main user-facing docs are in `README.md`.
- Claude-specific project notes are in `CLAUDE.md`; keep this file aligned with those notes when changing agent behavior.
- Runtime configuration lives in `src/config.py` and `.env.example`.
- The Slack bot supports `intent`, `agent`, and `multi_agent` modes. `multi_agent` routes through specialist agents in `src/bot/agents/`.
- Calendar "next/upcoming" behavior is current-time-aware through `USER_TIMEZONE`; preserve that invariant when changing calendar tools or prompts.

## Development

- Use `rg` for repo search.
- Prefer narrow, behavior-focused changes over broad refactors.
- Confirmable write actions live under `src/bot/actions/` and should be routed through the pending-action confirmation flow.
- Do not bypass Slack confirmation for writes such as email drafts/sends, calendar events, GitHub issues, Todoist changes, Notion writes, or Zotero additions.
- Run focused tests for touched areas, and run full `pytest` when changing shared bot, agent, tool, or integration code.

## Common Commands

```bash
pytest
pytest tests/test_executor.py
ruff check src tests
python scripts/run_bot.py
```
17 changes: 13 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Engram - Claude Code Instructions

Personal knowledge graph system aggregating data from 6 Google accounts, GitHub, and Slack with semantic search and Slack bot interface.
Personal knowledge graph system aggregating data from Google accounts, GitHub, Slack, Notion, Todoist, and Zotero with semantic search and a Slack bot interface.

## Project Structure

Expand All @@ -14,7 +14,10 @@ engram/
│ │ ├── google_multi.py # Multi-account manager with tiered search
│ │ ├── gmail.py, gdrive.py, gcalendar.py
│ │ ├── github_client.py
│ │ └── slack.py
│ │ ├── slack.py
│ │ ├── notion_client.py
│ │ ├── todoist_client.py
│ │ └── zotero_client.py
│ ├── indexers/ # Content indexers
│ │ ├── gmail_indexer.py, gdrive_indexer.py, gcal_indexer.py
│ │ ├── github_indexer.py, slack_indexer.py
Expand All @@ -27,7 +30,8 @@ engram/
│ │ ├── app.py # Main bot (Socket Mode)
│ │ ├── intent_router.py # LLM intent classification
│ │ ├── handlers/ # Intent handlers
│ │ └── actions/ # Confirmable actions
│ │ ├── agents/ # Multi-agent specialists
│ │ └── actions/ # Confirmable write actions
│ └── query/ # Query engine
├── scripts/ # Orchestration scripts
├── tests/ # Pytest tests
Expand All @@ -51,6 +55,9 @@ All secrets in `.env`:
- `OPENAI_API_KEY` - For embeddings
- `ANTHROPIC_API_KEY` - For intent classification
- `SLACK_AUTHORIZED_USERS` - Comma-separated user IDs
- `USER_TIMEZONE` - IANA timezone used for calendar and relative date handling
- `BOT_MODE` - `intent`, `agent`, or `multi_agent`
- `ENABLE_STREAMING` and `STREAMING_UPDATE_INTERVAL` - Slack streaming behavior

## Common Commands

Expand Down Expand Up @@ -97,7 +104,9 @@ SQLite database with tables:
- Socket Mode (no public URL needed)
- Claude Haiku for intent classification
- Multi-turn conversations with 30-min TTL
- Actions that modify data require confirmation
- Agent and multi-agent modes use Claude tool calling
- Calendar "next/upcoming" answers use current local time and exclude already-ended events
- Calendar create/update/cancel, Google Doc comments/replies/resolution, Todoist creates/updates/comments/completions/reopens, notification-setting changes, and other writes require Slack button confirmation

### Security
- OAuth tokens stored locally in `credentials/`
Expand Down
55 changes: 43 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This project is designed first for personal/local use. Do not expose it publicly

### Core Capabilities
- **Multi-Account Google Integration**: Sync Gmail, Google Drive, and Google Calendar from up to 6 accounts with tiered search (primary accounts searched first)
- **Google Write Capabilities**: Create email drafts, create/modify calendar events, and comment on Google Docs
- **Google Write Capabilities**: Create email drafts, create/modify/cancel calendar events, and comment/reply/resolve comments on Google Docs, with confirmation before writes
- **Zotero Integration**: Search papers, add references by DOI/URL with automatic metadata extraction (CrossRef + page scraping)
- **Notion & Todoist**: Search pages, manage tasks, create content
- **Knowledge Graph**: SQLite-based storage of entities (people, repos, files) and content with relationship tracking
Expand All @@ -23,11 +23,12 @@ This project is designed first for personal/local use. Do not expose it publicly
### Advanced Agent Features
- **Natural Conversation**: Chat naturally without triggering tool searches - greetings, questions about the bot, and general conversation are handled intelligently
- **Multi-Agent Architecture**: Orchestrator routes tasks to specialist agents (Calendar, Email, GitHub, Research) for domain expertise
- **Streaming Responses**: Real-time token-by-token response streaming for better UX
- **Streaming Responses**: Slack-friendly streaming with readable partial updates instead of choppy token-level edits
- **Tool Calling**: LLM-driven tool selection with multi-step execution capabilities
- **Persistent Memory**: Conversation history and user preferences survive restarts
- **Proactive Alerts**: Calendar reminders, important email notifications, and daily briefings
- **Confirmation-Gated Actions**: Sensitive actions use explicit Slack confirmation buttons with action-ID validation
- **Slack-Configurable Notifications**: Inspect and update proactive reminder/briefing/quiet-hours settings from Slack
- **Confirmation-Gated Actions**: Write actions use explicit Slack confirmation buttons with action-ID validation

### Security
- **Prompt Injection Protection**: Pattern-based detection and sanitization of malicious inputs
Expand Down Expand Up @@ -162,6 +163,12 @@ SLACK_BOT_TOKEN=xoxb-xxxxx
SLACK_APP_TOKEN=xapp-xxxxx
SLACK_WORKSPACE=your_workspace

# Notion API
# Create integration at: https://www.notion.so/my-integrations
# Share each target page/database with the integration
NOTION_API_KEY=secret_xxxxx
NOTION_WORKSPACE=default

# Authorized Slack User IDs (comma-separated)
# Find your ID: Click profile → More → Copy member ID
SLACK_AUTHORIZED_USERS=U12345678
Expand Down Expand Up @@ -197,6 +204,10 @@ AGENT_MODEL=claude-sonnet-4-20250514
# Enable streaming responses (applies to agent and multi_agent modes)
ENABLE_STREAMING=true

# Slack message edit interval for streaming responses.
# Slack redraws edited messages; 1.0-1.5s usually feels smoother than token-level updates.
STREAMING_UPDATE_INTERVAL=1.25

# Direct email send behavior
# false (default): draft-only (recommended)
# true: SendEmailTool enabled, but still requires explicit Slack confirmation button
Expand All @@ -215,6 +226,17 @@ AUDIT_RETENTION_DAYS=90
AUDIT_LOG_MESSAGES=false # Store raw message text in audit logs
```

### Notion Setup

1. Go to [Notion integrations](https://www.notion.so/my-integrations) and create an **Internal Integration**.
2. Copy the **Internal Integration Token** and set `NOTION_API_KEY` in `.env`.
3. In Notion, open each page/database you want indexed, click **Share**, and invite your integration.
4. Verify access:

```bash
python -c "from src.integrations.notion_client import NotionClient; print(NotionClient().test_connection())"
```

### Google Account Authentication

Run the OAuth setup for each Google account:
Expand Down Expand Up @@ -298,12 +320,13 @@ Legacy mode using intent classification with hardcoded handlers.
Single agent with LLM-driven tool calling.
- Dynamic tool selection
- Multi-step execution
- Streaming responses
- Slack-friendly streaming responses
- Natural conversation support

### Multi-Agent Mode (`multi_agent`)
Orchestrator routes to specialist agents.
- **Calendar Agent**: View events, check availability, create events with attendee invites
- **Calendar Agent**: View events, answer next/upcoming questions using current local time, check availability, create events with attendee invites
- **Calendar Agent**: View events, answer next/upcoming questions using current local time, check availability, create/update/cancel events with attendee notifications
- **Email Agent**: Search, drafts, and optional send (feature-flagged)
- **GitHub Agent**: PRs, issues, repository activity
- **Research Agent**: Semantic search, briefings
Expand All @@ -319,12 +342,16 @@ Talk to the bot via DM or @mention in channels:
| `Hi` / `Hello` | Natural greeting - no tool search triggered |
| `What can you do?` | Help and capabilities overview |
| `What's on my calendar today?` | Show today's events from all accounts |
| `What's my next event?` | Show the next event using your configured local timezone |
| `What's my schedule for tomorrow?` | Show tomorrow's calendar |
| `When am I free this week?` | Find available time slots |
| `Search for emails about [topic]` | Semantic search across emails |
| `Send an email to [person] about [topic]` | Create draft by default, or send via explicit confirmation if enabled |
| `Create a meeting with [person] tomorrow at 2pm` | Create calendar events |
| `Move event [id] to tomorrow at 3pm` | Update calendar events after confirmation |
| `Cancel event [id]` | Cancel calendar events after confirmation |
| `Find documents about [topic]` | Search Google Drive files |
| `Comment on Google Doc [id]: [comment]` | Add Google Doc comments after confirmation |
| `Show my open PRs` | List your GitHub pull requests |
| `What issues are assigned to me?` | List assigned GitHub issues |
| `Search my papers for [topic]` | Search Zotero library |
Expand All @@ -333,6 +360,7 @@ Talk to the bot via DM or @mention in channels:
| `Find papers by [author]` | Search papers by author |
| `What did I miss yesterday?` | Daily briefing for a specific date |
| `Help` | Show available commands |
| `Set my briefing to 8am weekdays` | Update proactive notification settings |

### Example Interactions

Expand Down Expand Up @@ -370,9 +398,10 @@ Bot: 🟢 Available slots tomorrow:
• 4:00 PM - 6:00 PM

You: Create a meeting with alice@company.com tomorrow at 2pm for 30 minutes
Bot: Created event "Meeting" on 2024-02-04 at 2:00 PM.
Calendar invite sent to alice@company.com.
https://calendar.google.com/event?eid=xxx
Bot: Please confirm creating this calendar event:
Event: Meeting
When: 2024-02-04 2:00 PM (30 min)
Attendees: alice@company.com
```

## Project Structure
Expand Down Expand Up @@ -417,13 +446,15 @@ engram/
│ │ ├── event_handlers.py # Message handlers with security
│ │ ├── intent_router.py # LLM intent classification
│ │ ├── conversation.py # Conversation state + persistence
│ │ ├── datetime_utils.py # Shared date/time parsing helpers
│ │ ├── formatters.py # Slack Block Kit formatting
│ │ ├── tools.py # Tool definitions for LLM
│ │ ├── executor.py # Agent executor with streaming
│ │ ├── user_memory.py # Long-term user preferences
│ │ ├── heartbeat.py # Proactive notifications
│ │ ├── security.py # Input sanitization + rate limiting
│ │ ├── audit.py # Comprehensive audit logging
│ │ ├── actions/ # Confirmable write actions
│ │ ├── handlers/ # Intent-specific handlers
│ │ │ ├── calendar.py
│ │ │ ├── email.py
Expand Down Expand Up @@ -462,7 +493,7 @@ engram/
│ └── audit.db # Security audit log
├── logs/ # Log files (gitignored)
├── credentials/ # OAuth tokens (gitignored)
└── tests/ # Test suite (360 tests)
└── tests/ # Test suite (361 passing, 6 skipped)
```

## Automation (macOS)
Expand All @@ -488,9 +519,9 @@ launchctl load ~/Library/LaunchAgents/com.engram.bot.plist
- **Data**: All indexed data stays local in `data/` (gitignored)
- **Bot Access**: Only Slack users listed in `SLACK_AUTHORIZED_USERS` can interact with the bot
- **Email Sending**: Draft-only by default. Set `ENABLE_DIRECT_EMAIL_SEND=true` to enable send, which still requires explicit confirmation.
- **Calendar Events**: The bot can create/modify events but confirms before making changes
- **Doc Comments**: The bot can add comments to Google Docs you have access to
- **GitHub Actions**: Issue creation requires explicit confirmation
- **Calendar Events**: The bot answers "next/upcoming" queries using `USER_TIMEZONE` and excludes events that already ended today. Creating, updating, and cancelling events require confirmation.
- **Doc Comments**: The bot can add, reply to, and resolve comments on Google Docs you have access to
- **Other Write Actions**: Email drafts, GitHub issues, Todoist task changes, Notion writes, and Zotero additions require explicit confirmation
- **Action Integrity**: Confirmation clicks are validated by action ID and thread-aware context lookup to prevent stale/mismatched execution
- **Confirmation Timeout**: Pending confirmations expire after 5 minutes and must be re-requested

Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ dependencies = [
"anthropic>=0.18.0",
"chromadb>=0.4.0",
"mem0ai>=0.1.0",
"numpy>=1.24.0",
"notion-client>=2.0.0",
"httpx>=0.25.0",
"requests>=2.31.0",
"tenacity>=8.2.0",
"tqdm>=4.66.0",
"python-dateutil>=2.8.0",
Expand Down
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@ openai>=1.0.0
anthropic>=0.18.0
chromadb>=0.4.0
mem0ai>=0.1.0
numpy>=1.24.0

# Notion
notion-client>=2.0.0

# Database
# SQLite is built-in

# Utilities
httpx>=0.25.0
requests>=2.31.0
tenacity>=8.2.0
tqdm>=4.66.0
python-dateutil>=2.8.0
Expand Down
61 changes: 61 additions & 0 deletions scripts/ideaspark_cron.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash
# IdeaSpark cron wrapper — loads env and runs daily pipeline.
#
# Usage (crontab -e):
# 0 5 * * * /Users/hani/Box\ Sync/CLAUDE/engram/scripts/ideaspark_cron.sh generate
# 0 22 * * * /Users/hani/Box\ Sync/CLAUDE/engram/scripts/ideaspark_cron.sh feedback
#
# Or use launchd (recommended on macOS) — see scripts/launchd/ directory.

set -euo pipefail

ENGRAM_DIR="$(cd "$(dirname "$0")/.." && pwd)"
LOGFILE="${ENGRAM_DIR}/logs/ideaspark_cron.log"
mkdir -p "${ENGRAM_DIR}/logs"

# Source .env for API keys
if [ -f "${ENGRAM_DIR}/.env" ]; then
set -a
source "${ENGRAM_DIR}/.env"
set +a
fi

# Use the correct Python — miniforge3 has project dependencies
if [ -f "${HOME}/miniforge3/bin/python" ]; then
PYTHON="${HOME}/miniforge3/bin/python"
elif [ -f "${HOME}/miniconda3/bin/python" ]; then
PYTHON="${HOME}/miniconda3/bin/python"
elif [ -f "${HOME}/anaconda3/bin/python" ]; then
PYTHON="${HOME}/anaconda3/bin/python"
elif command -v python3 &>/dev/null; then
PYTHON=python3
else
echo "$(date): ERROR — python3 not found" >> "$LOGFILE"
exit 1
fi

MODE="${1:-generate}"

{
echo "===== $(date) — ideaspark ${MODE} ====="
cd "${ENGRAM_DIR}"

case "$MODE" in
generate)
"$PYTHON" scripts/ideaspark_daily.py --generate 2>&1
;;
feedback)
"$PYTHON" scripts/ideaspark_daily.py --feedback 2>&1
;;
full)
"$PYTHON" scripts/ideaspark_daily.py 2>&1
;;
*)
echo "Unknown mode: ${MODE}. Use generate, feedback, or full."
exit 1
;;
esac

echo "===== done ====="
echo ""
} >> "$LOGFILE" 2>&1
Loading
Loading