Skip to content

Latest commit

 

History

History
162 lines (120 loc) · 6.4 KB

File metadata and controls

162 lines (120 loc) · 6.4 KB

Personal Agent Template — Architecture

Back to README | See also: Environment, Customization

This document describes the technical architecture of Personal Agent Template — a durable personal AI assistant built with Eve, Nuxt 4, and Better Auth.

System overview

The app runs as two cooperating services on Vercel:

flowchart TB
  subgraph surfaces [User surfaces]
    web[Web chat — Nuxt]
    slack[Slack DMs and mentions]
    imessage[iMessage — Photon]
  end

  subgraph eve [Eve agent — agent/]
    channels[Channels: eve · slack · photon]
    tools[Tools: weather · github]
    skills[Skills · memory · connections: Linear MCP]
  end

  subgraph nuxt [Nuxt app — app/ + server/]
    api["/api/* — public API"]
    internal["/api/internal — agent-only"]
    auth[Better Auth]
    db[(NuxtHub PostgreSQL — Drizzle)]
  end

  connect[Vercel Connect — GitHub · Linear · Slack · Photon]

  surfaces --> eve
  eve -->|"HTTP + Bearer INTERNAL_API_SECRET"| nuxt
  api --> db
  internal --> db
  auth --> db
  nuxt --> connect
Loading
Vercel service Entry Role
web / Nuxt UI + Nitro API
eve /eve/v1/* Eve agent runtime (service generated by eve/nuxt)

The eve/nuxt module generates the Vercel service and its /eve/v1/* route at build time — vercel.json does not declare them.

Project structure

personal-agent-template/
├── agent/                    # Eve agent
│   ├── agent.ts              # Model and agent config
│   ├── channels/             # eve (web), slack, photon (iMessage)
│   ├── tools/                # weather
│   ├── extensions/           # mounted eve extensions (github)
│   ├── memory/               # memory slots (profile)
│   ├── skills/               # e.g. daily-summary.md
│   ├── connections/          # Linear MCP
│   ├── lib/                  # profile-internal, slack-internal, phone-internal, internal-api
│   └── instructions.ts       # system instructions
├── app/                      # Nuxt frontend
│   ├── pages/                # chat, settings, login
│   ├── components/           # chat UI, profile, integrations
│   └── composables/          # useProfile, useConnectors, chat providers
├── server/                   # Nitro API
│   ├── api/                  # Public + internal routes
│   ├── db/                   # Drizzle schema + migrations
│   └── utils/                # profile, auth, connectors, threads
├── shared/                   # Cross-layer types and helpers
│   ├── agent.ts              # Branding metadata
│   └── types/                # profile, thread, connector
└── docs/                     # Documentation

Request flows

Web chat

  1. User opens /chat/[id] — Nuxt loads thread via /api/threads
  2. Chat streams through Eve's Nuxt module (eve/nuxt)
  3. Tool calls render in MessageContentEve.vue
  4. Approvals and questions render through AgentInputRequest.vue

Memory recall

  1. Eve resolves the profile slot's scope to the authenticated principal (agent/memory/profile.ts)
  2. fileMemory() reads that principal's document from private Vercel Blob storage
  3. The indexed entries are recalled on turn.started and again after compaction

Records arrive as untrusted user-role messages, never as system instructions. The agent maintains them with profile__save_memory and profile__remove_memory.

Slack

  1. Slack events hit Eve's slack channel (agent/channels/slack.ts)
  2. Linked users map Slack ID → app user via slack_links table
  3. Unlinked users get instructions to generate a link code in the web app
  4. Link flow: web generates code → user DMs link <code> → agent consumes via internal API

Integrations (Linear)

  1. User connects Linear in Settings → Integrations
  2. Vercel Connect provisions MCP credentials
  3. Eve connection (agent/connections/linear.ts) exposes Linear tools to the agent

Internal API

Routes under /api/internal/* require:

Authorization: Bearer <INTERNAL_API_SECRET>

Validated in server/utils/internal-api.ts.

Route Purpose
GET /api/internal/profile Caller identity for the session instructions
GET /api/internal/phone/link Resolve phone number → app user
GET /api/internal/slack/link/member Resolve Slack user → app user
POST /api/internal/slack/link/consume Consume link code

Agent-side clients live in agent/lib/*-internal.ts.

Database

PostgreSQL via NuxtHub, postgres-js driver. Schema in server/db/schema/.

Key tables:

Table Purpose
user / session / account Better Auth
threads Chat threads
user_profile Name, timezone, phone
phone_links Phone ↔ app user mapping, used by the iMessage channel
slack_links Slack ↔ app user mapping
slack_link_codes Temporary link codes

Migrations: pnpm db:generatepnpm db:migrate.

server/db/schema/auth.ts is generated by the Better Auth CLI, not hand-written. db:generate regenerates it before diffing the schema, so the adapter's expected tables and the migration cannot drift. Run pnpm auth:schema on its own after changing the Better Auth config.

Memory model

  • Provider — Eve's fileMemory(), one bounded document per scope
  • ScopebyPrincipal: one document per authenticated caller
  • Storage — private Vercel Blob, eve/memory/file/<scope>/MEMORY.md
  • Toolsprofile__save_memory, profile__remove_memory
  • Bounds — 4,000 recalled characters, 2 KB per entry, 64 KB per document

Auth

Better Auth with email/password. Config: server/utils/auth.ts, route: server/api/auth/[...all].ts.

Global middleware: app/middleware/auth.global.ts.

Eve docs

For channels, tools, connections, and deployment details, read Eve guides in node_modules/eve/dist/docs/public/.