sf-agent is built on eve, a filesystem-first agent
framework: capability comes from which folder a file lands in. This doc is the
map. For the framework's own guides, read node_modules/eve/docs/.
agent/
agent.ts Runtime config (model = claude-sonnet-5).
instructions/ Always-on system prompt, composed alphabetically.
00-identity.md Who the agent is ("Computer"), who Steven is.
05-voice.md Tone & persona (sincere + authoritative/ironic, dense, no snark/corny).
10-behavior.md Operating rules (proactive capture, confirm side effects).
20-memory.md How to use the memory tools.
30-calendar.md How to use the calendar tools.
channels/ How you reach the agent.
telegram.ts Conversational chat + inbound images (vision).
discord.ts Slash commands + the bot's presence (owner-gated).
eve.ts Default HTTP API, locked to localDev + vercelOidc.
cron.ts Secret-protected reminder trigger (external cron).
gmail.ts Gmail push (Pub/Sub) + watch-renew endpoints.
presence.ts Home/away updates from the Pi (records a fact).
discord-ingest.ts Batched Discord messages from the Pi's gateway listener → triage.
wake.ts The Pi's clock calling back to run a real agent turn.
tools/ Typed actions the model can call.
capture.ts Quick-capture inbox.
list_inbox.ts List inbox notes with ids.
delete_note.ts Remove a handled note.
current_time.ts Authoritative local time (host clock + DST-aware offset).
set_location.ts Set/clear a travel timezone override (auto-expires).
recall.ts Search notes + facts + tasks + Discord digests + email log.
discord_recap.ts What people were talking about on Discord (per-channel digests).
remember_fact.ts Save/update durable profile facts.
list_facts.ts List all profile facts.
add_task.ts Add a task or recurring chore.
list_tasks.ts List open/all tasks.
complete_task.ts Finish a task; recurring chores roll forward.
reopen_task.ts Reopen a task wrongly assumed done.
list_calendar_events.ts Read Google Calendar.
create_calendar_event.ts Write to Google Calendar (approval-gated).
delete_calendar_event.ts Delete a Google Calendar event (approval-gated).
harmonic_matches.ts Camelot-wheel compatible keys (DJ).
list_work.ts List work from the GitHub board.
add_work_item.ts Add a card to the GitHub board.
set_work_status.ts Move a card on the GitHub board.
set_work_schedule.ts Set the recurring work rhythm (days/hours/meetings).
set_work_day.ts One-off work-day override (PTO/WFH/holiday).
work_schedule.ts Read the schedule + a day's computed work status.
search_email.ts Search Gmail.
read_email.ts Read one email's body.
send_email.ts Send email (approval-gated).
modify_email.ts Archive / mark read / unread.
attention_status.ts Per-surface policy + what crossed each surface and what was done.
configure_attention.ts Mute/notify/escalate/capture per surface (enforced in code).
schedule_wake.ts One future moment — a real agent turn, or plain text.
manage_wakeups.ts The agent's recurring entries (incl. brief + review).
remind_me.ts Delayed plain-text reminder via the home Pi worker.
remind_when.ts Reminder or wake on getting home / leaving (presence).
list_reminders.ts Everything on the clock: one-shots, presence, recurring.
cancel_reminder.ts Cancel a pending one-shot by id or key.
lighting_status.ts Inspect live LIFX state, automation, and configuration.
set_lights.ts Direct per-light/all-light power and HSBK control.
manage_lighting_schedule.ts Edit the Pi's recurring lighting entries.
lighting_effect.ts Themes, drift, scenes, party mode, and flashes.
configure_lighting.ts Tune daemon and per-light behavior.
check_parking.ts One-shot parking-camera check (Wyze cam via the Pi).
watch_parking.ts Timed parking watch on the Pi; Telegram ping when a spot opens.
get_weather.ts Real weather via Open-Meteo (no key): current + forecast.
skills/ On-demand procedures (loaded when relevant).
plan_a_trip.md Family-aware travel planning.
dj_set.md DJ sets, harmonic mixing, crate.
freelance.md Client/project ops.
gamedev.md Devlog, backlog, playtests.
work.md GitHub board + day-job work schedule.
gmail.md Email: read, search, triage, send.
lighting.md Home-lab lighting themes + tuning.
autonomy.md Attention policy + scheduling its own thinking.
new_baby.md Second-daughter prep: checklists, leave, logistics.
subagents/ Specialist child agents.
planner/ Example: decompose a fuzzy goal into a plan.
lib/ Shared, import-only code (never enters the sandbox).
store/ The durable-memory backbone (see below).
attention.ts Per-surface policy: mute / notify / escalate, enforced in code.
triage.ts The one triage path every input surface runs through.
clock.ts The agent's clock on the Pi (wakes + recurring entries).
horizon.ts Look ahead; put precise moments on the clock.
recurrence.ts Next-due computation for recurring chores.
time.ts Pure tz toolkit (DST-aware offset, local "today", quiet hours).
location.ts Effective tz: travel override (set_location) or home.
work-schedule.ts Resolve work rhythm per day (weekly pattern + overrides).
task-reconcile.ts Assume-done pass: decay overdue, roll recurring.
google.ts Service-account Calendar access (JWT, zero-dep).
gmail.ts Gmail via OAuth refresh token (read/send/manage/watch).
github.ts Projects v2 GraphQL (one board, zero-dep).
telegram.ts Direct Bot API send (zero-token pings).
remote.ts Transport to the home Pi worker + the parking camera.
evals/ Scored behavior checks (eve eval).
docs/ This folder.
The root agent's name comes from package.json name (sf-agent). Identity is
always path-derived — no file sets its own name.
defineState from eve is session-scoped — it dies with the conversation.
Everything a personal assistant remembers (notes, facts, chores) must outlive the
session, so it lives in a small storage layer instead:
kv.ts— a five-methodKvseam (get/set/del/keys/mget;settakes an optional TTL for ephemera like dedup marks). Backed by Vercel KV / Upstash over its REST API whenKV_REST_API_URL+KV_REST_API_TOKENare set (zero extra dependencies, justfetch); otherwise an in-memory map that does not persist across restarts — and on Vercel's serverless runtime it won't survive between invocations, so configure KV before relying on memory there. Lists read via one batchedMGET, not a round trip per key.repositories.ts— typed repos (notes,facts,tasks,reminders,conversations,activity,attention) that own their key prefixes and JSON serialization.conversationsholds rolling per-channel Discord digests — capped at 30 entries per channel with a 30-day TTL, so heavy chatter can never swamp the store.activityis the surface-generic spine: a rolling per-source log of what crossed each interaction surface (every non-spam email, every Discord channel that saw conversation, every arrival home) plus what the agent did with it —skipped,muted,considered,handled, orescalated, with the actions taken and why. Capped at 200 entries per source with the same 30-day TTL. New surfaces write toactivitywith their ownsourcetag and get recall for free.attentionholds the standing per-surface policy (below) — small, no TTL, configuration rather than traffic.index.ts— assemblesstoreonce; import it anywhere:import { store } from "../lib/store/index.js";
Swapping to Postgres or another backend is a one-file change (kv.ts) — repos
and tools don't move.
The project's ethos is to reduce attention, not demand it — so Steven never has to tell the agent a task is finished. Tasks decay on their own:
- Recurring chores roll forward to their next occurrence once past due.
- Low-stakes one-offs are silently assumed done a day or two after the due date
(
TASK_GRACE_DAYS). High-stakes tasks (set onadd_task) never are — they keep surfacing until handled. - Email correlation: the triage pass closes any open task an incoming email confirms is done (a receipt, a delivery, a confirmation), with a quiet FYI.
The decay + recurring roll live in lib/task-reconcile.ts, run each sweep by the
cron channel before reminders are collected. Every auto-close is recoverable with
reopen_task. Done tasks are deleted for good after TASK_DONE_RETENTION_DAYS
(default 90) so the store never grows unbounded. All recurrence math runs in
Steven's local day (never raw UTC), and rolling a long-overdue chore catches up
to the next genuinely future date.
- Telegram verifies the
X-Telegram-Bot-Api-Secret-Tokenheader, andonMessagedrops anything not fromOWNER_TELEGRAM_USER_ID. - Discord verifies Ed25519 signatures on every interaction, and
onCommanddrops anything not fromOWNER_DISCORD_USER_ID. - eve HTTP channel is restricted to
localDev()+vercelOidc()— reachable from your own CLI and trusted Vercel deployments, not the public web. Add real auth before exposing a browser UI. - Side-effecting actions (sending, booking, buying) are gated with
human-in-the-loop approval —
needsApprovalfromeve/tools/approval, as oncreate_calendar_event. The instructions also tell the model to confirm first. - Secrets stay server-side: the Google service-account key and any connection credentials live in tools/env and never reach the model.
See .env.example. Pull deployed values with vercel env pull.
| Var | Purpose |
|---|---|
TELEGRAM_BOT_TOKEN / TELEGRAM_WEBHOOK_SECRET_TOKEN / TELEGRAM_BOT_USERNAME |
Telegram channel |
OWNER_TELEGRAM_USER_ID |
Restrict the agent to you; also the brief's target |
DISCORD_PUBLIC_KEY / DISCORD_APPLICATION_ID / DISCORD_BOT_TOKEN |
Discord channel |
OWNER_DISCORD_USER_ID |
Restrict Discord commands to you |
KV_REST_API_URL / KV_REST_API_TOKEN |
Durable store (required on Vercel) |
GOOGLE_SERVICE_ACCOUNT_EMAIL / GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY |
Google service account |
GOOGLE_CALENDAR_ID |
Calendar to read/write (your email address) |
OWNER_TIMEZONE |
Local tz for "today" + quiet hours |
APPOINTMENT_LEAD_MIN |
Minutes before an event to wake (default 60) |
HORIZON_HOURS |
How far ahead the sweep plans wakeups (default 36) |
TASK_DONE_RETENTION_DAYS |
Days to keep completed tasks before pruning (default 90) |
CRON_SECRET |
Bearer secret for POST /eve/v1/cron/reminders (the planning sweep) |
WAKE_SECRET |
Bearer secret for POST /eve/v1/wake (the Pi's clock starting a turn) |
TRIAGE_MODEL |
Cheap triage model for every surface (default anthropic/claude-haiku-4.5) |
GITHUB_TOKEN |
PAT with Projects read/write |
GITHUB_PROJECT_ID |
The agent's board (a copy of your real board) — node id |
GOOGLE_OAUTH_CLIENT_ID / _SECRET / _REFRESH_TOKEN |
Gmail (OAuth user token) |
GMAIL_PUBSUB_TOPIC |
Pub/Sub topic for users.watch |
GMAIL_PUSH_SECRET |
Guards the Gmail push + watch endpoints |
PI_WORKER_URL / PI_WORKER_SECRET |
Home Pi worker (Tailscale Funnel): the clock, lighting, camera, gateway |
PRESENCE_SECRET |
Guards POST /eve/v1/presence (home/away from the Pi) |
DISCORD_INGEST_SECRET |
Guards POST /eve/v1/discord/ingest (message batches from the Pi) |
<SURFACE>_TRIAGE_MODEL |
Per-surface model override, e.g. DISCORD_TRIAGE_MODEL |
Add a tool — drop a file in agent/tools/. Filename = tool name.
import { defineTool } from "eve/tools";
import { z } from "zod";
import { store } from "../lib/store/index.js";
export default defineTool({
description: "Mark a task or chore as done.",
inputSchema: z.object({ id: z.string() }),
async execute({ id }) {
return (await store.tasks.complete(id)) ?? { error: "not found" };
},
});Google Calendar (service account) — wired via lib/google.ts and the
list_calendar_events / create_calendar_event / delete_calendar_event tools. No per-user OAuth: a
service account signs a JWT locally (zero-dep, Node crypto) and exchanges it for
an access token. To set it up:
- In Google Cloud (project with the Calendar API enabled), create a service account and download a JSON key.
- In Google Calendar → your calendar's Settings and sharing → Share with specific people → add the service account's email with "Make changes to events".
- Set
GOOGLE_SERVICE_ACCOUNT_EMAIL,GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY(the PEM, newlines escaped as\n), andGOOGLE_CALENDAR_ID(your email address — notprimary, which is the service account's own empty calendar).
A service account can't reach Google Tasks on a personal Gmail (no sharing,
no domain-wide delegation), so Tasks would need the OAuth refresh-token route.
The agent's own add_task / list_tasks cover to-dos in the meantime.
Add a proactive job — not a file. Recurring work lives on the Pi's clock as a
named entry the agent edits with manage_wakeups (see below). agent/schedules/
is deliberately empty: a Vercel cron would be a build-time constant the agent
couldn't touch.
Add a specialist — a folder in agent/subagents/<id>/ with an agent.ts
that exports a description. It inherits nothing from the root, so give it its
own tools/skills as needed.
Every input surface — email, Discord, and whatever comes next — runs through one
path in lib/triage.ts, parameterized by a durable per-surface policy in
lib/attention.ts. A surface is a name, a normalizer into SurfaceEvent, and a
policy record; it is not a second copy of the triage logic. That symmetry is the
point: preferences used to stick on email and not Discord because they were two
hand-written prompts.
The order is deliberately not all up to the model:
- Log — everything that crossed the surface, before any judgment, so recall works for mail and chatter the agent never read.
- Mute — deterministic patterns. Matching items are logged and never seen by a model or reported. This is the hard off switch.
- Escalate — deterministic patterns (or the triage model asking) promote an item to a full agent turn with every tool and all of memory. Escalation beats muting. It costs a real turn, so the bar is "I can't act correctly without it."
- Triage — one cheap structured call, restricted to what
captureallows. - Notify — gated in code.
notify: "never"outranks whatever triage decided;"always"reports everything read.
Patterns are case-insensitive substrings, optionally scoped (from:, subject:,
body:, channel:) or written as /regex/. Steps 2 and 5 exist because "stop
telling me about sign-in emails" has to be something the agent can enforce — the
old design could only append that sentence to a prompt and hope a small model
honored it. guidance is the remaining soft steer, for judgment calls patterns
can't express; a legacy <source>_triage_rules fact is folded in as guidance so
rules set the old way keep working.
The agent reads all of it with attention_status (policies + the per-item log of
what it did and why) and changes it with configure_attention. Defaults ship
muting the automated mail nobody wants a buzz for — sign-in alerts, verification
codes, 2FA — and every default is editable, including back on.
The Pi worker holds time, and the agent owns what's on it. Vercel Hobby caps cron at a couple of daily jobs defined at build time, which is exactly the wrong shape: the agent could not add, retime, or remove its own recurring thinking. Two shapes, both durable across a reboot:
- One-shot (
schedule_wake) — fires once at a moment. Aremindersends fixed text; awakeruns a real agent turn viaPOST /eve/v1/wake, so the agent decides then what to do, with everything it knows at that time — or stays quiet, which is the expected outcome for most wakeups. An optionalkeymakes a wake idempotent: re-scheduling the same key re-times the pending job instead of stacking a duplicate. - Recurring (
manage_wakeups) — named entries with minute-precise local times and weekday selection, edited by name with the same replace/remove/upsert idiom as the lighting schedule.morning_briefandevening_reviewlive here as ordinary entries, seeded once on a worker that has never had a cron file. "Move my brief to 7:30" or "stop the evening review" is a tool call, not a deploy.
presence triggers take the same fork — remind_when with kind: think wakes the
agent on arrival or departure instead of pushing a string.
The sweep no longer notifies. POST /eve/v1/cron/reminders
(Authorization: Bearer $CRON_SECRET, driven every 30 min by
.github/workflows/reminders.yml) runs the assume-done reconcile and then
plans: lib/horizon.ts walks the next HORIZON_HOURS (default 36) of calendar
events and due tasks and asserts a keyed wake on the Pi for each — an appointment
at start − APPOINTMENT_LEAD_MIN, a task at 9am on its due day. Keys make this
safe to re-run, and a meeting that moves drags its wake along with it. A moment
landing in quiet hours (10pm–7am) shifts back to 9pm the previous evening rather
than being dropped.
That inverts the old behavior, which polled and could only announce that a date had already passed — news Steven already had. The sweep's cadence now affects only how quickly a newly created event gets a wake planned, never when he hears anything. Since every timed nudge depends on the worker, the sweep also reports a worker it can't reach — once a day, not once a sweep.
Steven's recurring contexts are packaged as on-demand skills rather than
always-on prompt: plan_a_trip (family travel), dj_set (sets + harmonic mixing,
backed by the harmonic_matches tool), freelance (client/project ops),
gamedev (devlog + backlog), and new_baby (second-daughter prep: checklists,
leave, logistics). The model loads one only when a request matches its
description, so they never bloat every turn, and each leans on the shared memory
and calendar tools. A pack graduates to a subagent only if it needs its own tool
surface or a distinct role.
Deployed on Vercel, eve tags every workflow run with $eve.* attributes, so the
Agent Runs tab traces each session → turn → tool call with token usage — no
instrumentation.ts required. To export AI SDK spans to an OpenTelemetry backend
(Braintrust, Datadog, Honeycomb, …), add agent/instrumentation.ts:
import { defineInstrumentation } from "eve/instrumentation";
import { registerOTel } from "@vercel/otel";
export default defineInstrumentation({
setup: ({ agentName }) => registerOTel({ serviceName: agentName }),
});It runs before agent code, so keep it minimal and install the exporter you use.
Set recordInputs / recordOutputs to false if spans shouldn't carry message
content.
evals/ holds eve eval checks — deterministic (completed, calledTool,
content includes) plus one LLM-as-judge (closedQA) for reply quality. The
judge routes through the AI Gateway and skips cleanly without credentials, so a
plain run never errors. .github/workflows/ci.yml type-checks every push and PR;
run eve eval --strict in CI when you want soft-threshold misses to fail too.
The agent fully controls one GitHub Projects (v2) board — a copy of your real
board, so the original is never touched. It reads and writes that copy:
list_work summarizes current work, add_work_item adds draft cards, and
set_work_status moves them (matched by title, so opaque node ids never enter the
model's context).
It's three small authored tools over the Projects GraphQL API (lib/github.ts),
not a GitHub MCP — a deliberately token-frugal choice, since an MCP pushes a large
tool surface into context every turn. The work skill loads only on demand, and
list_work returns just titles + status. Set GITHUB_TOKEN and
GITHUB_PROJECT_ID.
Gmail uses OAuth (a user refresh token), not the Calendar service account — a
service account can't reach a personal inbox. lib/gmail.ts refreshes the access
token and calls the Gmail REST API; tools are search_email, read_email,
send_email (approval-gated — sending is irreversible), and modify_email.
Real-time processing runs through the gmail channel, not polling — and it
doesn't ping per email (you already see mail arrive). It quietly keeps tasks and
memory in sync, and only pings when something is time-sensitive:
- Gmail publishes mailbox changes to a Pub/Sub topic; Pub/Sub push-delivers to
POST /eve/v1/gmail/push?token=<GMAIL_PUSH_SECRET>. The push carries only ahistoryId, so the webhook fetches the delta (paginated) and dedupes per message. The stored baseline advances only after a successful sweep, so a transient failure retries instead of dropping mail; an expired baseline (Gmail keeps ~a week) resets with a warning. HTML-only mail is tag-stripped so triage isn't blind to it. - Free spam/bulk barrier (no model): Gmail's Promotions / Social / Updates /
Forums / Spam categories and mailing-list mail (
List-Unsubscribe,Precedence: bulk) are logged asskippedand go no further. Only Spam/Trash are excluded from the log entirely, so "did I get that email from GEICO the other day?" is answerable viarecalleven for mail the agent never read. - Everything else goes to the shared triage path with the
emailpolicy — the same code, the same policy shape, and the same activity log Discord uses. See Attention for the ordering. The cheap structured call (Haiku by default) records tasks (with stakes — a bill or deadline is marked high so it never silently decays), saves durable facts, schedules a reminder or a think on the Pi, closes tasks the mail confirms are done, and can escalate to a full turn. It does not auto-reply or write to the calendar — those become tasks. - Notification philosophy (identical on every surface, because it's the same
code): report what the agent did, never what's happening — Steven already sees
his own inbox. Every triage that takes actions sends one compact Telegram report
(
+ task: …,⏰ reminder …,✓ closed: …). A human-notify fires only when a real person is specifically trying to reach him or a deadline would genuinely hurt to miss. Automated mail is muted by default in code rather than argued down in a prompt. users.watchexpires after 7 days, soPOST /eve/v1/gmail/watchrenews it, driven daily by.github/workflows/gmail-watch.yml(no Vercel cron used).
Set the OAuth trio plus GMAIL_PUBSUB_TOPIC, GMAIL_PUSH_SECRET, and optionally
EMAIL_TRIAGE_MODEL (or the surface-generic TRIAGE_MODEL).
Some work doesn't fit a serverless function — arbitrary-delay reminders, long-
running or interactive jobs. Those go to an always-on worker on a home Raspberry
Pi (sf-pi-worker), reached over
Tailscale Funnel (a stable public HTTPS URL) and gated by PI_WORKER_SECRET.
Most importantly it holds the agent's clock (see above): one-shot jobs — a
reminder that sends text, or a wake that calls POST /eve/v1/wake to run a
real agent turn — plus the durable recurring entries the agent owns
(src/crons.ts). Both survive a reboot; a failed delivery retries and is only
abandoned once it's hours stale, because by then it isn't the moment anymore.
list_reminders shows everything pending, cancel_reminder drops a one-shot (by
id or key), manage_wakeups edits the recurring set. The job protocol is typed
and extensible, so new long-running job types are added on the worker side
without touching the agent's serverless limits.
The worker also runs a cooperative LIFX lighting daemon on the LAN. Its durable, agent-editable schedule supports arbitrary named entries, weekday selection, minute-precise local times, power-off windows, and independent choices about reclaiming manually held bulbs or interrupting a held theme. Party mode and direct control remain available even when automatic lighting is disabled.
Five focused tools expose the mechanics instead of prescribing routines:
lighting_status, set_lights, manage_lighting_schedule, lighting_effect, and
configure_lighting. Direct changes can hold per bulb; custom themes retain gentle
palette drift; configuration covers avoided colors, timing, brightness, and
exclusions. Party mode remains the deliberate takeover exception: it snapshots
every participating light, runs fast colors and optional flashes, supports in-place
retuning, and restores the exact pre-party state when stopped.
It also runs a presence monitor (ip monitor neigh) watching Steven's phone on
the LAN. Arrivals/departures fire remind_when items — either a Telegram message
or a full agent wake (kind: think) — and POST to the agent's presence channel,
which records a home_status fact and logs the transition to activity.
The worker also fronts the parking camera: a Wyze cam reached through
docker-wyze-bridge on the LAN
(unofficial — grab a free API key/id from Wyze's developer portal; see the
worker's deploy/wyze-bridge.compose.yml). The worker pulls a JPEG frame and
runs one cheap vision call (Haiku, direct Anthropic key or AI Gateway key) that
returns {open, total, detail}. check_parking asks once; watch_parking
starts a timed watch on the Pi that re-checks every couple of minutes and pings
Telegram the moment a spot opens (stopping by default). Watches are in-memory —
a Pi restart simply ends one.
Finally, it holds the Discord gateway listener (see below).
The Discord channel on Vercel is HTTP Interactions only — slash commands in,
no passive reading. Autonomous ingestion needs a persistent Gateway WebSocket
with the privileged Message Content intent, which a serverless function can't
hold — so the Pi worker holds it (src/discord/gateway.ts, zero-dep raw
WebSocket with heartbeat/resume). It watches server messages and DMs (skipping
bots, optionally filtered to DISCORD_WATCH_CHANNELS), buffers them, and
forwards a batch every ~60s to POST /eve/v1/discord/ingest guarded by
DISCORD_INGEST_SECRET.
The agent side isn't merely like Gmail's — it is literally the same code, with
the discord policy instead of the email one (see Attention). No
per-message agent turns: one cheap structured call per batch records tasks, saves
facts, schedules Pi reminders or thinks, closes tasks the chatter confirms done,
escalates when it can't act correctly on its own, and pings only when a real
person is specifically trying to reach Steven. His own messages are labeled so
commitments he makes get captured but never ping him. Because Discord's policy
groups by channel, the batch lands in activity as one line per channel rather
than one per message.
The triage call also writes conversation digests — 1-2 sentences per active
channel (topics, participants, outcome) into the conversations store, capped
and TTL'd so heavy servers can't swamp memory. That's what lets Steven ask
"what were people talking about in #gamedev?" later: discord_recap gives a
channel overview or per-channel history, recall searches digests alongside
notes/facts/tasks, and the agent points him to the channel for the full thread.
Batches are deduped by id, so a worker retry never double-processes. Enable the
Message Content Intent in the Discord Developer Portal (Bot → Privileged
Gateway Intents) or the gateway closes with code 4014.
Root runs claude-opus-5 for planning and reasoning. Push heavy planning to
claude-opus-4.8 and cheap, high-volume subtasks to claude-haiku-4.5 via a
subagent. Per-agent model is set in each agent.ts.
- There are no Vercel Cron Jobs. Recurring work lives on the Pi's clock so the agent can edit it; the only external timers are two GitHub Actions workflows (the planning sweep and the Gmail watch renewal).
- Point the worker's
AGENT_WAKE_URLathttps://<app>/eve/v1/wakeand matchAGENT_WAKE_SECRETto the agent'sWAKE_SECRET, or scheduled wakes degrade to plain Telegram messages. - Replace the eve route auth with real auth before any public exposure.
- Run
eve eval --strictin CI so soft-threshold misses also fail the build.