@elizaos/app-lifeops
v2.0.0-beta.1
Published
LifeOps app plugin: routines, goals, Google Workspace, Apple Reminders, Twilio, browser companion control, hosts-file blocking (self-control), and related agent surfaces.
Readme
@elizaos/app-lifeops
LifeOps is the elizaOS app that runs the user's day: routines, goals,
calendar, email, messaging, follow-ups with people, blockers, watchers, and
the operational glue around them. This README is the architecture summary
for contributors. User-facing docs live in docs/user/lifeops-setup.mdx;
the QA reference lives in docs/launchdocs/14-lifeops-qa.md; the REST
contract lives in docs/rest/lifeops.md.
The single primitive
Every reminder, check-in, follow-up, watcher, recap, approval surface, and
nag-the-user-when-they-go-quiet flow is a ScheduledTask owned by the
runner at src/lifeops/scheduled-task/runner.ts. There is no second
mechanism. If LifeOps does it, it is a ScheduledTask.
The shape:
interface ScheduledTask {
taskId: string;
kind: "reminder" | "checkin" | "followup" | "approval" | "recap" | "watcher" | "output" | "custom";
promptInstructions: string;
contextRequest?: { /* owner facts, entities, relationships, recent task states, event payload */ };
trigger: /* once | cron | interval | relative_to_anchor | during_window | event | manual | after_task */;
priority: "low" | "medium" | "high";
shouldFire?: { compose: "all" | "any" | "first_deny"; gates: Array<{ kind: string; params? }> };
completionCheck?: { kind: string; params?: ...; followupAfterMinutes? };
escalation?: { ladderKey?: string; steps?: EscalationStep[] };
output?: { destination: ...; target?: string; persistAs?: ... };
pipeline?: { onComplete?, onSkip?, onFail? };
subject?: { kind: "entity" | "relationship" | "thread" | "document" | "calendar_event" | "self"; id: string };
idempotencyKey?: string;
respectsGlobalPause: boolean;
state: ScheduledTaskState;
source: "default_pack" | "user_chat" | "first_run" | "plugin";
createdBy: string;
ownerVisible: boolean;
metadata?: Record<string, unknown>;
}The runner pattern-matches only on the structural fields above
(kind, trigger, shouldFire, completionCheck, pipeline, output,
subject, priority, respectsGlobalPause). It never inspects
promptInstructions content. This is non-negotiable.
The frozen contract lives in docs/audit/wave1-interfaces.md §1.
Runtime layout
src/lifeops/
scheduled-task/ Spine: runner, state log, gate registry,
completion-check registry, escalation, runtime
wiring.
entities/ Entity primitive: store, merge engine, types.
relationships/ Relationship edges: store, observation
extraction, types.
registries/ AnchorRegistry, EventKindRegistry, FamilyRegistry,
BlockerRegistry, app/website blocker contributions.
signals/ ActivitySignalBus.
channels/ ChannelRegistry, priority-posture map, default
channel pack.
connectors/ ConnectorRegistry + per-connector contributions
(calendly, discord, duffel, google, imessage,
signal, telegram, twilio, whatsapp, x).
send-policy/ Per-connector send-policy contract + registry.
owner/ OwnerFactStore.
first-run/ FirstRunService, state store, customize
questions, replay.
pending-prompts/ PendingPromptsStore (the planner-visible
"questions waiting for the user" surface).
global-pause/ GlobalPauseStore.
handoff/ HandoffStore (per-room handoff state).
i18n/ MultilingualPromptRegistry.
graph-migration/ Migration into the entity/relationship graph.
seed-routine-migration/ Migration off legacy seed routines.
...other domain helpers (calendar, email, messaging, payments,
subscriptions, sleep, screen-time, etc.)Default packs
Default packs are bundles of ScheduledTask records (and sometimes
anchor-consolidation policies, escalation ladders, autofill whitelists).
LifeOps-owned packs live in src/default-packs/:
daily-rhythm— gm, gn, daily check-in.morning-brief— fired onwake.confirmed.quiet-user-watcher— daily watcher.habit-starters— eight habits, offered (not auto-seeded).inbox-triage-starter— opt-in, gated on Gmail.followup-starter— watcher firing per overdue relationship.autofill-whitelist-pack,consolidation-policies,escalation-ladders— policy-only packs.
@elizaos/plugin-health ships bedtime, wake-up, sleep-recap and
registers them when a health connector pairs.
Adding a new default pack
- Add a file under
src/default-packs/<name>.tsthat exports aDefaultPackmatchingregistry-types.ts. - Import and append it to
DEFAULT_PACKSinsrc/default-packs/index.ts. - If the pack should be auto-enabled, list it in
getDefaultEnabledPacks. If it should be offered during first-run customize, list it ingetOfferedDefaultPacks. If neither, the pack only seeds when invoked explicitly. - Run
bun run lint:default-packs(also runs aspretest). The lint rules live indocs/audit/prompt-content-lint.md. CI rejects packs that violate them. - Add a record-id constant export so consumers can target the records by stable ID.
The runtime never seeds packs by name string-match; everything goes through
getAllDefaultPacks().
Knowledge graph
EntityStore (nodes) and RelationshipStore (edges) at
src/lifeops/entities/ and src/lifeops/relationships/. The graph is
per-agent. The entityId === "self" row is bootstrapped on first use.
- Cadence lives on the edge. "Pat — every 14 days" is a
Relationship, not anEntityattribute. Cadence-bearingScheduledTasks usesubject.kind = "relationship". - Identities are observed.
(platform, handle)pairs route throughobserveIdentity; the merge engine inentities/merge.tscollapses entities with high-confidence identity matches. Manual merges go throughPOST /api/lifeops/entities/mergeand are audited. - REST surface — see
docs/rest/lifeops.md.
Pause and handoff
- Global pause (
global-pause/store.ts) — stops everyScheduledTaskwithrespectsGlobalPause: true. Toggleable via UI or/api/lifeops/app-state. - Per-room handoff (
handoff/store.ts) — flips a multi-party room into handoff after the agent says "I'll let you take it from here." Typed resume conditions (mention | explicit_resume | silence_minutes | user_request_help). TheRoomPolicyProviderreadsHandoffStore.status(roomId).activeand gates further agent contributions.
Plugin dependencies
LifeOps consumes @elizaos/plugin-health for sleep/circadian/health metrics
and screen-time. The plugin contributes through the registries listed above
(AnchorRegistry, ConnectorRegistry, FamilyRegistry, default packs).
LifeOps does not import directly into the health internals; it consumes the
plugin's public exports only. See plugins/plugin-health/README.md.
Cross-agent invariants
- The runner never pattern-matches
promptInstructions. subject.kind = "relationship"for cadence-bearing tasks.- Identities are observed, not assigned.
- Connectors and channels return typed
DispatchResult. Noboolean. shouldFire.gatesis always an array.acknowledged≠completed. PipelineonCompleteonly fires oncompleted.- Snooze resets the escalation ladder.
- Global pause skips tasks with
respectsGlobalPause: true.
Where to look next
- Frozen interface contracts:
docs/audit/wave1-interfaces.md. - Implementation plan:
docs/audit/IMPLEMENTATION_PLAN.md. - Post-cleanup architecture summary:
docs/audit/post-cleanup-architecture.md. - Coverage matrix:
coverage-matrix.md. - Prompt-content lint rules:
docs/audit/prompt-content-lint.md. - Health domain:
plugins/plugin-health/README.md. - REST:
docs/rest/lifeops.md. - QA reference:
docs/launchdocs/14-lifeops-qa.md. - User-facing setup:
docs/user/lifeops-setup.mdx.
