@hasna/loops 0.4.14 → 0.4.23

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 (48) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +119 -29
  3. package/dist/api/index.d.ts +1 -0
  4. package/dist/api/index.js +825 -7
  5. package/dist/cli/index.js +5423 -2619
  6. package/dist/daemon/index.js +478 -84
  7. package/dist/generated/storage-kit/index.d.ts +1 -1
  8. package/dist/index.js +2521 -226
  9. package/dist/lib/cloud/mode.d.ts +17 -0
  10. package/dist/lib/cloud/resolve.d.ts +16 -0
  11. package/dist/lib/cloud/storage.d.ts +82 -0
  12. package/dist/lib/cloud/transport.d.ts +148 -0
  13. package/dist/lib/format.d.ts +2 -1
  14. package/dist/lib/migration.d.ts +40 -0
  15. package/dist/lib/mode.js +3 -3
  16. package/dist/lib/route/index.d.ts +2 -0
  17. package/dist/lib/route/policies.d.ts +51 -0
  18. package/dist/lib/route/provider-admission.d.ts +37 -0
  19. package/dist/lib/route/throttle.d.ts +4 -0
  20. package/dist/lib/route/types.d.ts +8 -0
  21. package/dist/lib/run-receipts.d.ts +14 -0
  22. package/dist/lib/storage/contract.d.ts +7 -1
  23. package/dist/lib/storage/index.js +710 -31
  24. package/dist/lib/storage/postgres-loop-storage.d.ts +6 -0
  25. package/dist/lib/storage/postgres-schema.js +29 -0
  26. package/dist/lib/storage/postgres.js +29 -0
  27. package/dist/lib/storage/sqlite.d.ts +6 -0
  28. package/dist/lib/storage/sqlite.js +412 -18
  29. package/dist/lib/store/index.d.ts +268 -0
  30. package/dist/lib/store.d.ts +48 -1
  31. package/dist/lib/store.js +396 -18
  32. package/dist/lib/template-kit.d.ts +25 -0
  33. package/dist/lib/templates.d.ts +16 -1
  34. package/dist/mcp/http.d.ts +16 -0
  35. package/dist/mcp/index.js +1658 -168
  36. package/dist/runner/index.js +76 -9
  37. package/dist/sdk/http.d.ts +67 -0
  38. package/dist/sdk/http.js +21 -0
  39. package/dist/sdk/index.d.ts +50 -17
  40. package/dist/sdk/index.js +1509 -132
  41. package/dist/serve/index.js +1598 -122
  42. package/dist/types.d.ts +49 -0
  43. package/docs/AUTOMATION_RUNTIME_DESIGN.md +442 -1
  44. package/docs/CUTOVER-RUNBOOK.md +73 -56
  45. package/docs/DEPLOYMENT_MODES.md +35 -18
  46. package/docs/RUNTIME_BOUNDARY.md +203 -0
  47. package/docs/USAGE.md +145 -27
  48. package/package.json +3 -3
