@mindstudio-ai/remy 0.1.277 → 0.1.278
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.
|
@@ -202,25 +202,23 @@ export async function createPurchaseOrder(input: {
|
|
|
202
202
|
|
|
203
203
|
A method can return immediately while kicking off slow work (like `runTask()`) that continues in the background. Don't await the slow call — use `.then()` / `.catch()` to update the record when it completes, and return an early result to the caller. The frontend polls the record's status to track progress. Wrap background chains other than `runTask()` in `mindstudio.waitUntil(...)` so the platform keeps the sandbox alive for them and records an interruption if they're cut short — `runTask()` registers itself automatically.
|
|
204
204
|
|
|
205
|
-
The example below shows the fire-and-forget shape, not a complete `runTask()` call. Load the `taskAgents` skill before writing one — configuring its tools,
|
|
205
|
+
The example below shows the fire-and-forget shape, not a complete `runTask()` call. Load the `taskAgents` skill before writing one — configuring its tools, the `outputSchema` output contract, and handling failures are all there, and none of them are visible here.
|
|
206
206
|
|
|
207
207
|
```typescript
|
|
208
208
|
export async function enrichRestaurant(input: { id: string; name: string }) {
|
|
209
209
|
await Restaurants.update(input.id, { status: 'enriching' });
|
|
210
210
|
|
|
211
211
|
// Fire — don't await
|
|
212
|
-
mindstudio.runTask
|
|
212
|
+
mindstudio.runTask({
|
|
213
213
|
prompt: '...',
|
|
214
214
|
input: { name: input.name },
|
|
215
215
|
tools: ['searchGoogle', 'fetchUrl', 'generateImage'],
|
|
216
|
-
|
|
216
|
+
outputSchema: { type: 'object', properties: { /* ... */ }, required: [/* ... */] },
|
|
217
217
|
model: 'claude-5-sonnet',
|
|
218
218
|
}).then(async (result) => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
await Restaurants.update(input.id, { status: 'failed' });
|
|
223
|
-
}
|
|
219
|
+
// outputSchema means result.output is validated and typed — a task that
|
|
220
|
+
// can't produce conforming output rejects into the .catch instead.
|
|
221
|
+
await Restaurants.update(input.id, { ...result.output, status: 'complete' });
|
|
224
222
|
}).catch(async () => {
|
|
225
223
|
await Restaurants.update(input.id, { status: 'failed' });
|
|
226
224
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Task Agents
|
|
3
|
-
what: A full autonomous agent loop callable from any method. Give it a prompt, a set of tools, and
|
|
3
|
+
what: A full autonomous agent loop callable from any method. Give it a prompt, a set of tools, and a JSON Schema for the output shape; the platform runs the model until it produces validated output matching that schema — searching, scraping, generating images, retrying approaches that failed, and calling your app's own methods to read and write data as it goes. Tools can be any of the 1000+ SDK actions and your own methods in any combination, which is what makes it part of the app rather than a detached research bot. This is the difference between a feature that saves what the user typed and one that researches, enriches, and creates on their behalf, and it is one of the most powerful things the platform can do. Consider it whenever a feature would be dramatically more compelling if the app could do real work autonomously.
|
|
4
4
|
when: Before writing any `mindstudio.runTask()` call — background enrichment, research-and-generate, anything where the model decides its own next step.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ when: Before writing any `mindstudio.runTask()` call — background enrichment,
|
|
|
8
8
|
|
|
9
9
|
A user types the name of a restaurant into your app, or uploads a photo of a storefront. The API call returns early, and in the background, a task agent searches Google, finds the official website, scrapes the address, gets the official social media accounts, and generates a stylized watercolor postcard of the exterior from images it found online. The user gets back a rich, illustrated card with the canonical name, website, address, and a custom image. A few tool calls (some in parallel), fully autonomous.
|
|
10
10
|
|
|
11
|
-
`runTask()` makes this possible. It runs a multi-step, tool-use agent loop: give it a prompt, a set of tools, and
|
|
11
|
+
`runTask()` makes this possible. It runs a multi-step, tool-use agent loop: give it a prompt, a set of tools, and a JSON Schema for the structured output you want (`outputSchema`). The platform runs the loop (calling the model, executing tool calls, feeding results back) until the model produces JSON conforming to your schema — validated every turn, with automatic repair when it doesn't. `result.output` is typed by inference from the schema: no generic argument, no manual validation. The model decides what to do next based on intermediate results — retrying searches with different terms, working around failed tools, batching independent calls in parallel.
|
|
12
12
|
|
|
13
13
|
Tools are **SDK actions** (`searchGoogle`, `generateImage`, …) and **your own app's methods** (`{ appMethod: 'saveVendor' }`), in any combination. That second half is what makes a task agent part of your app rather than a detached research bot: it can read your tables to decide what to do next, and write results back itself instead of handing them to you to persist.
|
|
14
14
|
|
|
@@ -38,12 +38,7 @@ Run tasks in the background — depending on complexity they can take time to co
|
|
|
38
38
|
```typescript
|
|
39
39
|
import { mindstudio } from '@mindstudio-ai/agent';
|
|
40
40
|
|
|
41
|
-
const result = await mindstudio.runTask
|
|
42
|
-
name: string;
|
|
43
|
-
url: string;
|
|
44
|
-
address: string;
|
|
45
|
-
photoUrl: string;
|
|
46
|
-
}>({
|
|
41
|
+
const result = await mindstudio.runTask({
|
|
47
42
|
prompt: `You are a restaurant research assistant. Given a restaurant name,
|
|
48
43
|
find its canonical name, website URL, full address, and create a stylized
|
|
49
44
|
watercolor illustration of the restaurant exterior. Save the result before
|
|
@@ -61,30 +56,46 @@ const result = await mindstudio.runTask<{
|
|
|
61
56
|
},
|
|
62
57
|
],
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
outputSchema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
name: { type: 'string' },
|
|
63
|
+
url: { type: ['string', 'null'] }, // nullable = a type array, never `nullable: true`
|
|
64
|
+
address: { type: 'string' },
|
|
65
|
+
photoUrl: { type: 'string' },
|
|
66
|
+
},
|
|
67
|
+
required: ['name', 'url', 'address', 'photoUrl'],
|
|
69
68
|
},
|
|
70
69
|
|
|
71
70
|
model: 'claude-5-sonnet', // ask askMindStudioSdk — don't copy this one blind
|
|
72
71
|
maxTurns: 15,
|
|
73
72
|
});
|
|
74
73
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
throw new Error('Task agent failed');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
console.log(result.output.name); // 'Tartine Bakery'
|
|
82
|
-
console.log(result.output.photoUrl); // URL to the generated illustration
|
|
74
|
+
// result.output is typed from the schema — no generic argument, no validation.
|
|
75
|
+
console.log(result.output.name); // string
|
|
76
|
+
console.log(result.output.url); // string | null
|
|
83
77
|
```
|
|
84
78
|
|
|
85
|
-
##
|
|
79
|
+
## Output Contracts: `outputSchema` vs `structuredOutputExample`
|
|
86
80
|
|
|
87
|
-
`
|
|
81
|
+
**With `outputSchema` (use this):** validation is built in. The schema is plain JSON Schema in the tool-definition dialect — `type`, `properties`, `required`, `enum`, `items`, nullability via type arrays like `['string', 'null']`. No `oneOf`/`anyOf`/`$ref`, no `nullable: true` — out-of-dialect schemas are rejected up front with `task_output_schema_unsupported`. Output is validated every turn and repaired automatically; `runTask()` either returns schema-conforming typed output or throws a `MindStudioError` with `code === 'task_output_schema_mismatch'` (raw text and validation errors in `err.details`). It never resolves with garbage, so use `result.output` directly and validate only domain invariants the schema can't express. For dynamic value sets, build the schema at runtime and put the set in an `enum` — the value is checked at runtime even though the type widens to `string`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import type { JsonObjectSchema } from '@mindstudio-ai/agent';
|
|
85
|
+
|
|
86
|
+
const services = [...new Set(rows.map((r) => r.service))];
|
|
87
|
+
const result = await mindstudio.runTask({
|
|
88
|
+
// ...
|
|
89
|
+
outputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: { service: { enum: services } },
|
|
92
|
+
required: ['service'],
|
|
93
|
+
} as const satisfies JsonObjectSchema, // needed when the schema lives in a variable
|
|
94
|
+
// ...
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**With `structuredOutputExample` (legacy):** the output shape is suggested by example only, `output` is typed by your generic argument, and `runTask()` can return successfully with garbage — fields null, data echoed back, or raw text instead of JSON. Always check `parsedSuccessfully` before using the output:
|
|
88
99
|
|
|
89
100
|
```typescript
|
|
90
101
|
const result = await mindstudio.runTask<MyType>({ ... });
|
|
@@ -151,7 +162,8 @@ When a task agent produces user-facing text, the prompt must state the voice and
|
|
|
151
162
|
| `prompt` | Yes | — | System prompt defining the agent's behavior |
|
|
152
163
|
| `input` | Yes | — | Structured input (passed as user message) |
|
|
153
164
|
| `tools` | Yes | — | SDK action names and/or `{ appMethod, description }` entries, each with optional `defaults` |
|
|
154
|
-
| `
|
|
165
|
+
| `outputSchema` | One of these two | — | Plain JSON Schema for the output (`type`/`properties`/`required`/`enum`/`items`; nullable via type arrays, never `nullable: true`; no `oneOf`/`$ref`). Validated every turn with automatic repair; `result.output` typed from the schema. Use this |
|
|
166
|
+
| `structuredOutputExample` | One of these two | — | Legacy: object or JSON string showing expected output shape, unvalidated. Use realistic example values, not placeholders like `'string'`, and always check `parsedSuccessfully` |
|
|
155
167
|
| `model` | Yes | — | Model ID (must support tool use). Ask `askMindStudioSdk` for the right one — MindStudio's ids don't match vendor ids, so a plausible-looking guess is usually wrong |
|
|
156
168
|
| `maxTurns` | No | 20 | Max loop iterations (capped at 100) |
|
|
157
169
|
| `onEvent` | No | — | SSE event callback for real-time streaming |
|
|
@@ -160,9 +172,9 @@ When a task agent produces user-facing text, the prompt must state the voice and
|
|
|
160
172
|
|
|
161
173
|
```typescript
|
|
162
174
|
interface RunTaskResult<T> {
|
|
163
|
-
output: T; //
|
|
175
|
+
output: T; // With outputSchema: validated, typed from the schema. With an example: whatever parsed
|
|
164
176
|
outputRaw: string; // Raw model text before JSON parse
|
|
165
|
-
parsedSuccessfully: boolean; //
|
|
177
|
+
parsedSuccessfully: boolean; // Example mode only — always true in schema mode (a failure throws instead)
|
|
166
178
|
turns: number; // Number of loop iterations used
|
|
167
179
|
usage: {
|
|
168
180
|
inputTokens: number;
|
|
@@ -201,22 +213,23 @@ Without `onEvent`, the SDK uses async polling (returns silently when complete).
|
|
|
201
213
|
## Error Handling
|
|
202
214
|
|
|
203
215
|
- Model produces non-JSON output: retried automatically if turns remain
|
|
216
|
+
- With `outputSchema`, JSON that doesn't conform: the validation errors go back to the model as a repair turn (up to 3), then the call throws
|
|
204
217
|
- Tool execution fails: error fed back to model, it can retry or work around it
|
|
205
218
|
- An app method that throws: its actual error message goes back to the model, so a `MindStudioError` you throw deliberately ("vendor not found", "missing required field") is usable guidance the agent can act on. Worth throwing informative errors in methods you expose as tools
|
|
206
219
|
- Max turns exceeded: one final forced output attempt with tools disabled
|
|
207
|
-
-
|
|
220
|
+
- Exhausted with nonconforming output: schema mode **throws** `task_output_schema_mismatch` (raw text and errors in `err.details`); example mode resolves with `parsedSuccessfully: false` and raw text in `outputRaw`
|
|
208
221
|
|
|
209
222
|
```typescript
|
|
210
223
|
try {
|
|
211
|
-
const result = await mindstudio.runTask({ ... });
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
console.error('Raw output:', result.outputRaw);
|
|
215
|
-
console.error('Tool calls:', result.toolCalls);
|
|
216
|
-
}
|
|
224
|
+
const result = await mindstudio.runTask({ ...with outputSchema... });
|
|
225
|
+
// Conforming, typed output — use it directly.
|
|
226
|
+
await Table.update(id, result.output);
|
|
217
227
|
} catch (err) {
|
|
218
228
|
if (err instanceof MindStudioError) {
|
|
219
|
-
// err.code: '
|
|
229
|
+
// err.code: 'task_output_schema_mismatch' (couldn't produce conforming output;
|
|
230
|
+
// err.details has outputRaw + validation errors + toolCalls)
|
|
231
|
+
// | 'task_output_schema_unsupported' (schema uses out-of-dialect keywords)
|
|
232
|
+
// | 'task_execution_error' | 'poll_token_expired' | 'stream_error'
|
|
220
233
|
}
|
|
221
234
|
}
|
|
222
235
|
```
|
|
@@ -64,17 +64,17 @@ When a plan includes multiple screens/API calls, always note this item for the d
|
|
|
64
64
|
|
|
65
65
|
- **Auth state read once instead of subscribed.** If the plan reads `auth.currentUser` or `auth.getCurrentUser()` in a `useState` initializer, at component top-level, or in a one-time check, the UI won't update after login/logout. The correct pattern is `auth.onAuthStateChanged(cb)` which fires immediately and on every auth transition. Flag if you see auth state read without a subscription.
|
|
66
66
|
|
|
67
|
-
- **Manual multi-step chains that should be `runTask()`.** If a method chains AI-driven work with branching logic (search, then scrape based on results, then generate based on what was scraped), that's a `runTask()` use case. `runTask()` runs an agent loop that autonomously calls tools and returns structured JSON. The developer writes a prompt and
|
|
67
|
+
- **Manual multi-step chains that should be `runTask()`.** If a method chains AI-driven work with branching logic (search, then scrape based on results, then generate based on what was scraped), that's a `runTask()` use case. `runTask()` runs an agent loop that autonomously calls tools and returns structured JSON. The developer writes a prompt and a JSON Schema for the output (`outputSchema`) instead of imperative code. Flag when you see methods with complex sequential/branching chains — especially research, enrichment, or content generation pipelines. Similarly, flag opportunities where the developer might not have realized they could get better and richer data via runTask - it's a really powerful lever for working with data (e.g., user provides some fragment and agent task goes off and enriches it) that the developer might not have remembered when planning their work.
|
|
68
68
|
|
|
69
69
|
Tools are SDK actions **and the app's own methods** (`{ appMethod: 'saveVendor', description: '...' }`), which widens this considerably. The pattern most worth looking for: work that needs to *read* app state to decide what to do next (fetch rows, loop, research each, update each) hand-rolled as a loop, when the agent could take a read method and a write method and exercise the judgment itself. App methods run as the invoking user with their roles, so authorization is unchanged.
|
|
70
70
|
|
|
71
|
-
Two things NOT to flag. A deterministic sequence that happens to touch several methods — if the order is known up front and no judgment is involved, imperative code is correct and cheaper. And the fire-and-forget background pattern, where a method kicks off `runTask()` and writes the result back in `.then()`: that write-back is doing status management
|
|
71
|
+
Two things NOT to flag. A deterministic sequence that happens to touch several methods — if the order is known up front and no judgment is involved, imperative code is correct and cheaper. And the fire-and-forget background pattern, where a method kicks off `runTask()` and writes the result back in `.then()` with failures landing in `.catch()`: that write-back is doing status management the agent can't do for itself. That one is the recommended pattern, not a smell.
|
|
72
72
|
|
|
73
73
|
- **Task agent tool descriptions missing or recycled.** When a plan exposes app methods to `runTask()`, each entry should carry an inline `description` written for *that* task — when to call it, when not to, what to do with the result. Falling back to the method's own description is allowed but usually too generic, and the description is the main thing determining whether the agent uses the tool correctly. Flag entries with no description, or the same description pasted across different tasks.
|
|
74
74
|
|
|
75
75
|
- **A method exposed as a task tool that itself calls `runTask()`.** Task agents can't nest — the inner call is rejected at runtime. Flag it and suggest flattening the decomposition.
|
|
76
76
|
|
|
77
|
-
- **MindStudio SDK `runTask()` output used without validation.** `runTask()` can return successfully with garbage output (null fields, echoed input, raw text)
|
|
77
|
+
- **MindStudio SDK `runTask()` output used without validation — check which output option the call uses.** With `outputSchema` (the current, preferred form): the SDK validates every turn and throws `MindStudioError` `code === 'task_output_schema_mismatch'` instead of returning garbage, so using `result.output` directly is correct — flag only a missing `.catch()`/`try-catch` where the failure would otherwise vanish (fire-and-forget chains especially). With `structuredOutputExample` (legacy): `runTask()` can return successfully with garbage output (null fields, echoed input, raw text) — flag `result.output` used without checking `result.parsedSuccessfully` first, and suggest migrating to `outputSchema`. That unvalidated form is the #1 footgun with task agents.
|
|
78
78
|
|
|
79
79
|
- **Layout shift with dynamic data or AI generated text** If the plan includes dynamically-sized data (e.g., a wizard form with questions of differing lengths) or AI generated text (where text stream length is unpredictable), make sure to flag concerns about layout stability. Everything must either be a fixed size or smoothly animate between sizes. Text can never be clipped by a container or cause layout to jump around or grow in snappy/janky ways. Make sure to remind the developer that this is important to pay attention to.
|
|
80
80
|
|