You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Event-driven intentions — subscription primitive + on_fire action list on intentions. Item 5 in the revised pre-beta sequence (per pre-beta-plan-synthesis-2026-04-17). Spec is implementation-ready; design is stable.
Current intention flow fires on deliver_at timestamps only. Real triggers are often event-based: "first external beta user onboards," "claude-dev starts in mcp-clipboard repo," "sleep score drops below 70 three nights running." Using timestamps as proxies for events produces false-positive fires, silent misses, and placeholder-date decay.
Core insight: awareness needs an interrupt mechanism. Events are signals. Subscriptions are pre-registered handlers. The controller matches signals to subscribers and fires handlers when predicates match.
Event sources — edge providers and internal state changes write entries. They don't know who's listening.
Interrupt controller — new component. Receives "entry written" signals, evaluates active subscriptions, fires matching handlers. Priority, masking, dispatch.
Handlers — intentions with event triggers. Fire means state transition pending → fired, with the matching entry linked as cause, and execution of the intention's on_fire action list.
Phase 1 builds a narrow slice of all three.
Intention on_fire action list
Every intention gets an on_fire field: an array of actions to execute on fire. Default [{action: "surface_in_briefing"}] (preserves current behavior).
Phase 1 supported actions
surface_in_briefing — current behavior; intention appears in next briefing
execute_directive — invoke a directive in execute mode with parameter bindings
Deferred to Phase 2+ (needs REST + consent channel)
webhook — POST to configured URL using the outbound payload schema
email — send via configured SMTP
sms — send via configured SMS provider (paid tier)
All outbound actions will require creation-time consent (category external-call from the action-gating model) and user-configured endpoint allowlists.
execute_directive action semantics
When an intention's on_fire includes execute_directive:
Target — directive entry (referenced by logical_key or entry_id)
Fire-time context — awareness automatically merges the triggering entry_id and event metadata into the parameter binding before schema validation
Creation-time authority — when the intention is created, the user pre-authorizes the intention-to-directive binding with the specific parameters and action categories (see action-gating sibling issue)
Phase 1 constraint: "queue for next AI session" model
Awareness does NOT autonomously invoke an AI to execute the directive. The directive is queued as a pending execution that surfaces in the next briefing in execute-mode. The human-connected AI session (claude-dev arriving in a repo, Chris opening claude.ai, etc.) picks it up and runs it.
This keeps the "silence is the product" model intact. Nothing runs without an AI session attached. Autonomous execution (awareness invokes an AI endpoint on its own) is Phase 2+ with careful consent architecture.
Concrete success case: claude-dev starts in mcp-clipboard. An event fires an intention whose on_fire is execute_directive(load-wip-context, repo={repo_name}). Directive queues. Claude-dev reads the briefing, sees the pending directive in execute-mode, runs it, context loaded. No autonomous AI invocation; just faster-than-manual context handoff.
Authority intersection
Directive runs with the intersection of (directive's standing authority) and (intention's pre-authorized invocation authority captured at creation). If the directive gains new authority later, existing intentions don't automatically benefit. Bind-at-creation.
Loop prevention
Directives invoked by intention fire cannot themselves cause another intention fire (causality depth ≤ 1)
Intention-triggered writes do NOT fire new subscriptions
Directives cannot create new intentions (Phase 1 coarse rule)
Revocation
If target directive is deleted or scan_status becomes flagged:
Intention auto-disabled
Loud surfacing in next briefing: "intention X disabled because directive Y is no longer runnable"
Predicate language: AND-only composition of source match, tag match (any-of), entry_type match. No OR, no NOT, no nesting, no time/spatial/fuzzy predicates.
Synchronous evaluation in-process, in the write path. Every remember / add_context / report_status / update_entry / remind call evaluates active subscriptions against the new entry after commit.
Action types in Phase 1: activate_intention(intention_id) only. Fires the linked intention (pending → fired), then executes the intention's on_fire actions (surface_in_briefing and/or execute_directive).
Single-owner scope. Subscriptions evaluate only against entries owned by the same user.
Edge-triggered only. Fires once when predicate matches a new entry. No level-triggered accumulators.
All three fields optional. Field presence means "must match." Tag match is array-overlap. Present fields ANDed. Absent fields unconstrained.
Forward-compatibility: predicate shape must be expressible as a strict subset of the Awareness DSL (dsl-social-knowledge-design). Migration from JSON predicates to DSL expressions must be mechanical, not a rewrite.
source → DSL SOURCE "X"
tags_any → DSL tag("X") OR tag("Y")
entry_type → DSL type(note) or equivalent
Breaking changes are cheap before launch, expensive after. Verify mapping before finalizing.
Firing semantics
User writes an entry via any tool.
After successful commit in the transaction, subscription evaluator runs synchronously in the same request.
Evaluator queries subscriptions WHERE owner_id = :owner AND enabled = true.
For each matching subscription, execute activate_intention(intention_id):
Update intention state pending → fired
Record triggering entry_id in intention data
Execute on_fire actions:
surface_in_briefing — no-op now; intention will appear in next briefing
execute_directive — merge fire-time context into parameter binding, validate against directive's parameter schema, queue the directive execution request for the next AI session's briefing
Write action log via existing acted_on mechanism (subscription → triggering entry → fired intention → invoked directive when applicable).
Return original write response.
Failure handling: subscription evaluation errors are logged; original write must still succeed.
Integration with consent/gating
Intentions with on_fire actions in gated categories require consent at creation time:
execute_directive → requires directive's own authorization to be valid and pre-authorized for this binding
Fire-time: if pre-authorized binding exists, execute. If not, action is deferred to the next user session with a pending consent request (in-chat Ask in Phase 1; out-of-band channels once the sibling issue ships).
Kill switch and safety
User can disable all on_fire actions globally (or per-category) without affecting intention state otherwise. The backstop: if things go wrong, the user stops all automation immediately without needing to unwind subscriptions one by one.
Rate limits per intention and per destination (when webhook/email/sms ship) prevent runaway behavior even on pre-authorized bindings.
Schema sketch
CREATETABLEsubscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id UUID NOT NULLREFERENCES users(id) ON DELETE CASCADE,
name TEXT,
predicate JSONB NOT NULL,
action_type TEXTNOT NULLCHECK (action_type IN ('activate_intention')),
action_payload JSONB NOT NULL,
enabled BOOLEANNOT NULL DEFAULT true,
created_at TIMESTAMPTZNOT NULL DEFAULT now(),
updated_at TIMESTAMPTZNOT NULL DEFAULT now()
);
CREATEINDEXidx_subscriptions_owner_enabledON subscriptions(owner_id) WHERE enabled = true;
CREATEINDEXidx_subscriptions_predicate_tagsON subscriptions USING GIN ((predicate ->'tags_any'));
ALTERTABLE subscriptions ENABLE ROW LEVEL SECURITY;
CREATE POLICY subscriptions_owner_isolation ON subscriptions
USING (owner_id = current_setting('app.current_user_id')::uuid);
Intention schema extended with:
on_fire JSONB array of action objects (default [{"action": "surface_in_briefing"}])
Pre-authorized bindings stored as part of on_fire payload for execute_directive actions
Chris creates intention "Load claude-dev context for mcp-clipboard" with subscription predicate matching a "claude-dev started" event and on_fire: execute_directive(load-wip-context, {repo: from_fire_context}). When the event fires, the directive queues and claude-dev picks it up on next briefing read.
Non-subscription intentions still fire on timestamps as before. No regression.
Cross-owner isolation maintained. Subscriptions on Chris's user never fire from other users' writes.
Failed directive invocation (revoked, scan-flagged) auto-disables the intention and surfaces the failure.
Implementation to-do (Claude Code flesh-out)
Module organization within existing codebase
Tool definitions for create_subscription, list_subscriptions, update_subscription, delete_subscription
Integration with all write paths (remember, add_context, report_status, update_entry, remind)
Intention state transition with on_fire execution
Directive invocation queue mechanism (how execute_directive requests surface in briefing)
Alembic migration for subscriptions table and intention on_fire field
Test scaffolding following existing patterns
Documentation updates
Naming decision: "subscription" vs "watcher" vs "trigger" vs "hook"
Out-of-scope reminders (do NOT creep)
OR composition
Time / spatial / fuzzy predicates
Webhooks, email, SMS on_fire actions (deferred to post-REST, post-consent-channel)
Async subscription evaluation
Cross-owner or shared-token firing
Autonomous AI invocation for directive execution (Phase 1 queues for next AI session)
Summary
Event-driven intentions — subscription primitive +
on_fireaction list on intentions. Item 5 in the revised pre-beta sequence (perpre-beta-plan-synthesis-2026-04-17). Spec is implementation-ready; design is stable.Current intention flow fires on
deliver_attimestamps only. Real triggers are often event-based: "first external beta user onboards," "claude-dev starts in mcp-clipboard repo," "sleep score drops below 70 three nights running." Using timestamps as proxies for events produces false-positive fires, silent misses, and placeholder-date decay.Core insight: awareness needs an interrupt mechanism. Events are signals. Subscriptions are pre-registered handlers. The controller matches signals to subscribers and fires handlers when predicates match.
Architecture (three layers, "CPU interrupts" analogy)
on_fireaction list.Phase 1 builds a narrow slice of all three.
Intention
on_fireaction listEvery intention gets an
on_firefield: an array of actions to execute on fire. Default[{action: "surface_in_briefing"}](preserves current behavior).Phase 1 supported actions
surface_in_briefing— current behavior; intention appears in next briefingexecute_directive— invoke a directive in execute mode with parameter bindingsDeferred to Phase 2+ (needs REST + consent channel)
webhook— POST to configured URL using the outbound payload schemaemail— send via configured SMTPsms— send via configured SMS provider (paid tier)All outbound actions will require creation-time consent (category
external-callfrom the action-gating model) and user-configured endpoint allowlists.execute_directiveaction semanticsWhen an intention's
on_fireincludesexecute_directive:logical_keyorentry_id)parameters_schema(see feat(security): prompt-injection Phase 1 — directive entry type, scan_status, execute-mode retrieval #306)entry_idand event metadata into the parameter binding before schema validationPhase 1 constraint: "queue for next AI session" model
Awareness does NOT autonomously invoke an AI to execute the directive. The directive is queued as a pending execution that surfaces in the next briefing in execute-mode. The human-connected AI session (claude-dev arriving in a repo, Chris opening claude.ai, etc.) picks it up and runs it.
This keeps the "silence is the product" model intact. Nothing runs without an AI session attached. Autonomous execution (awareness invokes an AI endpoint on its own) is Phase 2+ with careful consent architecture.
Concrete success case: claude-dev starts in mcp-clipboard. An event fires an intention whose
on_fireisexecute_directive(load-wip-context, repo={repo_name}). Directive queues. Claude-dev reads the briefing, sees the pending directive in execute-mode, runs it, context loaded. No autonomous AI invocation; just faster-than-manual context handoff.Authority intersection
Directive runs with the intersection of (directive's standing authority) and (intention's pre-authorized invocation authority captured at creation). If the directive gains new authority later, existing intentions don't automatically benefit. Bind-at-creation.
Loop prevention
Revocation
If target directive is deleted or
scan_statusbecomesflagged:Phase 1 subscription model
In scope
subscriptions (id, owner_id, predicate jsonb, action_type, action_payload jsonb, enabled, created_at, updated_at)sourcematch, tag match (any-of),entry_typematch. No OR, no NOT, no nesting, no time/spatial/fuzzy predicates.remember/add_context/report_status/update_entry/remindcall evaluates active subscriptions against the new entry after commit.activate_intention(intention_id)only. Fires the linked intention (pending → fired), then executes the intention'son_fireactions (surface_in_briefingand/orexecute_directive).create_subscription,list_subscriptions,update_subscription,delete_subscription.Out of scope (deferred)
activate_intention)Predicate format (forward-compatible with DSL)
{ "source": "mcp-awareness-project", "tags_any": ["beta-user", "onboarding"], "entry_type": "note" }All three fields optional. Field presence means "must match." Tag match is array-overlap. Present fields ANDed. Absent fields unconstrained.
Forward-compatibility: predicate shape must be expressible as a strict subset of the Awareness DSL (
dsl-social-knowledge-design). Migration from JSON predicates to DSL expressions must be mechanical, not a rewrite.source→ DSLSOURCE "X"tags_any→ DSLtag("X") OR tag("Y")entry_type→ DSLtype(note)or equivalentBreaking changes are cheap before launch, expensive after. Verify mapping before finalizing.
Firing semantics
subscriptions WHERE owner_id = :owner AND enabled = true.activate_intention(intention_id):entry_idin intention dataon_fireactions:surface_in_briefing— no-op now; intention will appear in next briefingexecute_directive— merge fire-time context into parameter binding, validate against directive's parameter schema, queue the directive execution request for the next AI session's briefingacted_onmechanism (subscription → triggering entry → fired intention → invoked directive when applicable).Failure handling: subscription evaluation errors are logged; original write must still succeed.
Integration with consent/gating
Intentions with
on_fireactions in gated categories require consent at creation time:execute_directive→ requires directive's own authorization to be valid and pre-authorized for this bindingwebhook(deferred) →external-callcategory, requires user consent + allowlisted endpointemail/sms(deferred) →external-call, requires consentFire-time: if pre-authorized binding exists, execute. If not, action is deferred to the next user session with a pending consent request (in-chat Ask in Phase 1; out-of-band channels once the sibling issue ships).
Kill switch and safety
User can disable all
on_fireactions globally (or per-category) without affecting intention state otherwise. The backstop: if things go wrong, the user stops all automation immediately without needing to unwind subscriptions one by one.Rate limits per intention and per destination (when webhook/email/sms ship) prevent runaway behavior even on pre-authorized bindings.
Schema sketch
Intention schema extended with:
on_fireJSONB array of action objects (default[{"action": "surface_in_briefing"}])on_firepayload forexecute_directiveactionsRLS enforces owner isolation. Application layer enforces predicate evaluation and directive authority intersection.
Success criteria
on_fire: execute_directive(load-wip-context, {repo: from_fire_context}). When the event fires, the directive queues and claude-dev picks it up on next briefing read.Implementation to-do (Claude Code flesh-out)
create_subscription,list_subscriptions,update_subscription,delete_subscriptionremember,add_context,report_status,update_entry,remind)on_fireexecutionexecute_directiverequests surface in briefing)subscriptionstable and intentionon_firefieldOut-of-scope reminders (do NOT creep)
on_fireactions (deferred to post-REST, post-consent-channel)Sequencing
on_fireactions are deferred)References
event-driven-intentions-phase1-spec(revised 2026-04-17, marked implementation-ready)dsl-social-knowledge-design(DSL forward-compat requirement),defense-in-depth-injection-model(scan_status check before directive execution)