@@ -0,0 +1,268 @@
1
+ import type { CreateLoopInput, CreateWorkflowInput, Loop, LoopRun, LoopStatus, RunReceipt, RunStatus, WorkflowEvent, WorkflowInvocation, WorkflowRun, WorkflowSpec, WorkflowStepRun, WorkflowWorkItem, WorkflowWorkItemStatus, WriteRunReceiptInput } from "../../types.js";
2
+ import type { Goal, GoalPlanNode, GoalRun, GoalStatus } from "../goal/types.js";
3
+ import type { HasnaStorageClient } from "../cloud/storage.js";
4
+ import type { Env } from "../cloud/mode.js";
5
+ import { Store } from "../store.js";
6
+ import type { PruneHistoryOptions, PruneHistorySummary } from "../store.js";
7
+ /** Which transport backs a resolved store (for banners/diagnostics only). */
8
+ export type StoreTransport = "local" | "cloud-http";
9
+ /**
10
+ * Thrown when a command routed through the cloud ApiStore hits an operation that
11
+ * the hosted `/v1` API does not yet expose. Failing loudly here is deliberate:
12
+ * the alternative (silently reading/writing the on-box sqlite island while the
13
+ * machine is flipped to cloud) is exactly the split-brain bug we are removing.
14
+ */
15
+ export declare class CloudUnsupportedError extends Error {
16
+ constructor(operation: string);
17
+ }
18
+ /**
19
+ * The single data interface for the OpenLoops client. Both {@link LocalStore}
20
+ * and {@link ApiStore} implement it; callers hold a `LoopStore` and never know
21
+ * (or branch on) which one. Every method is async so the two transports share
22
+ * one surface.
23
+ */
24
+ export interface LoopStore {
25
+ readonly transport: StoreTransport;
26
+ close(): Promise<void>;
27
+ createLoop(input: CreateLoopInput, from?: Date): Promise<Loop>;
28
+ getLoop(id: string): Promise<Loop | undefined>;
29
+ findLoopByName(name: string): Promise<Loop | undefined>;
30
+ requireLoop(idOrName: string): Promise<Loop>;
31
+ requireUniqueLoop(idOrName: string): Promise<Loop>;
32
+ listLoops(opts?: {
33
+ status?: LoopStatus;
34
+ limit?: number;
35
+ offset?: number;
36
+ archived?: boolean;
37
+ includeArchived?: boolean;
38
+ name?: string;
39
+ }): Promise<Loop[]>;
40
+ countLoops(status?: LoopStatus, opts?: {
41
+ archived?: boolean;
42
+ includeArchived?: boolean;
43
+ }): Promise<number>;
44
+ updateLoop(id: string, patch: Partial<Pick<Loop, "status" | "nextRunAt" | "retryScheduledFor" | "expiresAt">>): Promise<Loop>;
45
+ renameLoop(id: string, name: string): Promise<Loop>;
46
+ archiveLoop(idOrName: string): Promise<Loop>;
47
+ unarchiveLoop(idOrName: string): Promise<Loop>;
48
+ deleteLoop(idOrName: string): Promise<boolean>;
49
+ createWorkflow(input: CreateWorkflowInput): Promise<WorkflowSpec>;
50
+ getWorkflow(id: string): Promise<WorkflowSpec | undefined>;
51
+ findWorkflowByName(name: string): Promise<WorkflowSpec | undefined>;
52
+ requireWorkflow(idOrName: string): Promise<WorkflowSpec>;
53
+ listWorkflows(opts?: {
54
+ status?: WorkflowSpec["status"];
55
+ limit?: number;
56
+ offset?: number;
57
+ }): Promise<WorkflowSpec[]>;
58
+ countWorkflows(opts?: {
59
+ status?: WorkflowSpec["status"];
60
+ }): Promise<number>;
61
+ archiveWorkflow(idOrName: string): Promise<WorkflowSpec>;
62
+ getWorkflowRun(id: string): Promise<WorkflowRun | undefined>;
63
+ requireWorkflowRun(id: string): Promise<WorkflowRun>;
64
+ listWorkflowRuns(opts?: {
65
+ workflowId?: string;
66
+ loopRunId?: string;
67
+ limit?: number;
68
+ }): Promise<WorkflowRun[]>;
69
+ listWorkflowStepRuns(workflowRunId: string): Promise<WorkflowStepRun[]>;
70
+ listWorkflowEvents(workflowRunId: string, limit?: number): Promise<WorkflowEvent[]>;
71
+ recoverWorkflowRun(workflowRunId: string, reason?: string): Promise<{
72
+ run: WorkflowRun;
73
+ recoveredSteps: WorkflowStepRun[];
74
+ }>;
75
+ cancelWorkflowRun(workflowRunId: string, reason?: string): Promise<WorkflowRun>;
76
+ listWorkflowWorkItems(opts?: {
77
+ status?: WorkflowWorkItemStatus;
78
+ routeKey?: string;
79
+ limit?: number;
80
+ }): Promise<WorkflowWorkItem[]>;
81
+ getWorkflowWorkItem(id: string): Promise<WorkflowWorkItem | undefined>;
82
+ requeueWorkflowWorkItem(id: string, patch?: {
83
+ reason?: string;
84
+ }): Promise<WorkflowWorkItem>;
85
+ listWorkflowInvocations(opts?: {
86
+ limit?: number;
87
+ }): Promise<WorkflowInvocation[]>;
88
+ getWorkflowInvocation(id: string): Promise<WorkflowInvocation | undefined>;
89
+ getGoal(id: string): Promise<Goal | undefined>;
90
+ findGoalByLoop(idOrName: string): Promise<Goal | undefined>;
91
+ findGoalByRunId(id: string): Promise<Goal | undefined>;
92
+ listGoals(opts?: {
93
+ status?: GoalStatus;
94
+ limit?: number;
95
+ }): Promise<Goal[]>;
96
+ listGoalPlanNodes(goalIdOrPlanId: string): Promise<GoalPlanNode[]>;
97
+ listGoalRuns(opts?: {
98
+ goalId?: string;
99
+ runId?: string;
100
+ limit?: number;
101
+ }): Promise<GoalRun[]>;
102
+ listRuns(opts?: {
103
+ loopId?: string;
104
+ status?: RunStatus;
105
+ limit?: number;
106
+ offset?: number;
107
+ }): Promise<LoopRun[]>;
108
+ getRun(id: string): Promise<LoopRun | undefined>;
109
+ writeRunReceipt(input: WriteRunReceiptInput): Promise<RunReceipt>;
110
+ getRunReceipt(runId: string): Promise<RunReceipt | undefined>;
111
+ listRunReceipts(opts?: {
112
+ loopId?: string;
113
+ repo?: string;
114
+ taskId?: string;
115
+ knowledgeId?: string;
116
+ status?: string;
117
+ limit?: number;
118
+ }): Promise<RunReceipt[]>;
119
+ pruneHistory(opts: PruneHistoryOptions): Promise<PruneHistorySummary>;
120
+ }
121
+ /**
122
+ * On-box SQLite transport. Wraps a single {@link Store} and awaits its
123
+ * synchronous methods so callers see the async {@link LoopStore} surface.
124
+ */
125
+ export declare class LocalStore implements LoopStore {
126
+ readonly transport: "local";
127
+ private readonly store;
128
+ constructor(store?: Store);
129
+ /**
130
+ * The underlying on-box sqlite {@link Store}. Exposed ONLY for genuinely
131
+ * local-runtime operations that cannot route over HTTP — the scheduler
132
+ * (tick/run-now inline execution), migration import/export, and local
133
+ * diagnostics (doctor/health/daemon status). Callers must gate on
134
+ * `transport === "local"` first; the hosted ApiStore has no `raw` store, so
135
+ * these operations fail loudly in cloud mode instead of touching an island.
136
+ */
137
+ get raw(): Store;
138
+ close(): Promise<void>;
139
+ createLoop(input: CreateLoopInput, from?: Date): Promise<Loop>;
140
+ getLoop(id: string): Promise<Loop | undefined>;
141
+ findLoopByName(name: string): Promise<Loop | undefined>;
142
+ requireLoop(idOrName: string): Promise<Loop>;
143
+ requireUniqueLoop(idOrName: string): Promise<Loop>;
144
+ listLoops(opts?: Parameters<Store["listLoops"]>[0]): Promise<Loop[]>;
145
+ countLoops(status?: LoopStatus, opts?: {
146
+ archived?: boolean;
147
+ includeArchived?: boolean;
148
+ }): Promise<number>;
149
+ updateLoop(id: string, patch: Partial<Pick<Loop, "status" | "nextRunAt" | "retryScheduledFor" | "expiresAt">>): Promise<Loop>;
150
+ renameLoop(id: string, name: string): Promise<Loop>;
151
+ archiveLoop(idOrName: string): Promise<Loop>;
152
+ unarchiveLoop(idOrName: string): Promise<Loop>;
153
+ deleteLoop(idOrName: string): Promise<boolean>;
154
+ createWorkflow(input: CreateWorkflowInput): Promise<WorkflowSpec>;
155
+ getWorkflow(id: string): Promise<WorkflowSpec | undefined>;
156
+ findWorkflowByName(name: string): Promise<WorkflowSpec | undefined>;
157
+ requireWorkflow(idOrName: string): Promise<WorkflowSpec>;
158
+ listWorkflows(opts?: Parameters<Store["listWorkflows"]>[0]): Promise<WorkflowSpec[]>;
159
+ countWorkflows(opts?: {
160
+ status?: WorkflowSpec["status"];
161
+ }): Promise<number>;
162
+ archiveWorkflow(idOrName: string): Promise<WorkflowSpec>;
163
+ getWorkflowRun(id: string): Promise<WorkflowRun | undefined>;
164
+ requireWorkflowRun(id: string): Promise<WorkflowRun>;
165
+ listWorkflowRuns(opts?: Parameters<Store["listWorkflowRuns"]>[0]): Promise<WorkflowRun[]>;
166
+ listWorkflowStepRuns(workflowRunId: string): Promise<WorkflowStepRun[]>;
167
+ listWorkflowEvents(workflowRunId: string, limit?: number): Promise<WorkflowEvent[]>;
168
+ recoverWorkflowRun(workflowRunId: string, reason?: string): Promise<{
169
+ run: WorkflowRun;
170
+ recoveredSteps: WorkflowStepRun[];
171
+ }>;
172
+ cancelWorkflowRun(workflowRunId: string, reason?: string): Promise<WorkflowRun>;
173
+ listWorkflowWorkItems(opts?: Parameters<Store["listWorkflowWorkItems"]>[0]): Promise<WorkflowWorkItem[]>;
174
+ getWorkflowWorkItem(id: string): Promise<WorkflowWorkItem | undefined>;
175
+ requeueWorkflowWorkItem(id: string, patch?: {
176
+ reason?: string;
177
+ }): Promise<WorkflowWorkItem>;
178
+ listWorkflowInvocations(opts?: Parameters<Store["listWorkflowInvocations"]>[0]): Promise<WorkflowInvocation[]>;
179
+ getWorkflowInvocation(id: string): Promise<WorkflowInvocation | undefined>;
180
+ getGoal(id: string): Promise<Goal | undefined>;
181
+ findGoalByLoop(idOrName: string): Promise<Goal | undefined>;
182
+ findGoalByRunId(id: string): Promise<Goal | undefined>;
183
+ listGoals(opts?: Parameters<Store["listGoals"]>[0]): Promise<Goal[]>;
184
+ listGoalPlanNodes(goalIdOrPlanId: string): Promise<GoalPlanNode[]>;
185
+ listGoalRuns(opts?: Parameters<Store["listGoalRuns"]>[0]): Promise<GoalRun[]>;
186
+ listRuns(opts?: Parameters<Store["listRuns"]>[0]): Promise<LoopRun[]>;
187
+ getRun(id: string): Promise<LoopRun | undefined>;
188
+ writeRunReceipt(input: WriteRunReceiptInput): Promise<RunReceipt>;
189
+ getRunReceipt(runId: string): Promise<RunReceipt | undefined>;
190
+ listRunReceipts(opts?: Parameters<Store["listRunReceipts"]>[0]): Promise<RunReceipt[]>;
191
+ pruneHistory(opts: PruneHistoryOptions): Promise<PruneHistorySummary>;
192
+ }
193
+ /**
194
+ * Self_hosted / cloud transport: routes every read+write through the hosted
195
+ * `/v1` API. Loop/name resolution (requireLoop/requireUniqueLoop/find*) is done
196
+ * client-side by listing and matching, mirroring the local Store behaviour, so a
197
+ * server that ignores a name filter can never return the wrong row.
198
+ */
199
+ export declare class ApiStore implements LoopStore {
200
+ private readonly client;
201
+ readonly baseUrl: string;
202
+ readonly transport: "cloud-http";
203
+ constructor(client: HasnaStorageClient, baseUrl: string);
204
+ private get t();
205
+ close(): Promise<void>;
206
+ createLoop(input: CreateLoopInput): Promise<Loop>;
207
+ getLoop(id: string): Promise<Loop | undefined>;
208
+ findLoopByName(name: string): Promise<Loop | undefined>;
209
+ requireLoop(idOrName: string): Promise<Loop>;
210
+ requireUniqueLoop(idOrName: string): Promise<Loop>;
211
+ private resolveLoop;
212
+ listLoops(opts?: Parameters<LoopStore["listLoops"]>[0]): Promise<Loop[]>;
213
+ countLoops(status?: LoopStatus, opts?: {
214
+ archived?: boolean;
215
+ includeArchived?: boolean;
216
+ }): Promise<number>;
217
+ updateLoop(id: string, patch: Partial<Pick<Loop, "status" | "nextRunAt" | "retryScheduledFor" | "expiresAt">>): Promise<Loop>;
218
+ renameLoop(id: string, name: string): Promise<Loop>;
219
+ archiveLoop(idOrName: string): Promise<Loop>;
220
+ unarchiveLoop(idOrName: string): Promise<Loop>;
221
+ deleteLoop(idOrName: string): Promise<boolean>;
222
+ createWorkflow(input: CreateWorkflowInput): Promise<WorkflowSpec>;
223
+ getWorkflow(id: string): Promise<WorkflowSpec | undefined>;
224
+ findWorkflowByName(name: string): Promise<WorkflowSpec | undefined>;
225
+ requireWorkflow(idOrName: string): Promise<WorkflowSpec>;
226
+ listWorkflows(opts?: Parameters<LoopStore["listWorkflows"]>[0]): Promise<WorkflowSpec[]>;
227
+ countWorkflows(opts?: {
228
+ status?: WorkflowSpec["status"];
229
+ }): Promise<number>;
230
+ archiveWorkflow(idOrName: string): Promise<WorkflowSpec>;
231
+ getWorkflowRun(id: string): Promise<WorkflowRun | undefined>;
232
+ requireWorkflowRun(id: string): Promise<WorkflowRun>;
233
+ listWorkflowRuns(opts?: Parameters<LoopStore["listWorkflowRuns"]>[0]): Promise<WorkflowRun[]>;
234
+ listWorkflowStepRuns(workflowRunId: string): Promise<WorkflowStepRun[]>;
235
+ listWorkflowEvents(workflowRunId: string, limit?: number): Promise<WorkflowEvent[]>;
236
+ recoverWorkflowRun(workflowRunId: string, reason?: string): Promise<{
237
+ run: WorkflowRun;
238
+ recoveredSteps: WorkflowStepRun[];
239
+ }>;
240
+ cancelWorkflowRun(_workflowRunId: string, _reason?: string): Promise<WorkflowRun>;
241
+ listWorkflowWorkItems(opts?: Parameters<LoopStore["listWorkflowWorkItems"]>[0]): Promise<WorkflowWorkItem[]>;
242
+ getWorkflowWorkItem(id: string): Promise<WorkflowWorkItem | undefined>;
243
+ requeueWorkflowWorkItem(_id: string, _patch?: {
244
+ reason?: string;
245
+ }): Promise<WorkflowWorkItem>;
246
+ listWorkflowInvocations(opts?: Parameters<LoopStore["listWorkflowInvocations"]>[0]): Promise<WorkflowInvocation[]>;
247
+ getWorkflowInvocation(id: string): Promise<WorkflowInvocation | undefined>;
248
+ getGoal(id: string): Promise<Goal | undefined>;
249
+ findGoalByLoop(idOrName: string): Promise<Goal | undefined>;
250
+ findGoalByRunId(id: string): Promise<Goal | undefined>;
251
+ listGoals(opts?: Parameters<LoopStore["listGoals"]>[0]): Promise<Goal[]>;
252
+ listGoalPlanNodes(goalIdOrPlanId: string): Promise<GoalPlanNode[]>;
253
+ listGoalRuns(opts?: Parameters<LoopStore["listGoalRuns"]>[0]): Promise<GoalRun[]>;
254
+ listRuns(opts?: Parameters<LoopStore["listRuns"]>[0]): Promise<LoopRun[]>;
255
+ getRun(id: string): Promise<LoopRun | undefined>;
256
+ writeRunReceipt(input: WriteRunReceiptInput): Promise<RunReceipt>;
257
+ getRunReceipt(runId: string): Promise<RunReceipt | undefined>;
258
+ listRunReceipts(opts?: Parameters<LoopStore["listRunReceipts"]>[0]): Promise<RunReceipt[]>;
259
+ pruneHistory(opts: PruneHistoryOptions): Promise<PruneHistorySummary>;
260
+ }
261
+ /**
262
+ * Resolve the client store for the current environment: an {@link ApiStore} when
263
+ * the client-flip contract resolves to cloud-http (self_hosted/cloud), else a
264
+ * {@link LocalStore}. Callers hold a {@link LoopStore} and never branch on mode.
265
+ */
266
+ export declare function getStore(env?: Env): LoopStore;
267
+ /** True when the resolved store is the hosted HTTP transport (cloud/self_hosted). */
268
+ export declare function isCloudStore(env?: Env): boolean;
@@ -1,4 +1,4 @@
1
- import type { CreateLoopInput, CreateWorkflowInvocationInput, CreateWorkflowInput, Goal, GoalAutoExecute, GoalPlanNode, GoalRun, GoalStatus, Loop, LoopRun, LoopStatus, RunStatus, TimeoutMs, WorkflowEvent, WorkflowInvocation, WorkflowRun, WorkflowRunStatus, WorkflowSpec, WorkflowStepRun, WorkflowWorkItem, WorkflowWorkItemStatus, UpsertWorkflowWorkItemInput } from "../types.js";
1
+ import type { CreateLoopInput, CreateWorkflowInvocationInput, CreateWorkflowInput, Goal, GoalAutoExecute, GoalPlanNode, GoalRun, GoalStatus, Loop, LoopRun, LoopStatus, RunReceipt, RunStatus, TimeoutMs, WriteRunReceiptInput, WorkflowEvent, WorkflowInvocation, WorkflowRun, WorkflowRunStatus, WorkflowSpec, WorkflowStepRun, WorkflowWorkItem, WorkflowWorkItemStatus, UpsertWorkflowWorkItemInput } from "../types.js";
2
2
  interface DaemonLeaseFence {
3
3
  daemonLeaseId?: string;
4
4
  now?: Date;
@@ -51,6 +51,23 @@ export interface RunRow {
51
51
  created_at: string;
52
52
  updated_at: string;
53
53
  }
54
+ export interface RunReceiptRow {
55
+ loop_id: string;
56
+ run_id: string;
57
+ machine_json: string;
58
+ repo: string;
59
+ task_ids_json: string;
60
+ knowledge_ids_json: string;
61
+ digest_id: string;
62
+ started_at: string | null;
63
+ finished_at: string | null;
64
+ status: string;
65
+ exit_code: number | null;
66
+ summary_json: string;
67
+ evidence_paths_json: string;
68
+ created_at: string;
69
+ updated_at: string;
70
+ }
54
71
  export interface WorkflowRow {
55
72
  id: string;
56
73
  name: string;
@@ -111,6 +128,7 @@ export interface WorkflowWorkItemRow {
111
128
  subject_ref: string;
112
129
  project_key: string | null;
113
130
  project_group: string | null;
131
+ machine_id: string | null;
114
132
  route_scope: string | null;
115
133
  priority: number;
116
134
  status: string;
@@ -229,6 +247,7 @@ export interface LeaseRow {
229
247
  }
230
248
  export declare function rowToLoop(row: LoopRow): Loop;
231
249
  export declare function rowToRun(row: RunRow): LoopRun;
250
+ export declare function rowToRunReceipt(row: RunReceiptRow): RunReceipt;
232
251
  export declare function rowToWorkflow(row: WorkflowRow): WorkflowSpec;
233
252
  export declare function rowToWorkflowRun(row: WorkflowRunRow): WorkflowRun;
234
253
  export declare function rowToWorkflowInvocation(row: WorkflowInvocationRow): WorkflowInvocation;
@@ -349,6 +368,7 @@ export interface RecordGoalEventInput {
349
368
  rawResponse?: unknown;
350
369
  }
351
370
  export declare function workItemStatusForLoopRun(status: RunStatus, attempt: number, maxAttempts: number | undefined): WorkflowWorkItemStatus | undefined;
371
+ export declare function scrubbedOrNull(value: string | undefined | null): string | null;
352
372
  /** Scrub secrets then bound size before persisting run stdout/stderr. */
353
373
  export declare function persistedRunOutput(value: string | undefined | null): string | null;
354
374
  export declare class Store {
@@ -360,6 +380,7 @@ export declare class Store {
360
380
  private migrate;
361
381
  private migrations;
362
382
  private createBaseSchema;
383
+ private createRunReceiptsSchema;
363
384
  /**
364
385
  * Add a column only if it does not already exist. Idempotent — avoids the
365
386
  * "duplicate column name" error that SQLite logs (via libsqlite3, before any
@@ -381,8 +402,10 @@ export declare class Store {
381
402
  listLoops(opts?: {
382
403
  status?: LoopStatus;
383
404
  limit?: number;
405
+ offset?: number;
384
406
  archived?: boolean;
385
407
  includeArchived?: boolean;
408
+ name?: string;
386
409
  }): Loop[];
387
410
  private withLatestRunSummaries;
388
411
  dueLoops(now: Date, limit?: number): Loop[];
@@ -566,7 +589,20 @@ export declare class Store {
566
589
  loopId?: string;
567
590
  status?: RunStatus;
568
591
  limit?: number;
592
+ offset?: number;
569
593
  }): LoopRun[];
594
+ writeRunReceipt(input: WriteRunReceiptInput, opts?: {
595
+ now?: Date;
596
+ }): RunReceipt;
597
+ getRunReceipt(runId: string): RunReceipt | undefined;
598
+ listRunReceipts(opts?: {
599
+ loopId?: string;
600
+ repo?: string;
601
+ taskId?: string;
602
+ knowledgeId?: string;
603
+ status?: string;
604
+ limit?: number;
605
+ }): RunReceipt[];
570
606
  private deferLiveExpiredRun;
571
607
  recoverExpiredRunLeases(now?: Date, opts?: DaemonLeaseFence & {
572
608
  limit?: number;
@@ -589,6 +625,17 @@ export declare class Store {
589
625
  }): number;
590
626
  countRuns(status?: RunStatus): number;
591
627
  exportMigrationRows(opts?: StoreMigrationRowsOptions): StoreMigrationRows;
628
+ /**
629
+ * Page through loop_runs for a streaming export. A full `exportMigrationRows`
630
+ * loads every run's stdout/stderr into memory at once (hundreds of MB on a
631
+ * busy host); a self-hosted backfill instead pulls stable ordered pages so
632
+ * peak memory stays bounded. Order is deterministic (created_at, id) so
633
+ * offset paging over an immutable snapshot never skips or repeats a row.
634
+ */
635
+ exportMigrationRunPage(opts: {
636
+ limit: number;
637
+ offset: number;
638
+ }): LoopRun[];
592
639
  private countTable;
593
640
  private migrationChecks;
594
641
  upsertMigrationWorkflow(workflow: WorkflowSpec, opts?: StoreMigrationUpsertOptions): WorkflowSpec;