Eva is a proactive, event-driven personal AI operator. The repository contains the application foundation, durable Event backbone, Gmail ingestion, Goal and Situation domain, relevance engine, scoped long-term memory, bounded email investigation, user-specific Telegram conversation, and approval-gated Gmail drafting and sending.
- Python 3.14 (managed automatically by
uv) - uv 0.12+
- Docker with Docker Compose
Copy .env.example to .env, then run:
make setup
make db-up
make migrate
make runThe API listens on http://127.0.0.1:8000.
curl http://127.0.0.1:8000/health/live
curl http://127.0.0.1:8000/health/readyBoth endpoints return HTTP 200 when the application and database are ready.
Milestone 5.5 packages the API and continuous worker into one immutable image and provisions the
production runtime with Terraform. Pull requests receive no Google credential; after CI succeeds on
main, GitHub Actions authenticates through Workload Identity Federation, applies Terraform with
workers paused, runs migrations, restores the configured worker count, and checks API readiness.
The same stack provisions a bounded Cloud Tasks queue and a private, IAM-authenticated action
executor. Proposal and dispatch paths use EVA_ACTIONS_ENABLED; the private runtime uses the
separate EVA_ACTION_EXECUTOR_ENABLED boot gate.
The one-time bootstrap and first Gmail activation steps are in the
GCP deployment guide.
Eva's dependency-free public website lives in site/. It provides the product homepage and the stable policy URLs used by Google OAuth:
https://evaatyourservice.com/https://evaatyourservice.com/privacy/https://evaatyourservice.com/terms/
Preview the site locally without starting Eva's backend:
uv run python -m http.server 8080 --directory siteOpen http://127.0.0.1:8080. The site contains no external scripts, analytics, cookies, or build-time dependencies. Its structural and policy contract tests run as part of make verify.
The Deploy public site GitHub Actions workflow publishes site/ after a site-related change reaches main. In the repository's Settings → Pages, select GitHub Actions as the build source if it is not selected automatically. Configure the custom domain as evaatyourservice.com before changing DNS; GitHub recommends setting the custom domain first to reduce takeover risk.
After the custom domain is saved in GitHub, add these records in Hostinger's DNS Zone Editor:
| Type | Name | Target |
|---|---|---|
| A | @ |
185.199.108.153 |
| A | @ |
185.199.109.153 |
| A | @ |
185.199.110.153 |
| A | @ |
185.199.111.153 |
| CNAME | www |
bugsbunnywanders.github.io |
Do not remove or replace existing MX or TXT records used for email, ownership, or other services. Wait for GitHub's DNS check to pass, then enable Enforce HTTPS. DNS and certificate issuance can take time to propagate.
Once all three HTTPS URLs resolve publicly:
- Verify
evaatyourservice.comin Google Search Console, normally with the DNS TXT method. - In Google Auth Platform → Branding, set the application homepage to
https://evaatyourservice.com/, the privacy policy tohttps://evaatyourservice.com/privacy/, and the terms link tohttps://evaatyourservice.com/terms/. - Add
evaatyourservice.comas an authorized domain and save the branding configuration. - In Audience, publish the OAuth app to In production. Personal use can remain unverified, but Google may show an unverified-app warning.
- Reauthorize the Gmail connector after production publishing so the local Testing refresh token is replaced with a production authorization.
The Pages merge does not itself change Hostinger DNS, verify Search Console, publish the OAuth app, or rotate Gmail authorization. Those remain deliberate operator steps.
Milestone 2 adds Desktop OAuth bootstrap, Gmail watch/history synchronization, a Google Pub/Sub pull subscriber, persisted watch maintenance, and expired-cursor recovery. Setup requires manual Google Auth Platform configuration and local GCP resources; follow the Gmail ingestion operator guide before running the worker.
The local command surface is:
uv run eva scope create --display-name "Saswat Ray" --workspace-name personal
uv run eva gmail connect --user-id USER_UUID --workspace-id WORKSPACE_UUID
uv run eva gmail sync --connector-id CONNECTOR_UUID
uv run eva gmail pull
uv run eva gmail maintainThe four Gmail Make wrappers consume IDs from exported environment variables as literal shell data. Create the scope with the direct CLI command above, export the UUIDs it prints, then use:
export EVA_USER_ID=USER_UUID
export EVA_WORKSPACE_ID=WORKSPACE_UUID
export EVA_GMAIL_CONNECTOR_ID=CONNECTOR_UUID
make gmail-connect
make gmail-sync
make gmail-pull
make gmail-maintainThere is no public Gmail webhook or HTTP ingestion endpoint. Gmail notifications are consumed from the configured pull subscription.
With PostgreSQL running:
make verifyThis runs Ruff formatting and lint checks, strict mypy checking, unit tests, PostgreSQL integration tests, and migration verification.
Create revisions deliberately and review them before applying:
uv run alembic revision --autogenerate -m "describe change"
uv run alembic upgrade headMigrations never run automatically during API startup.
The product architecture is in spec/2026-08-29-proactive-personal-ai-agent-design.md. Milestone designs and implementation plans are stored under docs/superpowers/.
Milestone 1 adds a durable event backbone with this flow:
NewEvent -> PostgreSQL transaction [Event + EventProcessing + OutboxMessage]
-> OutboxRelay claim -> Publisher acknowledgement
-> EventProcessor claim -> EventHandler -> HANDLED
Local tests use the in-memory publisher. When the Google Pub/Sub adapter is selected, it
uses Application Default Credentials and requires EVA_PUBSUB_PROJECT_ID; this milestone
does not create any GCP resources. Publication is at-least-once, so event handling remains
idempotent across redelivery.
Milestone 1's Event and Outbox reliability boundary is reused by the Milestone 2 Gmail subscriber. Telegram behavior remains deferred.
Milestone 3 adds scoped Goal and Situation persistence and services, lifecycle enforcement, optimistic Situation snapshots, explicit Event/Goal relationships, and deterministic Gmail-thread correlation. The local CLI can create, list, show, and update Goals and can list and inspect Situations with stable JSON output.
Gmail ingestion itself does not create Situations. The Milestone 4 relevance consumer invokes the resolver only for email that warrants operational attention. See the Goal and Situation operator guide for the mental model, command examples, lifecycle rules, and troubleshooting.
Milestone 4 turns stored Events into durable, revisitable relevance decisions:
Gmail -> Event + OutboxMessage -> continuous Event Relay -> eva-events
-> relevance consumer -> screening -> bounded AI context -> application policy
-> versioned Signal -> optional Gmail-thread Situation
The Event Relay and relevance consumer are independent long-running processes. IGNORE and
RECORD decisions remain queryable and can be explicitly re-evaluated later; NOTIFY and
INVESTIGATE create or reuse Situations but do not yet message the user or execute actions.
Relevance is disabled by default and historical scans are always explicit. See the
Relevance operator guide for GCP setup, privacy boundaries,
configuration, commands, and recovery procedures.
Milestone 5 adds two provenance-rich memory layers and a bounded context assembly path:
Explicit fact -> scoped versioned slot -> active structured context
Explicit episode -> bounded OpenAI embedding -> pgvector candidate retrieval
Situation + active Goals + facts + reranked episodes -> AgentWorkingContext
Facts preserve supersession and retraction history. Episodes are immutable summaries, support Goal and Situation links, and never expose their vectors through the service or CLI. Every operation is filtered by User and Workspace. Gmail and relevance events do not learn memory automatically in this milestone. See the Memory and Context operator guide for lifecycle, safety boundaries, configuration, examples, and smoke testing.
Milestone 6 launches a full agent after the relevance policy chooses NOTIFY or INVESTIGATE:
NOTIFY/INVESTIGATE Signal -> AgentRun + transactional outbox -> eva-agent-runs
-> scoped context + read-only Gmail tools
-> strict proposal-only AgentInvestigationResult
The same Cloud Run worker process now hosts a fourth agent-pull loop. Runs are leased, retried, idempotent per Signal/version, and tenant-scoped. Email remains untrusted evidence; the model cannot choose another account or gain write authority. Results may propose notifications, Situation updates, memory, follow-ups, or future actions. Milestone 7 now delivers notification proposals; the other proposal types remain inert. See the Agent investigation operator guide for setup, privacy boundaries, inspection, and recovery.
Milestone 7 makes Eva both proactive and reactive over a paired private Telegram chat:
Agent notification -> Notification + outbox -> Telegram delivery -> proactive message
User message -> authenticated webhook -> Event + outbox -> conversation agent -> Notification
-> Telegram delivery -> reply
Pairing binds immutable numeric Telegram user and chat IDs to one Eva User and Workspace. Every
conversation, Situation, memory lookup, Gmail read, and response remains within that persisted
scope. Replies to proactive messages recover the originating Situation; ordinary messages use the
active general chat, and /new starts a fresh one. The shared Cloud Run worker now supervises six
continuous loops. This milestone's Gmail tools remain read-only; Milestone 8 adds a separate,
approval-gated action path. See the Telegram operator guide.
Milestone 8 lets the agent propose reply or brand-new Gmail drafts without giving the model a Gmail write tool:
Agent/conversation intent -> deterministic validation -> Gmail draft creation
-> exact Telegram card [Send | Change | Discard]
-> immutable approval -> outbox -> Pub/Sub -> Cloud Tasks
-> private action executor -> Gmail -> durable result + Telegram status
Draft creation is automatic, but sending always requires the paired user to approve the exact
recipients, subject, body, and thread context. Change invalidates the old approval and produces a
new complete draft version; Discard deletes only Eva's exact managed draft. Approvals expire
after 24 hours. Ambiguous send outcomes become UNKNOWN and are never retried blindly.
Existing connectors must be reauthorized for both gmail.readonly and gmail.compose before write
actions work. Reauthorization preserves the connector identity, original connection boundary,
Gmail history cursor, and watch state. See the
Gmail actions operator and smoke-test guide.