@acquiredintelligence/attend 0.0.0-stage → 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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +580 -2
  3. package/dist/attend.d.ts +30 -0
  4. package/dist/attend.d.ts.map +1 -0
  5. package/dist/attend.js +428 -0
  6. package/dist/attend.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +4 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/core/ids.d.ts +12 -0
  12. package/dist/core/ids.d.ts.map +1 -0
  13. package/dist/core/ids.js +97 -0
  14. package/dist/core/ids.js.map +1 -0
  15. package/dist/core/reducer.d.ts +26 -0
  16. package/dist/core/reducer.d.ts.map +1 -0
  17. package/dist/core/reducer.js +437 -0
  18. package/dist/core/reducer.js.map +1 -0
  19. package/dist/core/seen.d.ts +22 -0
  20. package/dist/core/seen.d.ts.map +1 -0
  21. package/dist/core/seen.js +61 -0
  22. package/dist/core/seen.js.map +1 -0
  23. package/dist/core/state.d.ts +12 -0
  24. package/dist/core/state.d.ts.map +1 -0
  25. package/dist/core/state.js +90 -0
  26. package/dist/core/state.js.map +1 -0
  27. package/dist/core/step.d.ts +14 -0
  28. package/dist/core/step.d.ts.map +1 -0
  29. package/dist/core/step.js +17 -0
  30. package/dist/core/step.js.map +1 -0
  31. package/dist/core/threshold.d.ts +40 -0
  32. package/dist/core/threshold.d.ts.map +1 -0
  33. package/dist/core/threshold.js +72 -0
  34. package/dist/core/threshold.js.map +1 -0
  35. package/dist/core/types.d.ts +704 -0
  36. package/dist/core/types.d.ts.map +1 -0
  37. package/dist/core/types.js +24 -0
  38. package/dist/core/types.js.map +1 -0
  39. package/dist/core/window.d.ts +38 -0
  40. package/dist/core/window.d.ts.map +1 -0
  41. package/dist/core/window.js +322 -0
  42. package/dist/core/window.js.map +1 -0
  43. package/dist/decisions.d.ts +53 -0
  44. package/dist/decisions.d.ts.map +1 -0
  45. package/dist/decisions.js +115 -0
  46. package/dist/decisions.js.map +1 -0
  47. package/dist/errors.d.ts +36 -0
  48. package/dist/errors.d.ts.map +1 -0
  49. package/dist/errors.js +53 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/index.d.ts +10 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +4 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/internal/adapter.d.ts +21 -0
  56. package/dist/internal/adapter.d.ts.map +1 -0
  57. package/dist/internal/adapter.js +62 -0
  58. package/dist/internal/adapter.js.map +1 -0
  59. package/dist/internal/assert.d.ts +5 -0
  60. package/dist/internal/assert.d.ts.map +1 -0
  61. package/dist/internal/assert.js +14 -0
  62. package/dist/internal/assert.js.map +1 -0
  63. package/dist/internal/clock.d.ts +8 -0
  64. package/dist/internal/clock.d.ts.map +1 -0
  65. package/dist/internal/clock.js +38 -0
  66. package/dist/internal/clock.js.map +1 -0
  67. package/dist/internal/http.d.ts +42 -0
  68. package/dist/internal/http.d.ts.map +1 -0
  69. package/dist/internal/http.js +122 -0
  70. package/dist/internal/http.js.map +1 -0
  71. package/dist/internal/images.d.ts +18 -0
  72. package/dist/internal/images.d.ts.map +1 -0
  73. package/dist/internal/images.js +75 -0
  74. package/dist/internal/images.js.map +1 -0
  75. package/dist/internal/json.d.ts +3 -0
  76. package/dist/internal/json.d.ts.map +1 -0
  77. package/dist/internal/json.js +5 -0
  78. package/dist/internal/json.js.map +1 -0
  79. package/dist/internal/prompt.d.ts +31 -0
  80. package/dist/internal/prompt.d.ts.map +1 -0
  81. package/dist/internal/prompt.js +149 -0
  82. package/dist/internal/prompt.js.map +1 -0
  83. package/dist/internal/shared.d.ts +12 -0
  84. package/dist/internal/shared.d.ts.map +1 -0
  85. package/dist/internal/shared.js +134 -0
  86. package/dist/internal/shared.js.map +1 -0
  87. package/dist/jev.d.ts +74 -0
  88. package/dist/jev.d.ts.map +1 -0
  89. package/dist/jev.js +156 -0
  90. package/dist/jev.js.map +1 -0
  91. package/dist/llm.d.ts +164 -0
  92. package/dist/llm.d.ts.map +1 -0
  93. package/dist/llm.js +79 -0
  94. package/dist/llm.js.map +1 -0
  95. package/dist/mcp.d.ts +130 -0
  96. package/dist/mcp.d.ts.map +1 -0
  97. package/dist/mcp.js +550 -0
  98. package/dist/mcp.js.map +1 -0
  99. package/dist/memory.d.ts +10 -0
  100. package/dist/memory.d.ts.map +1 -0
  101. package/dist/memory.js +50 -0
  102. package/dist/memory.js.map +1 -0
  103. package/dist/postgres.d.ts +25 -0
  104. package/dist/postgres.d.ts.map +1 -0
  105. package/dist/postgres.js +56 -0
  106. package/dist/postgres.js.map +1 -0
  107. package/dist/redis.d.ts +20 -0
  108. package/dist/redis.d.ts.map +1 -0
  109. package/dist/redis.js +83 -0
  110. package/dist/redis.js.map +1 -0
  111. package/dist/server/compat.d.ts +13 -0
  112. package/dist/server/compat.d.ts.map +1 -0
  113. package/dist/server/compat.js +5 -0
  114. package/dist/server/compat.js.map +1 -0
  115. package/dist/server/cursor.d.ts +15 -0
  116. package/dist/server/cursor.d.ts.map +1 -0
  117. package/dist/server/cursor.js +50 -0
  118. package/dist/server/cursor.js.map +1 -0
  119. package/dist/server/events.d.ts +42 -0
  120. package/dist/server/events.d.ts.map +1 -0
  121. package/dist/server/events.js +189 -0
  122. package/dist/server/events.js.map +1 -0
  123. package/dist/server/fanout.d.ts +28 -0
  124. package/dist/server/fanout.d.ts.map +1 -0
  125. package/dist/server/fanout.js +184 -0
  126. package/dist/server/fanout.js.map +1 -0
  127. package/dist/server/guard.d.ts +19 -0
  128. package/dist/server/guard.d.ts.map +1 -0
  129. package/dist/server/guard.js +27 -0
  130. package/dist/server/guard.js.map +1 -0
  131. package/dist/server/http.d.ts +4 -0
  132. package/dist/server/http.d.ts.map +1 -0
  133. package/dist/server/http.js +323 -0
  134. package/dist/server/http.js.map +1 -0
  135. package/dist/server/ingest.d.ts +4 -0
  136. package/dist/server/ingest.d.ts.map +1 -0
  137. package/dist/server/ingest.js +400 -0
  138. package/dist/server/ingest.js.map +1 -0
  139. package/dist/server/install.d.ts +5 -0
  140. package/dist/server/install.d.ts.map +1 -0
  141. package/dist/server/install.js +91 -0
  142. package/dist/server/install.js.map +1 -0
  143. package/dist/server/poll.d.ts +4 -0
  144. package/dist/server/poll.d.ts.map +1 -0
  145. package/dist/server/poll.js +45 -0
  146. package/dist/server/poll.js.map +1 -0
  147. package/dist/server/serve.d.ts +19 -0
  148. package/dist/server/serve.d.ts.map +1 -0
  149. package/dist/server/serve.js +238 -0
  150. package/dist/server/serve.js.map +1 -0
  151. package/dist/server/sessions.d.ts +14 -0
  152. package/dist/server/sessions.d.ts.map +1 -0
  153. package/dist/server/sessions.js +54 -0
  154. package/dist/server/sessions.js.map +1 -0
  155. package/dist/server/stream.d.ts +11 -0
  156. package/dist/server/stream.d.ts.map +1 -0
  157. package/dist/server/stream.js +216 -0
  158. package/dist/server/stream.js.map +1 -0
  159. package/dist/server/tools.d.ts +22 -0
  160. package/dist/server/tools.d.ts.map +1 -0
  161. package/dist/server/tools.js +55 -0
  162. package/dist/server/tools.js.map +1 -0
  163. package/dist/server/types.d.ts +351 -0
  164. package/dist/server/types.d.ts.map +1 -0
  165. package/dist/server/types.js +5 -0
  166. package/dist/server/types.js.map +1 -0
  167. package/dist/server.d.ts +11 -0
  168. package/dist/server.d.ts.map +1 -0
  169. package/dist/server.js +10 -0
  170. package/dist/server.js.map +1 -0
  171. package/dist/shell/budget.d.ts +4 -0
  172. package/dist/shell/budget.d.ts.map +1 -0
  173. package/dist/shell/budget.js +5 -0
  174. package/dist/shell/budget.js.map +1 -0
  175. package/dist/shell/copy.d.ts +17 -0
  176. package/dist/shell/copy.d.ts.map +1 -0
  177. package/dist/shell/copy.js +67 -0
  178. package/dist/shell/copy.js.map +1 -0
  179. package/dist/shell/decisions.d.ts +6 -0
  180. package/dist/shell/decisions.d.ts.map +1 -0
  181. package/dist/shell/decisions.js +42 -0
  182. package/dist/shell/decisions.js.map +1 -0
  183. package/dist/shell/read.d.ts +7 -0
  184. package/dist/shell/read.d.ts.map +1 -0
  185. package/dist/shell/read.js +21 -0
  186. package/dist/shell/read.js.map +1 -0
  187. package/dist/shell/scheduler.d.ts +4 -0
  188. package/dist/shell/scheduler.d.ts.map +1 -0
  189. package/dist/shell/scheduler.js +90 -0
  190. package/dist/shell/scheduler.js.map +1 -0
  191. package/dist/shell/scope.d.ts +27 -0
  192. package/dist/shell/scope.d.ts.map +1 -0
  193. package/dist/shell/scope.js +343 -0
  194. package/dist/shell/scope.js.map +1 -0
  195. package/dist/shell/types.d.ts +156 -0
  196. package/dist/shell/types.d.ts.map +1 -0
  197. package/dist/shell/types.js +2 -0
  198. package/dist/shell/types.js.map +1 -0
  199. package/dist/sqlite.d.ts +14 -0
  200. package/dist/sqlite.d.ts.map +1 -0
  201. package/dist/sqlite.js +39 -0
  202. package/dist/sqlite.js.map +1 -0
  203. package/dist/step.d.ts +18 -0
  204. package/dist/step.d.ts.map +1 -0
  205. package/dist/step.js +14 -0
  206. package/dist/step.js.map +1 -0
  207. package/dist/store/memory.d.ts +12 -0
  208. package/dist/store/memory.d.ts.map +1 -0
  209. package/dist/store/memory.js +264 -0
  210. package/dist/store/memory.js.map +1 -0
  211. package/dist/store/postgres.d.ts +24 -0
  212. package/dist/store/postgres.d.ts.map +1 -0
  213. package/dist/store/postgres.js +364 -0
  214. package/dist/store/postgres.js.map +1 -0
  215. package/dist/store/redis.d.ts +12 -0
  216. package/dist/store/redis.d.ts.map +1 -0
  217. package/dist/store/redis.js +540 -0
  218. package/dist/store/redis.js.map +1 -0
  219. package/dist/store/sqlite-worker.d.ts +91 -0
  220. package/dist/store/sqlite-worker.d.ts.map +1 -0
  221. package/dist/store/sqlite-worker.js +322 -0
  222. package/dist/store/sqlite-worker.js.map +1 -0
  223. package/dist/store/sqlite.d.ts +34 -0
  224. package/dist/store/sqlite.d.ts.map +1 -0
  225. package/dist/store/sqlite.js +238 -0
  226. package/dist/store/sqlite.js.map +1 -0
  227. package/dist/store/types.d.ts +137 -0
  228. package/dist/store/types.d.ts.map +1 -0
  229. package/dist/store/types.js +2 -0
  230. package/dist/store/types.js.map +1 -0
  231. package/dist/testing/conformance.d.ts +13 -0
  232. package/dist/testing/conformance.d.ts.map +1 -0
  233. package/dist/testing/conformance.js +542 -0
  234. package/dist/testing/conformance.js.map +1 -0
  235. package/dist/testing/wire.d.ts +16 -0
  236. package/dist/testing/wire.d.ts.map +1 -0
  237. package/dist/testing/wire.js +304 -0
  238. package/dist/testing/wire.js.map +1 -0
  239. package/dist/testing.d.ts +113 -0
  240. package/dist/testing.d.ts.map +1 -0
  241. package/dist/testing.js +655 -0
  242. package/dist/testing.js.map +1 -0
  243. package/dist/types.d.ts +5 -0
  244. package/dist/types.d.ts.map +1 -0
  245. package/dist/types.js +5 -0
  246. package/dist/types.js.map +1 -0
  247. package/package.json +130 -4
  248. package/skills/attend/SKILL.md +231 -0
  249. package/skills/attend/references/judges.md +283 -0
  250. package/skills/attend/references/kinds.md +62 -0
  251. package/skills/attend/references/mcp.md +132 -0
  252. package/skills/attend/references/sinks.md +73 -0
  253. package/skills/attend/references/stores.md +90 -0
  254. package/skills/attend/references/testing.md +120 -0
