@agent-assistant/proactive 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +271 -0
  2. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # `@agent-assistant/proactive`
2
+
3
+ **Status:** IMPLEMENTATION_READY
4
+ **Version:** 0.1.0 (pre-1.0, provisional)
5
+ **Spec:** `docs/specs/v1-proactive-spec.md`
6
+ **Implementation plan:** `docs/architecture/v1-proactive-implementation-plan.md`
7
+
8
+ ---
9
+
10
+ ## What This Package Does
11
+
12
+ `@agent-assistant/proactive` is the decision layer for proactive assistant behavior — any assistant action that originates without a direct user message in the current turn.
13
+
14
+ It provides:
15
+
16
+ - **ProactiveEngine** — evaluates follow-up rules and watch rules against session context, applies suppression policy, and returns structured decisions
17
+ - **Follow-up rule evaluation** — rules fire, suppress, or are silenced by cooldown/max-count/user-activity checks
18
+ - **Reminder policy** — configurable `maxReminders`, `cooldownMs`, and `suppressWhenActive` per rule
19
+ - **Watch rules** — long-running monitoring rules that re-schedule themselves after every evaluation; lifecycle methods for pause, resume, and cancel
20
+ - **SchedulerBinding interface** — thin contract to external scheduling infrastructure (relaycron); this package does not own or wrap relaycron
21
+ - **InMemorySchedulerBinding** — test adapter with a manual trigger helper; no external infrastructure required for tests
22
+ - **FollowUpEvidenceSource interface** — optional pluggable evidence injection (e.g., memory entries) for rule conditions
23
+
24
+ This package does **not** own scheduling infrastructure, session lifecycle, message delivery, or domain-specific rule logic. All of that stays in product code or other packages.
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```sh
31
+ npm install @agent-assistant/proactive
32
+ ```
33
+
34
+ No `@agent-assistant/*` runtime dependencies. Only `nanoid` is required at runtime.
35
+
36
+ ---
37
+
38
+ ## Quick Start
39
+
40
+ ```ts
41
+ import {
42
+ createProactiveEngine,
43
+ InMemorySchedulerBinding,
44
+ } from '@agent-assistant/proactive';
45
+ import type { FollowUpRule } from '@agent-assistant/proactive';
46
+
47
+ // Wire a scheduler binding (InMemorySchedulerBinding for tests/dev)
48
+ const schedulerBinding = new InMemorySchedulerBinding();
49
+
50
+ const engine = createProactiveEngine({
51
+ schedulerBinding,
52
+ defaultReminderPolicy: {
53
+ maxReminders: 3,
54
+ cooldownMs: 3_600_000, // 1 hour
55
+ suppressWhenActive: true,
56
+ },
57
+ });
58
+
59
+ // Register a follow-up rule (stale-thread pattern)
60
+ const staleThreadRule: FollowUpRule = {
61
+ id: 'stale-thread',
62
+ description: 'Follow up if the session has been silent for more than 24 hours',
63
+ condition: (ctx) => {
64
+ const inactiveMs =
65
+ new Date(ctx.scheduledAt).getTime() - new Date(ctx.lastActivityAt).getTime();
66
+ return inactiveMs > 24 * 60 * 60 * 1000;
67
+ },
68
+ policy: {
69
+ maxReminders: 2,
70
+ cooldownMs: 24 * 60 * 60 * 1000,
71
+ },
72
+ routingHint: 'cheap',
73
+ messageTemplate: 'Checking in — any updates on this thread?',
74
+ };
75
+
76
+ engine.registerFollowUpRule(staleThreadRule);
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Usage in a Capability Handler
82
+
83
+ The proactive package does not register a capability handler. Products write their own:
84
+
85
+ ```ts
86
+ // In your assistant definition (product code)
87
+ const definition = {
88
+ capabilities: {
89
+ proactive: async (message, context) => {
90
+ const wakeUpContext = message.payload; // parsed from the synthetic wake-up message
91
+
92
+ // Fetch session state from your session store
93
+ const session = await context.sessionStore.get(wakeUpContext.sessionId);
94
+
95
+ // Evaluate follow-up rules
96
+ const decisions = await engine.evaluateFollowUp({
97
+ sessionId: wakeUpContext.sessionId,
98
+ scheduledAt: wakeUpContext.scheduledAt,
99
+ lastActivityAt: session.lastActivityAt,
100
+ });
101
+
102
+ for (const decision of decisions) {
103
+ if (decision.action === 'fire') {
104
+ await context.runtime.emit({
105
+ sessionId: decision.sessionId,
106
+ text: decision.messageTemplate ?? 'Following up.',
107
+ });
108
+ }
109
+ }
110
+ },
111
+ },
112
+ };
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Follow-Up Rules
118
+
119
+ Rules are product-supplied. The engine handles watch-rule re-scheduling via `SchedulerBinding` and reminder state. Follow-up scheduling is product-owned.
120
+
121
+ ```ts
122
+ interface FollowUpRule {
123
+ id: string;
124
+ condition(ctx: FollowUpEvaluationContext, evidence: EvidenceEntry[]): boolean | Promise<boolean>;
125
+ description?: string;
126
+ policy?: ReminderPolicy; // overrides engine default when present
127
+ routingHint?: 'cheap' | 'fast' | 'deep'; // defaults to 'cheap'
128
+ messageTemplate?: string;
129
+ }
130
+ ```
131
+
132
+ **Suppression order:**
133
+ 1. User became active after the wake-up was scheduled (`lastActivityAt > scheduledAt`)
134
+ 2. Max reminders reached for this `(sessionId, ruleId)` pair
135
+ 3. Cooldown window not elapsed since the last reminder
136
+ 4. Condition function returned false
137
+
138
+ The first matching suppression wins. If none match, the decision is `fire`.
139
+
140
+ ---
141
+
142
+ ## Watch Rules
143
+
144
+ Watch rules are long-running monitors that re-schedule themselves after every evaluation — whether or not the condition triggered.
145
+
146
+ ```ts
147
+ engine.registerWatchRule({
148
+ id: 'unreviewed-pr',
149
+ description: 'Alert if a PR has been open without review for over 2 hours',
150
+ condition: async (ctx) => {
151
+ // product fetches PR state from its own store
152
+ return myStore.hasUnreviewedPRsOlderThan(2 * 60 * 60 * 1000);
153
+ },
154
+ action: { type: 'notify_channel', payload: { channel: 'eng-alerts' } },
155
+ intervalMs: 30 * 60 * 1000, // check every 30 minutes
156
+ });
157
+
158
+ // Lifecycle
159
+ engine.pauseWatchRule('unreviewed-pr');
160
+ engine.resumeWatchRule('unreviewed-pr');
161
+ engine.cancelWatchRule('unreviewed-pr'); // permanent; re-register to restart
162
+
163
+ // List statuses
164
+ const statuses = engine.listWatchRules();
165
+ // [{ rule, status: 'active' | 'paused' | 'cancelled', lastEvaluatedAt, nextWakeUpBindingId }]
166
+
167
+ // Evaluate (called from your proactive capability handler after each wake-up)
168
+ const triggers = await engine.evaluateWatchRules({
169
+ ruleId: wakeUpContext.ruleId,
170
+ scheduledAt: wakeUpContext.scheduledAt,
171
+ metadata: wakeUpContext.metadata,
172
+ });
173
+ for (const trigger of triggers) {
174
+ await handleWatchAction(trigger.action, trigger.context);
175
+ }
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Scheduler Binding
181
+
182
+ The `SchedulerBinding` interface decouples the engine from scheduling infrastructure.
183
+
184
+ ```ts
185
+ interface SchedulerBinding {
186
+ requestWakeUp(at: Date, context: WakeUpContext): Promise<string>; // returns bindingId
187
+ cancelWakeUp(bindingId: string): Promise<void>;
188
+ }
189
+ ```
190
+
191
+ **For tests and local development:** use the built-in `InMemorySchedulerBinding`:
192
+
193
+ ```ts
194
+ const binding = new InMemorySchedulerBinding();
195
+
196
+ // Inspect pending wake-ups
197
+ console.log(binding.pendingWakeUps); // Map<bindingId, { at, context }>
198
+
199
+ // Manually fire a wake-up in tests
200
+ const context = await binding.trigger(bindingId);
201
+ ```
202
+
203
+ **For production:** implement `SchedulerBinding` against your relaycron client. The proactive package does not implement or ship a relaycron integration — that is a product/foundation concern.
204
+
205
+ ---
206
+
207
+ ## Evidence Sources
208
+
209
+ Optional. Wire an evidence source to give rule conditions access to recent memory entries or other context:
210
+
211
+ ```ts
212
+ const evidenceSource = {
213
+ getRecentEntries: (sessionId, opts) =>
214
+ memoryStore.retrieve({ scope: { kind: 'session', sessionId }, limit: opts?.limit }),
215
+ };
216
+
217
+ const engine = createProactiveEngine({ schedulerBinding, evidenceSource });
218
+ ```
219
+
220
+ If configured, the engine calls `getRecentEntries` once per `evaluateFollowUp` call (shared across all rules in that call). If `context.evidence` is pre-fetched and provided, the source is not called at all.
221
+
222
+ If not configured, condition functions receive an empty evidence array.
223
+
224
+ ---
225
+
226
+ ## Reminder State Management
227
+
228
+ Reminder state is in-memory and keyed by `(sessionId, ruleId)`.
229
+
230
+ ```ts
231
+ // Clear state for a specific rule in a session (e.g., when user resolves the thread)
232
+ engine.resetReminderState('session-abc', 'stale-thread');
233
+
234
+ // Clear all reminder state for a session (e.g., on session close)
235
+ engine.resetReminderState('session-abc');
236
+ ```
237
+
238
+ ---
239
+
240
+ ## What Stays Outside This Package
241
+
242
+ | Concern | Where it lives |
243
+ |---|---|
244
+ | Scheduling infrastructure (timers, cron, dispatch) | Relay foundation (relaycron) |
245
+ | Domain-specific rule definitions | Product repos |
246
+ | Product-specific timing thresholds | Product configuration |
247
+ | Memory persistence | `@agent-assistant/memory` (via `FollowUpEvidenceSource`) |
248
+ | Session lifecycle | `@agent-assistant/sessions` |
249
+ | Outbound message delivery | `@agent-assistant/surfaces` + Relay runtime |
250
+ | Cross-agent coordination of proactive actions | `@agent-assistant/coordination` (v1.2) |
251
+ | Proactive action rate limiting / budgets | `@agent-assistant/policy` (v2) |
252
+
253
+ ---
254
+
255
+ ## Package Structure
256
+
257
+ ```
258
+ packages/proactive/
259
+ package.json — nanoid runtime dep only
260
+ tsconfig.json
261
+ src/
262
+ types.ts — all exported types, interfaces, error classes
263
+ proactive.ts — createProactiveEngine factory and all engine logic
264
+ index.ts — public re-exports
265
+ proactive.test.ts — 45 tests
266
+ README.md
267
+ ```
268
+
269
+ ---
270
+
271
+ PROACTIVE_PACKAGE_DIRECTION_READY
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@agent-assistant/proactive",
3
+ "version": "0.1.0",
4
+ "description": "Proactive decision engine for Agent Assistant SDK",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "typecheck": "tsc --noEmit -p tsconfig.json"
22
+ },
23
+ "dependencies": {
24
+ "nanoid": "^5.0.7"
25
+ },
26
+ "devDependencies": {
27
+ "typescript": "^5.9.3",
28
+ "vitest": "^3.2.4"
29
+ },
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/AgentWorkforce/agent-assistant"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
38
+ }