smithers-orchestrator 0.2.4 → 0.2.6

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.
package/README.md CHANGED
@@ -1,92 +1,95 @@
1
1
  # Smithers
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/smithers.svg)](https://www.npmjs.com/package/smithers)
3
+ [![npm version](https://img.shields.io/npm/v/smithers-orchestrator.svg)](https://www.npmjs.com/package/smithers-orchestrator)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
6
6
  [![Bun](https://img.shields.io/badge/Bun-1.0+-black.svg)](https://bun.sh/)
7
7
 
8
- **Declarative JSX framework for building AI agent orchestration workflows.**
8
+ **Let your agent write agents.**
9
9
 
10
- I use Smithers for both long-term (weeks) agentic work, as well as one-off scripts.
10
+ React-style orchestration your coding agent can generate, then run safely as durable Ralph loops.
11
11
 
12
12
  <!-- TODO: Add GIF demo -->
13
13
 
14
14
  ![Smithers Demo](https://via.placeholder.com/800x400?text=Demo+GIF+Coming+Soon)
15
15
 
16
+ ```
17
+ ┌─────────────────────────────────────────────────────────────────────────────┐
18
+ │ React tree ──▶ Smithers executor ──▶ Tools + Claude ──▶ SQLite DB │
19
+ │ │
20
+ │ "React syntax, non-UI renderer" │
21
+ └─────────────────────────────────────────────────────────────────────────────┘
22
+ ```
23
+
24
+ - **Agent-native syntax** - Easy for Claude Code to generate, easy for humans to review
25
+ - **Sophisticated loops** - Multi-phase, parallel agents, conditional branches
26
+ - **Composable** - Build complex workflows from simple React components
27
+
16
28
  ---
17
29
 
18
30
  ## Table of Contents
19
31
 
20
- - [Why](#why)
32
+ - [Why Smithers](#why-smithers)
21
33
  - [Getting Started](#getting-started)
22
- - [AI SDK React Hooks](#ai-sdk-react-hooks)
23
- - [Recipes](#recipes)
34
+ - [Starter Workflows](#starter-workflows)
24
35
  - [Features](#features)
25
36
  - [Claude Component](#claude-component)
26
- - [Ralph Loop Controller](#ralph-loop-controller)
37
+ - [Sophisticated Ralph Loops](#sophisticated-ralph-loops)
27
38
  - [Structured Output with Zod](#structured-output-with-zod)
28
39
  - [MCP Tool Integration](#mcp-tool-integration)
29
40
  - [Smithers Subagent](#smithers-subagent)
30
41
  - [Git/JJ VCS Integration](#gitjj-vcs-integration)
31
- - [Orchestration Lifecycle](#orchestration-lifecycle)
32
42
  - [PhaseRegistry & Step](#phaseregistry--step)
33
43
  - [Parallel Execution](#parallel-execution)
34
- - [Database State Management](#database-state-management)
35
- - [Rate Limit Monitoring](#rate-limit-monitoring)
44
+ - [Database Persistence](#database-persistence)
45
+ - [FAQ](#faq)
36
46
  - [Contributing](#contributing)
37
47
 
38
48
  ---
39
49
 
40
- ## Why
50
+ ## Why Smithers
41
51
 
42
- I wanted a tool that allows me to:
52
+ ### The Problem
43
53
 
44
- - **Write agent workflows as JSX** - because declarative composition is easier to reason about than imperative chains
45
- - **Let Claude Code write the orchestration for me** - I describe what I want, and my agent builds the workflow
46
- - **Persist state across sessions** - pick up where I left off, even days later
47
- - **Mix short scripts with long-running workflows** - same syntax for a quick task or a week-long project
48
- - **See what my agents are doing** - full observability with database logging and reports
49
- - **Use reactive primitives** - React state means my workflows respond to state changes automatically
50
- - **Compose complex behaviors from simple components** - loops, phases, steps, and validation all snap together
51
- - **Keep everything in version control** - workflows are just TypeScript files
54
+ Simple Ralph loops work great for basic iteration. But as workflows grow complex:
55
+ - Multi-phase orchestration becomes hard to manage
56
+ - Parallel agents need coordination
57
+ - Plans live in prompts, not reviewable code
58
+ - Manual orchestration doesn't scale
52
59
 
53
- ---
60
+ ### The Solution
54
61
 
55
- ## Getting Started
62
+ Smithers uses React's component model + markup-like syntax to define execution plans. This isn't UI - it renders to **execution**.
56
63
 
57
- ### Prerequisites
64
+ **One syntax both humans and agents can work with:**
65
+ - You can read and review it
66
+ - Claude Code can generate it reliably
67
+ - Git can version it
58
68
 
59
- - **[Bun](https://bun.sh/)** - JavaScript runtime (v1.0+)
60
- - **[Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code)** - `bun install -g @anthropic-ai/claude-code`
69
+ **Sophisticated Ralph loops that stay reliable:**
70
+ - Multi-phase workflows with parallel agents
71
+ - Conditional branches, phases, steps
72
+ - Composable components you can reuse
73
+ - Persist state and audit history when you need it
61
74
 
62
- Optional:
63
- - **[jj (Jujutsu)](https://github.com/martinvonz/jj)** - Alternative VCS with better snapshot support
64
- - **[Codex CLI](https://github.com/openai/codex)** - Used for post-commit code reviews
75
+ ---
65
76
 
66
- ### Install into Claude Code
77
+ ## Getting Started
67
78
 
68
- Add to your Claude Code settings (`~/.claude/settings.json`):
79
+ ### Prerequisites
69
80
 
70
- ```json
71
- {
72
- "projects": {
73
- "/path/to/your/project": {
74
- "mcpServers": {},
75
- "allowedTools": ["smithers-orchestrator:*"]
76
- }
77
- }
78
- }
79
- ```
81
+ - **[Bun](https://bun.sh/)** v1.0+ (JavaScript runtime)
82
+ - **[Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code)** - `bun install -g @anthropic-ai/claude-code`
80
83
 
81
- Then install the package as a dev dependency:
84
+ ### Install
82
85
 
83
86
  ```bash
84
- bun add -d smithers-orchestrator
87
+ bun add smithers-orchestrator
85
88
  ```
86
89
 
87
- ### Use It
90
+ ### Let Claude Write It
88
91
 
89
- **You don't have to write Smithers yourself.** Tell Claude what you want:
92
+ **You don't have to write Smithers yourself.** Describe what you want:
90
93
 
91
94
  ```
92
95
  User: "Create a workflow that monitors my CI, fixes failures automatically,
@@ -95,191 +98,157 @@ User: "Create a workflow that monitors my CI, fixes failures automatically,
95
98
  Claude: *generates ci-recovery.tsx*
96
99
  ```
97
100
 
98
- Claude understands the component model and generates correct, working orchestration scripts.
101
+ ### Run It
102
+
103
+ ```bash
104
+ bun my-workflow.tsx
105
+ ```
106
+
107
+ ### Inspect History
108
+
109
+ ```bash
110
+ smithers db executions # View execution history
111
+ smithers db state --execution-id abc123 # Inspect specific run
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Starter Workflows
99
117
 
100
- ### Run a Workflow
118
+ ### 1. Night Shift: Run Until Tests Pass
119
+
120
+ Goal: Keep iterating until all tests pass, with incremental commits.
101
121
 
102
122
  ```tsx
103
123
  #!/usr/bin/env bun
124
+ import { createSmithersRoot, createSmithersDB, SmithersProvider, Claude } from "smithers-orchestrator";
104
125
 
105
- import { createSmithersRoot } from "smithers-orchestrator";
106
- import { createSmithersDB } from "smithers-orchestrator/db";
107
- import { SmithersProvider } from "smithers-orchestrator/components/SmithersProvider";
108
- import { Claude } from "smithers-orchestrator/components/Claude";
109
-
110
- const db = await createSmithersDB({ path: ".smithers/my-task" });
111
- const executionId = await db.execution.start("My Task", "scripts/my-task.tsx");
126
+ const db = await createSmithersDB({ path: ".smithers/night-shift" });
127
+ const executionId = await db.execution.start("Night Shift", "night-shift.tsx");
112
128
 
113
- async function MyWorkflow() {
129
+ function NightShift() {
114
130
  return (
115
- <SmithersProvider db={db} executionId={executionId}>
131
+ <SmithersProvider db={db} executionId={executionId} maxIterations={50}>
116
132
  <Claude
117
133
  model="sonnet"
118
- maxTurns={10}
119
- onFinished={(result) => console.log("Done:", result.output)}
134
+ onFinished={(result) => {
135
+ if (result.output.includes("All tests pass")) {
136
+ db.state.set("complete", "true");
137
+ }
138
+ }}
120
139
  >
121
- Analyze this codebase and suggest three improvements.
140
+ Run tests. If any fail, fix them and commit the fix. Repeat until all tests pass.
122
141
  </Claude>
123
142
  </SmithersProvider>
124
143
  );
125
144
  }
126
145
 
127
146
  const root = createSmithersRoot();
128
- await root.mount(MyWorkflow);
147
+ await root.mount(NightShift);
129
148
  await db.close();
130
149
  ```
131
150
 
132
151
  ```bash
133
- bun my-workflow.tsx
152
+ bun night-shift.tsx
134
153
  ```
135
154
 
136
- ### Inspect State
155
+ ### 2. PRD to Implementation
137
156
 
138
- All state persists in SQLite:
139
-
140
- ```bash
141
- smithers db executions # View execution history
142
- smithers db state --execution-id abc123 # View specific execution state
143
- ```
144
-
145
- ---
146
-
147
- ## AI SDK React Hooks
148
-
149
- Smithers re-exports the Vercel AI SDK React hooks so you can import everything
150
- from a single package:
157
+ Goal: Plan, implement, test, produce PR summary.
151
158
 
152
159
  ```tsx
153
- import { useChat, useCompletion, useSmithers } from "smithers-orchestrator";
160
+ #!/usr/bin/env bun
161
+ import { createSmithersRoot, createSmithersDB, SmithersProvider, Phase, Step, Claude } from "smithers-orchestrator";
154
162
 
155
- function ChatUI() {
156
- const { messages, sendMessage, status } = useChat({ api: "/api/chat" });
157
- const { db } = useSmithers();
163
+ const db = await createSmithersDB({ path: ".smithers/prd-impl" });
164
+ const executionId = await db.execution.start("PRD Implementation", "prd-impl.tsx");
158
165
 
166
+ function PRDToImplementation() {
159
167
  return (
160
- <div>
161
- <p>Status: {status}</p>
162
- {messages.map((message) => (
163
- <div key={message.id}>{message.content}</div>
164
- ))}
165
- <button onClick={() => sendMessage({ role: "user", content: "Hi" })}>
166
- Send
167
- </button>
168
- </div>
168
+ <SmithersProvider db={db} executionId={executionId} maxIterations={20}>
169
+ <Phase name="Plan">
170
+ <Step name="analyze-prd">
171
+ <Claude model="sonnet">
172
+ Read PRD.md. Create implementation plan with acceptance criteria.
173
+ </Claude>
174
+ </Step>
175
+ </Phase>
176
+
177
+ <Phase name="Implement">
178
+ <Step name="write-code">
179
+ <Claude model="sonnet">
180
+ Implement the plan. Commit after each logical unit of work.
181
+ </Claude>
182
+ </Step>
183
+ <Step name="write-tests">
184
+ <Claude model="sonnet">
185
+ Write tests for the implementation. Ensure all pass.
186
+ </Claude>
187
+ </Step>
188
+ </Phase>
189
+
190
+ <Phase name="Summary">
191
+ <Step name="pr-summary">
192
+ <Claude model="sonnet">
193
+ Generate PR summary with what changed and why.
194
+ </Claude>
195
+ </Step>
196
+ </Phase>
197
+ </SmithersProvider>
169
198
  );
170
199
  }
171
- ```
172
-
173
- You can also import directly from the hooks subpath:
174
200
 
175
- ```tsx
176
- import { useChat } from "smithers-orchestrator/hooks";
177
- import { useChat as useAiChat } from "smithers-orchestrator/hooks/ai-sdk";
201
+ const root = createSmithersRoot();
202
+ await root.mount(PRDToImplementation);
203
+ await db.close();
178
204
  ```
179
205
 
180
- ---
181
-
182
- ## Recipes
206
+ ### 3. Refactor with Checkpoints
183
207
 
184
- ### Multi-Phase Review Workflow
208
+ Goal: Staged refactoring with checkpoints and rollback capability.
185
209
 
186
210
  ```tsx
187
- async function ReviewWorkflow() {
188
- const phase = (await db.state.get("phase")) ?? "implement";
211
+ #!/usr/bin/env bun
212
+ import { createSmithersRoot, createSmithersDB, SmithersProvider, Phase, Step, Claude, Worktree } from "smithers-orchestrator";
213
+
214
+ const db = await createSmithersDB({ path: ".smithers/refactor" });
215
+ const executionId = await db.execution.start("Refactor", "refactor.tsx");
189
216
 
217
+ function RefactorWorkflow() {
190
218
  return (
191
- <SmithersProvider db={db} executionId={executionId} maxIterations={10}>
192
- <Orchestration globalTimeout={3600000}>
193
- {phase === "implement" && (
194
- <Phase name="Implementation">
195
- <Claude
196
- model="sonnet"
197
- onFinished={() => db.state.set("phase", "review")}
198
- >
199
- Implement the user authentication feature.
219
+ <SmithersProvider db={db} executionId={executionId} maxIterations={30}>
220
+ <Worktree branch="smithers/refactor" cleanup>
221
+ <Phase name="Analyze">
222
+ <Step name="identify-targets">
223
+ <Claude model="sonnet">
224
+ Identify code that needs refactoring. Create a prioritized list.
225
+ </Claude>
226
+ </Step>
227
+ </Phase>
228
+
229
+ <Phase name="Refactor">
230
+ <Step name="refactor-code" snapshotBefore commitAfter>
231
+ <Claude model="sonnet">
232
+ Refactor each item. Run tests after each change. Commit if green.
233
+ </Claude>
234
+ </Step>
235
+ </Phase>
236
+
237
+ <Phase name="Verify">
238
+ <Step name="final-check">
239
+ <Claude model="sonnet">
240
+ Run full test suite. Document all changes made.
200
241
  </Claude>
201
- </Phase>
202
- )}
203
-
204
- {phase === "review" && (
205
- <Phase name="Code Review">
206
- <Review
207
- target={{ type: "diff", ref: "main" }}
208
- criteria={[
209
- "No security vulnerabilities",
210
- "Tests cover edge cases",
211
- "Types are properly defined",
212
- ]}
213
- onFinished={(review) => {
214
- if (review.approved) {
215
- db.state.set("phase", "complete");
216
- } else {
217
- db.state.set("phase", "implement");
218
- }
219
- }}
220
- />
221
- </Phase>
222
- )}
223
- </Orchestration>
242
+ </Step>
243
+ </Phase>
244
+ </Worktree>
224
245
  </SmithersProvider>
225
246
  );
226
247
  }
227
- ```
228
-
229
- ### Structured Output with Validation
230
-
231
- ```tsx
232
- import { z } from 'zod'
233
-
234
- const AnalysisSchema = z.object({
235
- summary: z.string(),
236
- issues: z.array(z.object({
237
- severity: z.enum(['low', 'medium', 'high', 'critical']),
238
- file: z.string(),
239
- description: z.string(),
240
- })),
241
- recommendations: z.array(z.string()),
242
- })
243
-
244
- <Claude
245
- model="sonnet"
246
- schema={AnalysisSchema}
247
- schemaRetries={2}
248
- onFinished={(result) => {
249
- // result.structured is typed!
250
- for (const issue of result.structured.issues) {
251
- console.log(`[${issue.severity}] ${issue.file}: ${issue.description}`)
252
- }
253
- }}
254
- >
255
- Analyze this codebase for security issues.
256
- </Claude>
257
- ```
258
-
259
- ### Database Access with MCP
260
-
261
- ```tsx
262
- <Claude model="sonnet" maxTurns={5}>
263
- <Sqlite path="./analytics.db">
264
- The database contains user_events and sessions tables. Use this to answer
265
- questions about user behavior.
266
- </Sqlite>
267
- What are the top 10 most common user actions this week?
268
- </Claude>
269
- ```
270
-
271
- ### Spawning Subagent Workflows
272
248
 
273
- ```tsx
274
- <Smithers
275
- plannerModel="opus"
276
- executionModel="sonnet"
277
- onFinished={(result) => console.log(result.output)}
278
- >
279
- Create a comprehensive test suite for the authentication module. Include unit
280
- tests, integration tests, and edge cases. Set up proper mocking for external
281
- dependencies.
282
- </Smithers>
249
+ const root = createSmithersRoot();
250
+ await root.mount(RefactorWorkflow);
251
+ await db.close();
283
252
  ```
284
253
 
285
254
  ---
@@ -309,25 +278,40 @@ The core agent component that executes Claude with full tool access:
309
278
  </Claude>
310
279
  ```
311
280
 
312
- ### Ralph Loop Controller
281
+ ### Sophisticated Ralph Loops
313
282
 
314
- > **Prefer SmithersProvider.** The loop functionality is built into `<SmithersProvider>` with `maxIterations`. Use `<Ralph>` only for nested loops within a workflow.
315
-
316
- Named after Ralph Wiggum's "I'm in danger" catchphrase - controls iterative loops that could run away:
283
+ Ralphing = continuous agent iteration with verification gates. Smithers lets you build **complex** Ralph loops with structure:
317
284
 
318
285
  ```tsx
319
- <SmithersProvider db={db} executionId={executionId} maxIterations={10}>
320
- {/* Children re-render on each iteration */}
321
- <Claude
322
- onFinished={() => {
323
- /* state change triggers next iteration */
324
- }}
325
- >
326
- Keep improving until tests pass.
327
- </Claude>
286
+ <SmithersProvider db={db} executionId={executionId} maxIterations={50}>
287
+ <Phase name="Implement">
288
+ <Parallel>
289
+ <Claude model="sonnet">Fix auth module</Claude>
290
+ <Claude model="sonnet">Fix database module</Claude>
291
+ </Parallel>
292
+ </Phase>
293
+
294
+ <Phase name="Verify">
295
+ <Claude
296
+ model="sonnet"
297
+ onFinished={(result) => {
298
+ if (result.output.includes("All tests pass")) {
299
+ db.state.set("complete", "true");
300
+ }
301
+ }}
302
+ >
303
+ Run tests. If failures, iterate.
304
+ </Claude>
305
+ </Phase>
328
306
  </SmithersProvider>
329
307
  ```
330
308
 
309
+ **What Smithers adds:**
310
+ - Multi-phase workflows with conditional transitions
311
+ - Parallel agent execution with coordination
312
+ - Composable, reusable components
313
+ - Optional persistence for long-running workflows
314
+
331
315
  ### Structured Output with Zod
332
316
 
333
317
  Get typed, validated responses with automatic retry:
@@ -473,39 +457,57 @@ Run multiple agents concurrently within a step:
473
457
  </Parallel>
474
458
  ```
475
459
 
476
- ### Database State Management
460
+ ### Database Persistence
477
461
 
478
- Persistent state that survives restarts:
462
+ Every run is a "flight recorder" - all state persists in SQLite:
479
463
 
480
464
  ```tsx
481
- // Set state
482
- await db.state.set("phase", "review", "code_complete");
465
+ // Set state (survives restarts)
466
+ await db.state.set("phase", "review");
483
467
 
484
468
  // Get state
485
469
  const phase = await db.state.get("phase");
486
470
 
487
- // Query history
471
+ // Query history - see how state evolved
488
472
  const history = await db.state.getHistory("phase");
489
473
 
490
- // View all state
491
- const all = await db.state.getAll();
474
+ // Resume incomplete executions
475
+ const incomplete = await db.execution.findIncomplete();
476
+ if (incomplete) {
477
+ // Pick up exactly where you left off
478
+ executionId = incomplete.id;
479
+ }
492
480
  ```
493
481
 
494
- ### Rate Limit Monitoring
482
+ ```bash
483
+ # Inspect from CLI
484
+ smithers db executions # List all runs
485
+ smithers db state --execution-id abc123 # See state for a run
486
+ smithers db stats # Database statistics
487
+ ```
488
+
489
+ ---
495
490
 
496
- Track provider rate limit headroom and execution-scoped token usage:
491
+ ## FAQ
497
492
 
498
- ```typescript
499
- import { createRateLimitMonitor } from "smithers-orchestrator/rate-limits";
493
+ ### Is this actually React? Do I need to run a UI?
500
494
 
501
- const monitor = createRateLimitMonitor({
502
- anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! },
503
- db,
504
- });
495
+ No UI. React is a component model + markup-like syntax. People already use it to render to non-DOM targets (CLI, PDFs, native). Smithers renders to **execution**, not DOM.
505
496
 
506
- const status = await monitor.getStatus("anthropic", "claude-sonnet-4");
507
- const usage = await monitor.getUsage(executionId);
508
- ```
497
+ ### Why not YAML/JSON for workflows?
498
+
499
+ Because you want:
500
+ - Composition and reuse
501
+ - Version control diffs that make sense
502
+ - Something your coding agent can generate AND you can review like normal code
503
+
504
+ ### How does this relate to Ralphing?
505
+
506
+ Ralphing is the outer loop (iterate until verification passes). Smithers gives it structure, persistence, and inspectability. Think: "Smithers productionizes Ralph loops."
507
+
508
+ ### What's a Ralph loop?
509
+
510
+ From [vercel-labs/ralph-loop-agent](https://github.com/vercel-labs/ralph-loop-agent): continuous autonomy where the agent loops until the task is actually done, with verification gates (tests/linters). Smithers makes these loops durable.
509
511
 
510
512
  ---
511
513
 
@@ -521,4 +523,4 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
521
523
 
522
524
  ---
523
525
 
524
- **Built with React, powered by Claude.**
526
+ **Let your agent write agents. Built with React, powered by Claude.**
package/dist/bin/cli.js CHANGED
@@ -3679,6 +3679,25 @@ function createStateModule(ctx) {
3679
3679
  return rdb.query("SELECT * FROM transitions WHERE key = ? ORDER BY created_at DESC LIMIT ?", [key, limit]);
3680
3680
  }
3681
3681
  return rdb.query("SELECT * FROM transitions ORDER BY created_at DESC LIMIT ?", [limit]);
3682
+ },
3683
+ has: (key) => {
3684
+ if (rdb.isClosed)
3685
+ return false;
3686
+ const row = rdb.queryOne("SELECT COUNT(*) as count FROM state WHERE key = ?", [key]);
3687
+ return (row?.count ?? 0) > 0;
3688
+ },
3689
+ delete: (key, trigger) => {
3690
+ if (rdb.isClosed)
3691
+ return;
3692
+ const oldValue = state.get(key);
3693
+ if (oldValue === null)
3694
+ return;
3695
+ rdb.run("DELETE FROM state WHERE key = ?", [key]);
3696
+ rdb.invalidate(["state"]);
3697
+ const currentExecutionId = getCurrentExecutionId();
3698
+ if (currentExecutionId) {
3699
+ rdb.run("INSERT INTO transitions (id, execution_id, key, old_value, new_value, trigger, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [uuid(), currentExecutionId, key, JSON.stringify(oldValue), "null", trigger ?? "delete", now()]);
3700
+ }
3682
3701
  }
3683
3702
  };
3684
3703
  return state;
@@ -4181,6 +4200,17 @@ function createTasksModule(ctx) {
4181
4200
  return 0;
4182
4201
  const result = rdb.queryOne("SELECT value FROM state WHERE key = 'ralphCount'");
4183
4202
  return parseJson(result?.value, 0);
4203
+ },
4204
+ withTask: async (componentType, componentName, fn) => {
4205
+ const taskId = tasks.start(componentType, componentName);
4206
+ try {
4207
+ const result = await fn();
4208
+ tasks.complete(taskId);
4209
+ return result;
4210
+ } catch (error2) {
4211
+ tasks.fail(taskId);
4212
+ throw error2;
4213
+ }
4184
4214
  }
4185
4215
  };
4186
4216
  return tasks;