@@ -0,0 +1,704 @@
1
+ /**
2
+ * The domain, and the owned state the in-place reducer mutates. Everything but `ScopeState` and
3
+ * its parts is plain JSON: a `ScopeRecord` is a scope's state as a snapshot.
4
+ */
5
+ export type JsonPrimitive = string | number | boolean | null;
6
+ export type JsonValue = JsonPrimitive | JsonValue[] | {
7
+ [key: string]: JsonValue;
8
+ };
9
+ export type JsonObject = {
10
+ [key: string]: JsonValue;
11
+ };
12
+ /** One event as a source hands it in. `id` is unique within the scope and is the dedup key. */
13
+ export interface Event {
14
+ id: string;
15
+ /** ms since the epoch, as the source reports it. Informational: timers use `Entry.seenAt`. */
16
+ at: number;
17
+ name: string;
18
+ data: JsonObject;
19
+ /** One plain line describing the event. The judge never writes text; notices are built from these. */
20
+ text?: string;
21
+ }
22
+ export declare const KINDS: readonly ['closing', 'flush', 'continuous', 'passive', 'consequence', 'context'];
23
+ /**
24
+ * The settle role of an event. `closing`, `continuous` and `passive` are acts: things that
25
+ * happen in the scope and can call for the agent, whether the user did them or an outside
26
+ * party did, such as a deploy that failed. A flush is not an act but a direct address. The
27
+ * last two never settle a window on their own.
28
+ * - closing: ends an act; the window settles after a short grace so consequences land.
29
+ * - flush: direct address; settles at once, with wait not offered, and is never shed.
30
+ * - continuous: one step of an ongoing act; settles once no further step has arrived for a gap.
31
+ * - passive: a dwell or look; settles after a quiet period.
32
+ * - consequence: output that follows from an act; attaches to the act that caused it.
33
+ * - context: the agent's own earlier output; rides along.
34
+ */
35
+ export type Kind = (typeof KINDS)[number];
36
+ /** An event in a window: the event, the kind it was given at ingest, and the local clock then. */
37
+ export type Entry = Event & {
38
+ kind: Kind;
39
+ seenAt: number;
40
+ };
41
+ export declare const ROUTES: readonly ['interrupt', 'queue', 'note', 'ask', 'skip', 'wait'];
42
+ /**
43
+ * - interrupt: deliver now, even mid-turn.
44
+ * - queue: deliver when the agent's current turn ends.
45
+ * - note: context for the next turn; no response.
46
+ * - ask: offer the user a prompt to call the agent in.
47
+ * - skip: nothing.
48
+ * - wait: hold the window and judge it again; never leaves the reducer.
49
+ */
50
+ export type Route = (typeof ROUTES)[number];
51
+ /** What a judgment resolves to once `wait` is spent. */
52
+ export type DecidedRoute = Exclude<Route, 'wait'>;
53
+ /** What reaches a sink. A sink never sees `skip`. */
54
+ export type DeliveredRoute = Exclude<DecidedRoute, 'skip'>;
55
+ export type SettleReason = 'closing' | 'flush' | 'stream_quiet' | 'passive_quiet' | 'overflow' | 'age' | 'forced' | 'size';
56
+ /** Round starting points, not tuned values. Every number is in ms unless it is a count. */
57
+ export interface SettlePolicy {
58
+ closingGraceMs: number;
59
+ streamGapMs: number;
60
+ passiveQuietMs: number;
61
+ waitHorizonMs: number;
62
+ maxWaits: number;
63
+ /** Entries one window holds. Consequences and context leave first; a window of only acts settles as `overflow`. */
64
+ maxEvents: number;
65
+ /** A window settles this long after its first act however busy it is; older act-less entries are pruned. */
66
+ maxWindowMs: number;
67
+ /** Backoff before each retry of a judge call, a delivery or a source; its length is the retry budget for the first two. */
68
+ retryBackoffMs: number[];
69
+ /** Aborts a judge call. Its lease is twice this, so a slow call is never duplicated while live. */
70
+ judgeTimeoutMs: number;
71
+ /** Lease on a delivery. Expiry counts as one failed attempt. */
72
+ deliveryTimeoutMs: number;
73
+ /** Verdicts kept as judge context. */
74
+ recentCount: number;
75
+ /** Event ids kept for dedup. */
76
+ seenCount: number;
77
+ /** Undelivered decisions per scope before `push` and a source's `emit` reject. */
78
+ maxOutbox: number;
79
+ /** Undelivered dead letters per scope before `push` and a source's `emit` reject. */
80
+ maxDeadLetters: number;
81
+ /** Cap on the backoff between dead-letter deliveries, which never give up. */
82
+ deadLetterBackoffMaxMs: number;
83
+ /**
84
+ * Estimated rendered bytes at which the open window settles with reason `size`. Absent means
85
+ * off, and the reducer then behaves exactly as before. The shell derives it from the judge's
86
+ * budget unless the app sets it.
87
+ */
88
+ maxWindowBytes?: number;
89
+ }
90
+ export declare const DEFAULT_POLICY: SettlePolicy;
91
+ /** The open window. Always present; empty entries mean no window is open. */
92
+ export interface Buffer {
93
+ entries: Entry[];
94
+ /** Non-act entries shed by overflow or age. Acts are never shed. */
95
+ dropped: number;
96
+ /** The source reported lost events while this window was open, or before it opened. */
97
+ gap: boolean;
98
+ /**
99
+ * Set by `flush`; the window settles at the next tick whatever its deadline says. Set while a
100
+ * judge call is in flight, it forbids that call's verdict to hold the window.
101
+ */
102
+ forcedAt: number | null;
103
+ /** Set by `end()`. Absent when unset, so a record that never saw `end()` is unchanged. */
104
+ endedAt?: number;
105
+ }
106
+ /** A settled window: what the judge sees and what a decision carries. */
107
+ export interface Window {
108
+ /** Hash of the scope and the event ids; it becomes the decision id. */
109
+ id: string;
110
+ /** Oldest first, kinds included. */
111
+ events: Entry[];
112
+ /**
113
+ * Why the judge sees it: `flush` when the window holds a direct address, `forced` when
114
+ * `flush()` forced it, else the deadline that fired. The first two never offer `wait` and
115
+ * survive a merge with later events.
116
+ */
117
+ reason: SettleReason;
118
+ waits: number;
119
+ dropped: number;
120
+ gap: boolean;
121
+ settledAt: number;
122
+ }
123
+ /**
124
+ * `attempt` numbers the judge calls for this window and only grows, across a wait and across a
125
+ * merge, so a result from an earlier call never matches a later one. `failures` counts the
126
+ * failed calls since the last wait and indexes the backoff; a wait resets it.
127
+ */
128
+ export type BatchPhase = {
129
+ phase: 'judging';
130
+ attempt: number;
131
+ failures: number;
132
+ leaseUntil: number;
133
+ offerWait: boolean;
134
+ } | {
135
+ phase: 'waiting';
136
+ attempt: number;
137
+ until: number;
138
+ } | {
139
+ phase: 'retrying';
140
+ attempt: number;
141
+ failures: number;
142
+ at: number;
143
+ offerWait: boolean;
144
+ };
145
+ /** The one settled window. One slot: that is single-flight. */
146
+ export interface Batch {
147
+ window: Window;
148
+ phase: BatchPhase;
149
+ }
150
+ /** One routed verdict, kept as judge context. */
151
+ export interface Verdict {
152
+ id: string;
153
+ route: DecidedRoute;
154
+ at: number;
155
+ }
156
+ /**
157
+ * What a sink receives. `id` is the window id, so the same window redelivered has the
158
+ * same id (the sink's idempotency key) and a merged, re-judged window has a different one.
159
+ */
160
+ export interface Decision {
161
+ id: string;
162
+ scope: string;
163
+ route: DeliveredRoute;
164
+ /** Normalised over every route; routes the judge was not offered are 0. */
165
+ probabilities: Record<Route, number>;
166
+ /** Answers to the ride-along questions, by name. */
167
+ predicates: Record<string, number>;
168
+ /** How sure the judge was of the route, from 0 to 1, when it says; null when it does not. */
169
+ confidence: number | null;
170
+ window: Window;
171
+ at: number;
172
+ }
173
+ /** A window no judge or sink could take. Delivered to the dead-letter handler and never dropped. */
174
+ export interface DeadLetter {
175
+ id: string;
176
+ scope: string;
177
+ /** Which call gave up. */
178
+ stage: 'judge' | 'sink';
179
+ window: Window;
180
+ /** The decision the sink could not take; null when the judge gave up. */
181
+ decision: Decision | null;
182
+ message: string;
183
+ permanent: boolean;
184
+ attempts: number;
185
+ at: number;
186
+ }
187
+ /**
188
+ * `attempt` numbers the delivery attempts for this id and only grows, across the escalation to
189
+ * a dead letter too. `failures` counts the failed attempts in the current lane and indexes the
190
+ * backoff, so a fresh dead letter starts at the first rung. A retry is its own phase, never a
191
+ * lease, so a backoff is never counted as a failed attempt.
192
+ */
193
+ export type DeliveryPhase = {
194
+ phase: 'inFlight';
195
+ attempt: number;
196
+ failures: number;
197
+ leaseUntil: number;
198
+ } | {
199
+ phase: 'retrying';
200
+ attempt: number;
201
+ failures: number;
202
+ at: number;
203
+ };
204
+ export type DeliveryItem = {
205
+ id: string;
206
+ } & ({
207
+ kind: 'decision';
208
+ decision: Decision;
209
+ } | {
210
+ kind: 'deadLetter';
211
+ letter: DeadLetter;
212
+ });
213
+ /** An outbox entry. */
214
+ export interface Delivery {
215
+ item: DeliveryItem;
216
+ phase: DeliveryPhase;
217
+ }
218
+ export type OutboxLane = 'decisions' | 'deadLetters';
219
+ /**
220
+ * Two lanes, each delivered one at a time, oldest first, so a dead-letter handler that is
221
+ * down never holds a decision back. A decision that gives up leaves `decisions` and joins the
222
+ * end of `deadLetters`.
223
+ */
224
+ export type Outbox = Record<OutboxLane, Delivery[]>;
225
+ /** A scope's state as one JSON document: the snapshot format, and what `inspect` returns. Every field has one writer: the reducer. */
226
+ export interface ScopeRecord {
227
+ /** The count of committed inputs: the compare-and-swap token. An input that changed nothing is never committed. */
228
+ version: number;
229
+ scope: string;
230
+ /** Each attached source's position after its last ingested event, by `Source.key`; null when that source has none. */
231
+ cursors: Record<string, string | null>;
232
+ /** Recent event ids, oldest first, bounded by `seenCount`. */
233
+ seen: string[];
234
+ buffer: Buffer;
235
+ batch: Batch | null;
236
+ outbox: Outbox;
237
+ /** Oldest first, bounded by `recentCount`. */
238
+ recent: Verdict[];
239
+ /** The next instant the record wants a tick; null when nothing is pending. */
240
+ dueAt: number | null;
241
+ }
242
+ /** What a judge returns. Validated against the offered routes inside `step`. */
243
+ export interface Judgment {
244
+ route: Route;
245
+ /** Per offered route. Missing routes count as 0; the reducer renormalises. */
246
+ probabilities?: Partial<Record<Route, number>>;
247
+ predicates?: Record<string, number>;
248
+ /** From 0 to 1, when the judge reports one. Anything else is dropped. */
249
+ confidence?: number;
250
+ }
251
+ export interface EventsInput {
252
+ kind: 'events';
253
+ events: (Event & {
254
+ kind: Kind;
255
+ })[];
256
+ /** The `Source.key` these came from. A push has none, and then `cursor` is ignored. */
257
+ source?: string;
258
+ /** Absent: leave the source's cursor as it is. Present (string or null): replace it. */
259
+ cursor?: string | null;
260
+ /** The source reported a gap before these events. */
261
+ truncated?: boolean;
262
+ }
263
+ /** Settle the open window at the next tick, reason `forced`. */
264
+ export interface FlushInput {
265
+ kind: 'flush';
266
+ }
267
+ /**
268
+ * The runner stopped before starting the call this effect asked for. The lease is released
269
+ * and the call is due again at once with no failure counted, so the next runner takes it up
270
+ * on its first tick. The attempt number goes unspent and the next call takes it: a call that
271
+ * never happened produced no result that could collide with it. Stale against any other
272
+ * attempt, so a replay changes nothing.
273
+ */
274
+ export type ReleasedInput = {
275
+ kind: 'judgeReleased';
276
+ batchId: string;
277
+ attempt: number;
278
+ } | {
279
+ kind: 'deliveryReleased';
280
+ deliveryId: string;
281
+ attempt: number;
282
+ };
283
+ /**
284
+ * Punctuation from a producer that knows the unit is over. The open window settles at the next
285
+ * tick with reason `closing`, with no grace and with `wait` still offered. Stale when there is
286
+ * nothing to settle, like `flush`. Yields traces only.
287
+ */
288
+ export interface EndInput {
289
+ kind: 'end';
290
+ }
291
+ /**
292
+ * The shell could not start the call because of its `judgeConcurrency` cap. Like `judgeReleased`,
293
+ * except that the batch retries at `until` rather than now. The attempt goes unspent and no
294
+ * failure is counted. Stale against any other attempt.
295
+ */
296
+ export interface JudgeDeferredInput {
297
+ kind: 'judgeDeferred';
298
+ batchId: string;
299
+ attempt: number;
300
+ until: number;
301
+ }
302
+ export type Input = EventsInput | FlushInput | EndInput | ReleasedInput | JudgeDeferredInput | {
303
+ kind: 'tick';
304
+ } | {
305
+ kind: 'judged';
306
+ batchId: string;
307
+ attempt: number;
308
+ judgment: Judgment;
309
+ } | {
310
+ kind: 'judgeFailed';
311
+ batchId: string;
312
+ attempt: number;
313
+ message: string;
314
+ permanent: boolean;
315
+ } | {
316
+ kind: 'delivered';
317
+ deliveryId: string;
318
+ attempt: number;
319
+ } | {
320
+ kind: 'deliveryFailed';
321
+ deliveryId: string;
322
+ attempt: number;
323
+ message: string;
324
+ permanent: boolean;
325
+ };
326
+ export interface JudgeEffect {
327
+ kind: 'judge';
328
+ scope: string;
329
+ batchId: string;
330
+ attempt: number;
331
+ window: Window;
332
+ /** The routes the judge may answer with; `wait` present iff offered. */
333
+ offered: Route[];
334
+ recent: Verdict[];
335
+ leaseUntil: number;
336
+ }
337
+ export interface InFlightDelivery {
338
+ item: DeliveryItem;
339
+ phase: Extract<DeliveryPhase, {
340
+ phase: 'inFlight';
341
+ }>;
342
+ }
343
+ export interface DeliverEffect {
344
+ kind: 'deliver';
345
+ scope: string;
346
+ delivery: InFlightDelivery;
347
+ }
348
+ export type Trace = {
349
+ kind: 'ingested';
350
+ scope: string;
351
+ count: number;
352
+ duplicates: number;
353
+ } | {
354
+ kind: 'gap';
355
+ scope: string;
356
+ source: string | null;
357
+ cursor: string | null;
358
+ } | {
359
+ kind: 'dropped';
360
+ scope: string;
361
+ entries: Entry[];
362
+ why: 'overflow' | 'age';
363
+ } | {
364
+ kind: 'settled';
365
+ scope: string;
366
+ batchId: string;
367
+ reason: SettleReason;
368
+ count: number;
369
+ merged: boolean;
370
+ } | {
371
+ kind: 'judging';
372
+ scope: string;
373
+ batchId: string;
374
+ attempt: number;
375
+ offerWait: boolean;
376
+ } | {
377
+ kind: 'judged';
378
+ scope: string;
379
+ batchId: string;
380
+ route: DecidedRoute;
381
+ } | {
382
+ kind: 'waiting';
383
+ scope: string;
384
+ batchId: string;
385
+ waits: number;
386
+ until: number;
387
+ } | {
388
+ kind: 'retrying';
389
+ scope: string;
390
+ stage: 'judge' | 'sink' | 'deadLetter' | 'source';
391
+ id: string;
392
+ attempt: number;
393
+ at: number;
394
+ message: string;
395
+ } | {
396
+ kind: 'delivering';
397
+ scope: string;
398
+ deliveryId: string;
399
+ attempt: number;
400
+ } | {
401
+ kind: 'delivered';
402
+ scope: string;
403
+ deliveryId: string;
404
+ } | {
405
+ kind: 'released';
406
+ scope: string;
407
+ stage: 'judge' | 'sink' | 'deadLetter';
408
+ id: string;
409
+ attempt: number;
410
+ } | {
411
+ kind: 'deadLettered';
412
+ scope: string;
413
+ letter: DeadLetter;
414
+ } | {
415
+ kind: 'stale';
416
+ scope: string;
417
+ input: Input['kind'];
418
+ why: string;
419
+ } | {
420
+ kind: 'conflict';
421
+ scope: string;
422
+ version: number;
423
+ } | {
424
+ kind: 'abandoned';
425
+ scope: string;
426
+ input: Input['kind'];
427
+ } | {
428
+ kind: 'failed';
429
+ scope: string;
430
+ input: Input['kind'];
431
+ message: string;
432
+ } | {
433
+ kind: 'captureFailed';
434
+ scope: string;
435
+ batchId: string;
436
+ message: string;
437
+ } | {
438
+ kind: 'deferred';
439
+ scope: string;
440
+ batchId: string;
441
+ attempt: number;
442
+ until: number;
443
+ } | {
444
+ kind: 'replayedAcrossUpgrade';
445
+ scope: string;
446
+ from: number;
447
+ to: number;
448
+ } | {
449
+ kind: 'backpressure';
450
+ queued: number;
451
+ lagMs: number;
452
+ } | {
453
+ kind: 'routeThresholded';
454
+ scope: string;
455
+ batchId: string;
456
+ from: Route;
457
+ to: Route;
458
+ act: number;
459
+ };
460
+ export type TraceEffect = {
461
+ kind: 'trace';
462
+ trace: Trace;
463
+ };
464
+ export type Effect = JudgeEffect | DeliverEffect | TraceEffect;
465
+ export interface Stepped<E extends Effect = Effect> {
466
+ record: ScopeRecord;
467
+ effects: E[];
468
+ }
469
+ export interface LogEntry {
470
+ /** Opaque, strictly increasing in read order within a scope. */
471
+ position: string;
472
+ event: Event;
473
+ }
474
+ /**
475
+ * An append-only log per scope for producers outside the process. At-least-once: the
476
+ * reducer dedups by `event.id`, so the log never has to.
477
+ */
478
+ export interface EventLog {
479
+ /** Durable before resolving. Order within one call is preserved. */
480
+ append(scope: string, events: Event[]): Promise<void>;
481
+ /**
482
+ * Entries strictly after `after` (`null`: from the oldest retained entry, never from the
483
+ * head), oldest first, at most `limit`. With `blockMs`, an adapter that can wait returns
484
+ * early when an entry arrives and returns an empty page at the timeout; one that cannot
485
+ * returns at once.
486
+ */
487
+ read(scope: string, after: string | null, options: {
488
+ limit: number;
489
+ blockMs?: number;
490
+ }): Promise<LogEntry[]>;
491
+ }
492
+ export interface SourceBatch {
493
+ events: Event[];
494
+ /** The position after these events; omit when the source has no cursor. */
495
+ cursor?: string | null;
496
+ truncated?: boolean;
497
+ }
498
+ /**
499
+ * Something that feeds a scope. `emit` resolves once the batch is persisted, and the
500
+ * source must await it before producing more: that is the backpressure. Rejecting ends
501
+ * the subscription with that error; `attach` resubscribes after a backoff unless the error
502
+ * is a `PermanentError`.
503
+ */
504
+ export interface Source {
505
+ /** Names the source within a scope. The record keeps one cursor per key, so two sources on one scope never share a position. */
506
+ key: string;
507
+ subscribe(cursor: string | null, emit: (batch: SourceBatch) => Promise<void>, signal: AbortSignal): Promise<void>;
508
+ }
509
+ export interface OfferedRoute {
510
+ route: Route;
511
+ /** The caller's criterion for this route, when configured. Judges supply their own otherwise. */
512
+ criterion?: string;
513
+ }
514
+ /** A still image for the judge, as base64 with no `data:` prefix. Hosted judges have their own format and size limits. */
515
+ export interface JudgeImage {
516
+ data: string;
517
+ mimeType: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif';
518
+ }
519
+ /** What a judge decides from. Plain JSON, so a judge can run in another process or on a server. */
520
+ export interface JudgeInput {
521
+ scope: string;
522
+ window: Window;
523
+ /** `wait` is absent when the window may not wait. */
524
+ routes: OfferedRoute[];
525
+ /** Who the agent is and what matters to its user, when configured. */
526
+ guidance?: string;
527
+ /** Yes/no questions to answer beside the route, each as a probability. */
528
+ predicates: {
529
+ name: string;
530
+ question: string;
531
+ }[];
532
+ /** Live facts from `context()`, such as whether the agent is mid-turn. */
533
+ facts: JsonObject;
534
+ /** What `capture()` returned for this call, when configured and it returned any. */
535
+ images?: JudgeImage[];
536
+ /** This scope's last few verdicts, oldest first. */
537
+ recent: Verdict[];
538
+ now: number;
539
+ }
540
+ /**
541
+ * Decides how the agent hears about a window. Throw to retry; throw a `PermanentError`
542
+ * to dead-letter at once. A route that was not offered counts as a retryable failure.
543
+ * Must honour `signal`. It may declare a `budget`; a plain function still works.
544
+ */
545
+ export type Judge = ((input: JudgeInput, signal: AbortSignal) => Promise<Judgment>) & {
546
+ budget?: JudgeBudget;
547
+ };
548
+ /**
549
+ * Delivers decisions to the agent. Called at least once per decision and must be
550
+ * idempotent on `decision.id`. Throw to retry; throw a `PermanentError` to dead-letter
551
+ * at once. Deliveries in one scope arrive one at a time, oldest first.
552
+ */
553
+ export interface Sink {
554
+ deliver(decision: Decision, signal: AbortSignal): Promise<void>;
555
+ }
556
+ /** Time, injected. `fakeClock()` in ./testing.ts makes settling deterministic. */
557
+ export interface Clock {
558
+ now(): number;
559
+ /** Run `fn` at or after `at`. Returns a cancel. */
560
+ at(at: number, fn: () => void): () => void;
561
+ }
562
+ /** What a judge declares about its input limit. `jevJudge` declares 64k tokens, or 262k with `longContext`. */
563
+ export interface JudgeBudget {
564
+ contextTokens: number;
565
+ /** Tokens the judge spends outside the window: system prompt, criteria, facts. Default: measured from the judge's own prompt. */
566
+ reservedTokens?: number;
567
+ /** Default 4. */
568
+ bytesPerToken?: number;
569
+ }
570
+ /** FNV-1a 64 state on two unsigned 32-bit limbs. `foldId` mutates it in place. Its output is identical to `windowId`'s. */
571
+ export interface FnvState {
572
+ hi: number;
573
+ lo: number;
574
+ }
575
+ /**
576
+ * The seen ring: the last `capacity` ingested ids, oldest at `head`. Capacity grows lazily up to
577
+ * `seenCount`. Eviction happens only in `seenTrim`, after a whole batch.
578
+ */
579
+ export interface IdRing {
580
+ ids: string[];
581
+ head: number;
582
+ size: number;
583
+ capacity: number;
584
+ }
585
+ /**
586
+ * known.get(id) = (seen-ring slots holding id) + (1 if id is in the open window) + (1 if id is in
587
+ * the held window). An id with count 0 is deleted, so an id is known iff it is present.
588
+ */
589
+ export type KnownIds = Map<string, number>;
590
+ /**
591
+ * Everything `deadline()` reads, kept O(1). Acts and flushes are never shed or pruned, so their
592
+ * fields only move forward. NaN means none for the `*At` fields; `minFlushAt` and
593
+ * `prefixMinSheddableAt` use Infinity for none. Equal to what a scan of the live entries derives.
594
+ */
595
+ export interface TriggerSummary {
596
+ minFlushAt: number;
597
+ lastClosingAt: number;
598
+ lastStreamAt: number;
599
+ lastPassiveAt: number;
600
+ firstActAt: number;
601
+ /** Index of the first act relative to `start`, or -1. Adjusted on removal, unchanged by compaction. */
602
+ firstActIndex: number;
603
+ /** Smallest `seenAt` among sheddable entries before the first act: the O(1) prune test. */
604
+ prefixMinSheddableAt: number;
605
+ /** Live consequence and context entries; zero means nothing can be shed. */
606
+ sheddable: number;
607
+ lastSeenAt: number;
608
+ }
609
+ /** The open window, appended in place by its single writer. Live entries are `entries[start..]`. */
610
+ export interface OpenWindow {
611
+ entries: Entry[];
612
+ /** Index in `entries` and `sizes` of the first live slot. Slots before it are removed entries, at most half the live count. */
613
+ start: number;
614
+ /** Estimated rendered bytes per entry; `sizes[start..]` parallels the live entries. Recomputed by `fromRecord`; never stored. */
615
+ sizes: number[];
616
+ /** The sum of the live sizes. Compared with `SettlePolicy.maxWindowBytes`. */
617
+ bytes: number;
618
+ dropped: number;
619
+ gap: boolean;
620
+ forcedAt: number | null;
621
+ endedAt: number | null;
622
+ summary: TriggerSummary;
623
+ /** FNV state over `len:scope` then each live entry's `len:id`, when `hashValid`. */
624
+ hash: FnvState;
625
+ /** False after a prune or shed inside the window; the settle then refolds the final list. */
626
+ hashValid: boolean;
627
+ /** False once an appended `seenAt` was smaller than the last one; prune then scans the whole prefix. */
628
+ monotone: boolean;
629
+ }
630
+ /** The one settled window and its phase. `hash` continues over buffer ids when the window merges. */
631
+ export interface HeldBatch {
632
+ window: Window;
633
+ phase: BatchPhase;
634
+ hash: FnvState | null;
635
+ }
636
+ /**
637
+ * A scope's state, owned by exactly one writer and mutated in place by `apply`. Containers that
638
+ * effects, snapshots or decision rows may hold (cursors, recent, Window, Delivery) are replaced,
639
+ * never mutated.
640
+ */
641
+ export interface ScopeState {
642
+ scope: string;
643
+ /** The count of committed inputs: the compare-and-swap token. */
644
+ version: number;
645
+ cursors: Readonly<Record<string, string | null>>;
646
+ seen: IdRing;
647
+ known: KnownIds;
648
+ open: OpenWindow;
649
+ batch: HeldBatch | null;
650
+ /** Mutated in place: one in flight per lane, oldest first. `toRecord` copies them. */
651
+ outbox: {
652
+ decisions: Delivery[];
653
+ deadLetters: Delivery[];
654
+ };
655
+ recent: readonly Verdict[];
656
+ dueAt: number | null;
657
+ }
658
+ /** What `apply` needs from the config. Nothing async and no app code: kinds arrive in the logged events. */
659
+ export interface ApplyConfig {
660
+ policy: SettlePolicy;
661
+ /** `lane`: a Sink is configured and the decisions lane works as today. `log`: the decision row is the delivery. */
662
+ decisions: 'lane' | 'log';
663
+ /** `lane`: a dead-letter handler is configured. `log`: the dead-letter row is final. */
664
+ deadLetters: 'lane' | 'log';
665
+ /** Build trace effects. False when nothing observes. Traces never change state. */
666
+ traces: boolean;
667
+ }
668
+ /** A row `apply` asks the store to append in the same write as the input that produced it. */
669
+ export type DecisionAppend = {
670
+ kind: 'decision';
671
+ id: string;
672
+ scope: string;
673
+ at: number;
674
+ route: DeliveredRoute;
675
+ body: Decision;
676
+ } | {
677
+ kind: 'deadLetter';
678
+ id: string;
679
+ scope: string;
680
+ at: number;
681
+ route: DeliveredRoute | null;
682
+ body: DeadLetter;
683
+ };
684
+ /** Filled by `apply`. `changed` is false when the input changed nothing; the shell then neither logs nor writes it. */
685
+ export interface ApplyOutput {
686
+ effects: Effect[];
687
+ appends: DecisionAppend[];
688
+ changed: boolean;
689
+ }
690
+ /**
691
+ * What the store keeps per committed input, and all that replay needs: the input, with its
692
+ * events already carrying their kinds, and the clock it was applied at. Immutable once logged.
693
+ */
694
+ export interface LoggedInput {
695
+ input: Input;
696
+ now: number;
697
+ }
698
+ /** What the pure `step` wrapper needs: the policy, plus the delivery modes (both default to `lane`). */
699
+ export interface StepConfig {
700
+ policy: SettlePolicy;
701
+ decisions?: 'lane' | 'log';
702
+ deadLetters?: 'lane' | 'log';
703
+ }
704
+ //# sourceMappingURL=types.d.ts.map