@mindstudio-ai/remy 0.1.256 → 0.1.258
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/dist/headless.js +514 -315
- package/dist/index.js +517 -288
- package/dist/prompt/compiled/files.md +17 -3
- package/dist/prompt/compiled/interfaces.md +26 -472
- package/dist/prompt/compiled/methods.md +10 -15
- package/dist/prompt/compiled/scenarios.md +16 -0
- package/dist/prompt/compiled/sdk-actions.md +1 -2
- package/dist/prompt/{compiled/agent-interfaces.md → skills/agentInterfaces.md} +118 -10
- package/dist/prompt/skills/dataSources.md +131 -0
- package/dist/prompt/skills/inboundEmail.md +116 -0
- package/dist/prompt/skills/mcpInterfaces.md +280 -0
- package/dist/prompt/skills/restApi.md +149 -0
- package/dist/prompt/skills/scheduledJobs.md +51 -0
- package/dist/prompt/{compiled/task-agents.md → skills/taskAgents.md} +10 -8
- package/dist/prompt/skills/webhooks.md +108 -0
- package/dist/prompt/static/authoring.md +1 -1
- package/dist/prompt/static/instructions.md +1 -1
- package/dist/subagents/designExpert/prompts/images.md +1 -1
- package/package.json +2 -2
- package/dist/prompt/.notes.md +0 -194
- package/dist/prompt/compiled/README.md +0 -100
- package/dist/prompt/compiled/mcp-interfaces.md +0 -34
- package/dist/prompt/compiled/media-cdn.md +0 -51
- package/dist/prompt/sources/llms.txt +0 -1618
- package/dist/subagents/.notes-background-agents.md +0 -64
- package/dist/subagents/codeSanityCheck/.notes.md +0 -44
- package/dist/subagents/designExpert/.notes.md +0 -265
- package/dist/subagents/productVision/.notes.md +0 -79
|
@@ -104,6 +104,22 @@ This is deterministic — same scenario always produces the same state.
|
|
|
104
104
|
|
|
105
105
|
Scenarios are useful for seeding initial app state after build for testing, as well as to give the user a first impression of an app that is already filled with data and looks and feels usable. The user can choose to run further scenarios after initial build by clicking the Scenarios tab and selecting a scenario to run.
|
|
106
106
|
|
|
107
|
+
## What scenarios don't touch
|
|
108
|
+
|
|
109
|
+
**Scenarios seed database tables and nothing else.** They do not touch file stores or data sources —
|
|
110
|
+
deliberately: both are durable and shared across dev and prod, with no per-release copy to reset, so
|
|
111
|
+
there is nothing to truncate.
|
|
112
|
+
|
|
113
|
+
Don't try to seed documents into a data source from a scenario, and don't write `clear()`-style reset
|
|
114
|
+
helpers for one. Load a test corpus once from the CLI instead:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
mindstudio-prod datasources add --source policies --wait fixtures/*.pdf
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Re-running it is free (documents are content-addressed), so it's safe to keep in a setup script
|
|
121
|
+
beside your scenarios.
|
|
122
|
+
|
|
107
123
|
## Scenario Data
|
|
108
124
|
|
|
109
125
|
Align scenario data to the vibe of the app - construct data that feels like it fits.
|
|
@@ -83,7 +83,6 @@ result.$billingCost; // cost in credits (if applicable)
|
|
|
83
83
|
|
|
84
84
|
| Action | What it does |
|
|
85
85
|
|--------|-------------|
|
|
86
|
-
| `uploadFile` | Upload a file to CDN |
|
|
87
86
|
| `downloadVideo` | Download a video URL |
|
|
88
87
|
| `getMediaMetadata` | Get dimensions, duration, etc. |
|
|
89
88
|
| `convertPdfToImages` | PDF pages to PNG images |
|
|
@@ -154,4 +153,4 @@ Consider the ways in which AI can be incorporated into backend methods to solve
|
|
|
154
153
|
|
|
155
154
|
### Task Agents
|
|
156
155
|
|
|
157
|
-
For multi-step tasks where the model needs to autonomously compose actions (research + scrape + generate, enrichment pipelines, content creation), use `runTask()` instead of chaining actions manually. It runs an agent loop and returns structured JSON. Its tools can include SDK actions as well as your app's own methods, so the agent can read your data to decide what to do next and write results back itself.
|
|
156
|
+
For multi-step tasks where the model needs to autonomously compose actions (research + scrape + generate, enrichment pipelines, content creation), use `runTask()` instead of chaining actions manually. It runs an agent loop and returns structured JSON. Its tools can include SDK actions as well as your app's own methods, so the agent can read your data to decide what to do next and write results back itself. Load the `taskAgents` skill before writing one — it is the full reference.
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Agent Interfaces
|
|
3
|
+
what: Conversational AI as a first-class interface to the app — an LLM with authenticated, per-user access to the app's methods as tools, paired with a streaming chat UI. The platform handles auth, tool dispatch, threads, and streaming, so the work is authorship: who the agent is, which methods it can reach, and how each one is described to it. Any app whose methods do something interesting can be projected into a conversation this way, often as its most compelling surface. This reference covers the whole feature — writing the agent spec, compiling it, and building the chat frontend.
|
|
4
|
+
when: Before authoring `src/interfaces/agent.md`, compiling `dist/interfaces/agent/`, or building an agent's chat UI.
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# Building Agent Interfaces
|
|
2
8
|
|
|
3
9
|
Guidance for designing conversational AI agents and their frontends. An agent interface pairs an LLM (with per-user-scoped/authenticated access to app methods as tools, handled by platform automatically) with a chat UI. The developer authors the agent's character in MSFM (`src/interfaces/agent.md`); you compile it into a system prompt and tool descriptions (`dist/interfaces/agent/`).
|
|
@@ -10,21 +16,32 @@ A good system prompt establishes who the agent is — personality, tone, judgmen
|
|
|
10
16
|
|
|
11
17
|
Short and opinionated beats long and comprehensive. "Sounds like a sharp, organized friend — brief by default" gives the model more to work with than a page of behavioral rules. Define constraints through character, not checklists. Let the model's judgment work.
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- The user's name and current role(s) at the time of message, if any, will be automatically appended to the end of every system prompt at runtime like:
|
|
19
|
+
Three things every compiled system prompt should carry, on top of the character:
|
|
20
|
+
|
|
21
|
+
**Parallel tool use.** The model won't batch independent calls unless told it can. Include a section like:
|
|
17
22
|
|
|
23
|
+
```markdown
|
|
24
|
+
## Tool Usage
|
|
25
|
+
|
|
26
|
+
When multiple tool calls are independent, make them all in a single turn.
|
|
27
|
+
Searching for three different products, or fetching two reference sites:
|
|
28
|
+
batch them instead of doing one per turn.
|
|
18
29
|
```
|
|
30
|
+
|
|
31
|
+
**Markdown and house style.** Unless the user says otherwise, tell the agent it can use markdown (the chat UI renders it) and to avoid em dashes and emojis.
|
|
32
|
+
|
|
33
|
+
**The current user is appended for you.** At runtime the platform appends the user's name and roles to the end of every system prompt, so don't write your own placeholder for it:
|
|
34
|
+
|
|
35
|
+
```markdown
|
|
19
36
|
## Current User
|
|
37
|
+
|
|
20
38
|
Name: Jane Smith
|
|
21
39
|
Roles: editor
|
|
22
40
|
```
|
|
23
|
-
- Unless the user specifies otherwise, always include a note that the agent can use markdown in responses (since the chat UI renders it) and should avoid using em dashes and emojis in its responses.
|
|
24
41
|
|
|
25
42
|
### Tool descriptions are the most important artifact
|
|
26
43
|
|
|
27
|
-
The system prompt says *who* the agent is. The tool descriptions say *what it can do*. A great tool description means the agent uses the tool correctly without explicit instruction. Do not be overly precise or micromanage. Your goal with tool descriptions is to provide context and
|
|
44
|
+
The system prompt says *who* the agent is. The tool descriptions say *what it can do*. A great tool description means the agent uses the tool correctly without explicit instruction. Do not be overly precise or micromanage. Your goal with tool descriptions is to provide context and framing — trust that the model is intelligent enough to fill in the gaps. Each `tools/*.md` file should cover:
|
|
28
45
|
|
|
29
46
|
- **When to use** this tool (and when NOT to — e.g. "NOT for marking complete, use toggle-todo")
|
|
30
47
|
- **Parameter guidance** beyond the schema — what makes a good value, when to include optional fields, what to skip
|
|
@@ -64,6 +81,8 @@ When building the `dist/interfaces/agent/`, consider the agent spec, as well as
|
|
|
64
81
|
|
|
65
82
|
**`agent.json`** — ties it together. Model config from frontmatter, paths to system prompt and tool files, optional `webInterfacePath`.
|
|
66
83
|
|
|
84
|
+
The exact shape of all three, and of the spec frontmatter they compile from, is in "The wiring" at the end of this document.
|
|
85
|
+
|
|
67
86
|
## Chat UI Design
|
|
68
87
|
|
|
69
88
|
When the agent has a web frontend (via `webInterfacePath`), the chat UI is a page within the web interface.
|
|
@@ -116,10 +135,12 @@ response.abort();
|
|
|
116
135
|
|
|
117
136
|
**Attachments:**
|
|
118
137
|
|
|
119
|
-
Send images or documents alongside a message. Upload
|
|
138
|
+
Send images or documents alongside a message. Upload to the app's file store first (see Files & Storage), then pass the returned URLs as the 4th argument:
|
|
120
139
|
|
|
121
140
|
```ts
|
|
122
|
-
|
|
141
|
+
// backend method mints a token; the browser uploads straight to storage
|
|
142
|
+
const token = await api.getUploadSlot({ filename: file.name, contentType: file.type });
|
|
143
|
+
const { url } = await platform.upload(token, file);
|
|
123
144
|
|
|
124
145
|
chat.sendMessage(threadId, "What's in this document?", {
|
|
125
146
|
onText: (delta) => setText((prev) => prev + delta),
|
|
@@ -128,7 +149,7 @@ chat.sendMessage(threadId, "What's in this document?", {
|
|
|
128
149
|
});
|
|
129
150
|
```
|
|
130
151
|
|
|
131
|
-
Images
|
|
152
|
+
Images are sent as vision input; documents have their text extracted server-side and included in context. Attachments are preserved in thread history.
|
|
132
153
|
|
|
133
154
|
**Key points:**
|
|
134
155
|
- `onText` and `onThinking` receive deltas (append to state, don't replace)
|
|
@@ -174,7 +195,7 @@ The first screen should invite conversation. A greeting from the agent, a few su
|
|
|
174
195
|
|
|
175
196
|
### Mobile
|
|
176
197
|
|
|
177
|
-
Chat is inherently mobile-friendly — lean into it. Pay attention to viewport sizing on mobile as the virtual keyboard changes the available height.
|
|
198
|
+
Chat is inherently mobile-friendly — lean into it. Pay attention to viewport sizing on mobile as the virtual keyboard changes the available height.
|
|
178
199
|
|
|
179
200
|
### Respect the brand
|
|
180
201
|
|
|
@@ -184,3 +205,90 @@ The chat UI uses the app's design system — colors, typography, voice from `@br
|
|
|
184
205
|
|
|
185
206
|
- Avoid designs that look like dated messaging apps from 2015
|
|
186
207
|
- Avoid robotic empty states ("Hello! I'm your AI assistant. How can I help you today?")
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
# The wiring
|
|
212
|
+
|
|
213
|
+
## Spec: `src/interfaces/agent.md`
|
|
214
|
+
|
|
215
|
+
The human-readable spec. Frontmatter contains structured fields; the prose body is the behavioral spec —
|
|
216
|
+
voice, personality, capabilities, rules — written in MSFM.
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
---
|
|
220
|
+
name: Todo Assistant
|
|
221
|
+
model: {"model": "claude-4-5-haiku", "temperature": 0.5, "maxResponseTokens": 16000}
|
|
222
|
+
description: Conversational agent that helps users manage their to-do list.
|
|
223
|
+
---
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Frontmatter fields:
|
|
227
|
+
|
|
228
|
+
- `name` — agent display name
|
|
229
|
+
- `model` — JSON string with `model` (MindStudio model ID), `temperature`, `maxResponseTokens`, and
|
|
230
|
+
optional `config` (model-specific settings like `reasoning`, `tools`, etc.). Ask `askMindStudioSdk`
|
|
231
|
+
for available model IDs and their config options — MindStudio's ids don't match vendor ids, so treat
|
|
232
|
+
any id in this document's examples as illustrative rather than current. The user's UI has a visual picker for changing it later, so only validate
|
|
233
|
+
the model when you're setting it; if the value changes afterwards, assume it's correct.
|
|
234
|
+
- `description` — one-liner for agent card/listing
|
|
235
|
+
|
|
236
|
+
The prose body contains sections like Voice & Personality, Capabilities, Behavior — whatever structure
|
|
237
|
+
serves the agent's character. This is compiled into the system prompt and tool descriptions.
|
|
238
|
+
|
|
239
|
+
## Compiled Output: `dist/interfaces/agent/`
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
dist/interfaces/agent/
|
|
243
|
+
├── agent.json ← config the platform reads
|
|
244
|
+
├── system.md ← compiled system prompt
|
|
245
|
+
└── tools/
|
|
246
|
+
├── createTodo.md ← rich tool description per method
|
|
247
|
+
├── listTodos.md
|
|
248
|
+
└── ...
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Config (`agent.json`)
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"agent": {
|
|
256
|
+
"model": "claude-4-5-haiku",
|
|
257
|
+
"temperature": 0.5,
|
|
258
|
+
"maxTokens": 16000,
|
|
259
|
+
"systemPrompt": "system.md",
|
|
260
|
+
"tools": [
|
|
261
|
+
{ "method": "create-todo", "description": "tools/createTodo.md" },
|
|
262
|
+
{ "method": "list-todos", "description": "tools/listTodos.md" }
|
|
263
|
+
],
|
|
264
|
+
"webInterfacePath": "/chat"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**The token-limit field is renamed during compilation.** The spec frontmatter calls it
|
|
270
|
+
`maxResponseTokens`; the compiled `agent.json` calls it `maxTokens`. Same value, two names — carry it
|
|
271
|
+
across rather than copying the key.
|
|
272
|
+
|
|
273
|
+
| Field | Description |
|
|
274
|
+
|-------|-------------|
|
|
275
|
+
| `model` | MindStudio model ID. Comes from the spec frontmatter; look it up with `askMindStudioSdk` rather than guessing |
|
|
276
|
+
| `temperature` | Model temperature |
|
|
277
|
+
| `maxTokens` | Max response tokens (the spec's `maxResponseTokens`) |
|
|
278
|
+
| `systemPrompt` | Relative path to the compiled system prompt markdown file |
|
|
279
|
+
| `tools` | Array of tool entries — `method` references a method `id` from the manifest, `description` is a relative path to a markdown file with rich tool docs (when to use, examples, edge cases, parameter guidance) |
|
|
280
|
+
| `webInterfacePath` | Optional. If the app has a web interface with a chat page, this path tells the IDE where to show the preview. Otherwise the agent is accessed via API. |
|
|
281
|
+
|
|
282
|
+
Declare it in `mindstudio.json`:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{ "type": "agent", "path": "dist/interfaces/agent/agent.json" }
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Auth
|
|
289
|
+
|
|
290
|
+
Agent chat runs as the **authenticated user**, not as a system role — tool calls carry that user's
|
|
291
|
+
roles, so a method gated with `auth.requireRole` behaves exactly as it would if the user had called it
|
|
292
|
+
from the web frontend. That's what makes exposing real methods safe; it's also why role restrictions
|
|
293
|
+
belong in the tool descriptions, so the agent can decline gracefully instead of surfacing a rejection.
|
|
294
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Data Sources
|
|
3
|
+
what: A managed retrieval system for document corpora, not a bolt-on keyword search. Documents are chunked and embedded, candidate hits are re-scored by a reranking model, semantic search runs alongside exact keyword matching so part numbers and error codes still land, images inside documents are described by a vision model and made searchable, and every hit returns a citation that links to its source page. Chunking and embedding settings are versioned — a rebuilt corpus can be compared against the live one and promoted without downtime. All of that applies to unstructured documents queried by meaning, and to nothing else.
|
|
4
|
+
when: Only for unstructured documents queried by meaning — "find the clause about early termination". Structured data belongs in `db`: if the question can be expressed as a filter, it is not a search problem, and a `WHERE` clause is faster, cheaper and exact. Load before defining or querying a data source.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Data Sources (Search Over Documents)
|
|
8
|
+
|
|
9
|
+
Per-app searchable document corpora: upload documents, ask in plain language, get back the passages
|
|
10
|
+
that answer it with a citation to the source.
|
|
11
|
+
|
|
12
|
+
**Most apps should not use one — check this before reaching for it.** Structured data (rows with
|
|
13
|
+
fields you filter on) belongs in `db`; a `WHERE` clause is faster, cheaper and exact. A data source
|
|
14
|
+
earns its cost only for **unstructured documents queried by meaning**. "Find the clause about early
|
|
15
|
+
termination" is a data source. "Find contracts signed after March" is a `db` query. If the question
|
|
16
|
+
can be expressed as a filter, it is not a search problem.
|
|
17
|
+
|
|
18
|
+
Don't use one when: the data is structured (`db`), you only need to store files (`files` — nobody is
|
|
19
|
+
searching the contents), the requirement is exact lookup by identifier, or the corpus is a handful of
|
|
20
|
+
short docs that fit in a prompt.
|
|
21
|
+
|
|
22
|
+
## Behaviour (read before the API)
|
|
23
|
+
|
|
24
|
+
- **One corpus shared across dev and prod** — like a file store, not a table. No dev copy, no
|
|
25
|
+
per-release isolation. A document added while building is already live.
|
|
26
|
+
- **Scenarios never reset a data source.** Don't write `clear()`-style reset helpers.
|
|
27
|
+
- **Re-adding the same bytes is free** — content-addressed, so ingest scripts are safe to re-run.
|
|
28
|
+
- **Ingest is async.** `add()` returns once queued; poll `documents()`, or use `--wait` from the CLI.
|
|
29
|
+
- **Reprocessing costs real money**, so changing how a corpus is built is always explicit.
|
|
30
|
+
- **Limits apply**: 25 data sources per app, 5,000 documents per source, 10,000 chunks per
|
|
31
|
+
document, 300 searches/minute. Well clear of normal use — but **source names must be fixed, not
|
|
32
|
+
computed per user or per request**, since referencing one creates it. Partition inside a source
|
|
33
|
+
with a metadata filter instead.
|
|
34
|
+
|
|
35
|
+
## Defining and searching
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { dataSources } from '@mindstudio-ai/agent';
|
|
39
|
+
export const Policies = dataSources.defineDataSource('policies'); // lowercase [a-z0-9_-], ≤64
|
|
40
|
+
|
|
41
|
+
const { results } = await Policies.search('what are the payment terms?', { topK: 5 });
|
|
42
|
+
const context = results.map((r) => r.text).join('\n\n');
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Hits are `{ score, text, citation }` with
|
|
46
|
+
`citation: { documentId, filename, pageNumber, chunkIndex, headingPath, boundingBox?, url }`, plus
|
|
47
|
+
`retrievalRank`/`retrievalScore` — the position before reranking, so you can show what reranking did.
|
|
48
|
+
|
|
49
|
+
**Always render the citation.** `citation.url` is a stable on-domain link — put it in an `<a href>`
|
|
50
|
+
beside the answer. Retrieval is approximate; a user who can click through can judge for themselves.
|
|
51
|
+
An answer with no citation is an assertion.
|
|
52
|
+
|
|
53
|
+
Created on first use, so searching a source the build hasn't populated returns no results rather than
|
|
54
|
+
throwing. `search` options: `topK` (default 5, max 50), `scoreThreshold`, `rerank`, `hybrid`.
|
|
55
|
+
|
|
56
|
+
Search is deterministic for a fixed corpus and configuration, so eval sets and regression checks are
|
|
57
|
+
meaningful — key them on `(documentId, chunkIndex)` rather than on chunk text.
|
|
58
|
+
|
|
59
|
+
**Debugging retrieval.** Two opt-in options, neither of which changes the results or their order:
|
|
60
|
+
`explain: true` adds `explain.{dense, lexical, matchedVia}` (which half of hybrid found each hit;
|
|
61
|
+
costs two extra round trips), and `expand: 1` adds `neighbors.{before, after}` for surrounding
|
|
62
|
+
context. When a document never comes back at all, `Policies.stats()` reports the config actually in
|
|
63
|
+
effect and `Policies.chunks(documentId)` shows exactly how it was split.
|
|
64
|
+
|
|
65
|
+
**Configuration is not declared in code** — chunking and embedding settings live on the corpus and are
|
|
66
|
+
set with the CLI, so code and reality can't drift.
|
|
67
|
+
|
|
68
|
+
## Loading documents — normally at build time, from the CLI
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
mindstudio-prod datasources add --source policies --wait docs/*.pdf
|
|
72
|
+
mindstudio-prod datasources search --source policies "what are the payment terms?" # sanity-check
|
|
73
|
+
mindstudio-prod datasources delete --source policies # whole source; --source is required, never defaulted
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`--wait` blocks until processing finishes and exits non-zero on failure. Also `datasources list`,
|
|
77
|
+
`status` (per-document state + ingest errors), `rm --document <id>`. `--help` for flags.
|
|
78
|
+
|
|
79
|
+
**Seeding a test corpus:** scenarios don't touch data sources, so load fixtures with the same command
|
|
80
|
+
in a setup script — `datasources add --source <slug> --wait fixtures/*.pdf`. Re-running is free, so
|
|
81
|
+
it needs no guard.
|
|
82
|
+
|
|
83
|
+
Use the SDK's `add()` only when *users* upload documents that must become searchable:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
await Policies.add(buffer, { filename: 'policy.pdf', contentType: 'application/pdf' });
|
|
87
|
+
const docs = await Policies.documents(); // 'processing' | 'done' | 'error'
|
|
88
|
+
await Policies.remove(documentId);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Formats: pdf, docx, pptx, xlsx, odt, rtf, epub, images, txt, md, json, csv, tsv, log, html.
|
|
92
|
+
|
|
93
|
+
## Answering from results
|
|
94
|
+
|
|
95
|
+
Retrieve → join passages as context → have a model answer *from that context* → render citations.
|
|
96
|
+
Never paste raw chunks at the user; they're fragments. For agentic flows, give the model `search` as a
|
|
97
|
+
tool so it can query repeatedly and refine, rather than retrieving once up front.
|
|
98
|
+
|
|
99
|
+
## Tuning — two kinds of setting
|
|
100
|
+
|
|
101
|
+
| Kind | Settings | Cost |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| **Free** (ranking) | `--rerank`, `--rerank-model`, `--hybrid`, `--top-k` | none, next search |
|
|
104
|
+
| **Rebuild** (how docs become vectors) | `--max-chars`, `--min-chars`, `--drop-blocks`, `--contextual`, `--describe-images`, `--embedding-model`, `--extraction-model` | every document reprocessed |
|
|
105
|
+
|
|
106
|
+
Images inside documents are described by a vision model and the description substituted into the
|
|
107
|
+
searchable text (`--describe-images`, on by default) — without it a chart contributes nothing to
|
|
108
|
+
search at all. Documents with no images cost nothing.
|
|
109
|
+
|
|
110
|
+
`rerank` and `hybrid` default on and are usually right — reranking is the biggest quality lever, and
|
|
111
|
+
hybrid is what finds part numbers, error codes and proper nouns a semantic model never learned. Both
|
|
112
|
+
are also per-query (`search(q, { rerank: false })`) for a latency-sensitive path.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
mindstudio-prod datasources config --source policies # show
|
|
116
|
+
mindstudio-prod datasources config --source policies --top-k 8 # free, immediate
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**A rebuild-class change on a populated corpus is rejected** — you're told what it would invalidate
|
|
120
|
+
and what it costs. To make it, build a new version alongside the live one:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
mindstudio-prod datasources revectorize --source policies --max-chars 900 --wait
|
|
124
|
+
mindstudio-prod datasources search --source policies --candidate "payment terms" # compare
|
|
125
|
+
mindstudio-prod datasources promote --source policies # go live
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Search serves the current version throughout, so nothing degrades while the new one builds.
|
|
129
|
+
`datasources drop` discards an unwanted candidate.
|
|
130
|
+
|
|
131
|
+
For anything deeper on the SDK, ask `askMindStudioSdk` rather than guessing at an API.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Inbound Email
|
|
3
|
+
what: The app has its own email address, and mail sent to it runs a method. Every address on the app's subdomain routes to one handler, so `support@`, `receipts@` and `anything@` all arrive without registering anything — branch on the recipient in code. Attachments arrive as CDN URLs the platform has already uploaded, threading headers come through intact so replies land in the same conversation, and a verified custom domain both receives and sends under the app's own brand. "Forward a receipt and the app files it" is a real feature that costs one method.
|
|
4
|
+
when: Before writing an email-handler method, adding an `email` interface, or promising anything about what happens when a user emails the app.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Inbound Email Interfaces
|
|
8
|
+
|
|
9
|
+
Inbound email triggers. Each app has **one** email-handler method; the platform routes all inbound
|
|
10
|
+
mail destined for the app — across any of its address tiers — to that method.
|
|
11
|
+
|
|
12
|
+
The addresses themselves are configured at the project level by the user through the Remy platform.
|
|
13
|
+
Your job is the `interface.json` and the method that handles the mail, not domain registration or MX
|
|
14
|
+
records.
|
|
15
|
+
|
|
16
|
+
## Address tiers
|
|
17
|
+
|
|
18
|
+
Three tiers, all delivered to the same handler method. The new tiers are catchall (no localpart
|
|
19
|
+
registration); the legacy tier is specific-localpart and frozen for new apps.
|
|
20
|
+
|
|
21
|
+
| Tier | Address | How it's set up |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Platform subdomain (default) | `*@<custom_subdomain>.madewithremy.com` | Automatic the moment the app has a `custom_subdomain` set. Every address on that subdomain delivers to the handler. |
|
|
24
|
+
| Custom domain | `*@<their-domain>` | The user adds a domain in the dashboard's email-domains settings and points one MX record at `mx.msagent.ai`. Not something the agent provisions. |
|
|
25
|
+
| Legacy `mindstudio-hooks.com` | `<name>@mindstudio-hooks.com` | Existing apps only — frozen for new apps. Don't recommend it; treat as read-only history. |
|
|
26
|
+
|
|
27
|
+
Because the new tiers are catchall, `to` carries an arbitrary localpart. Methods that need to branch on
|
|
28
|
+
it should read `input.to` (e.g. `if (input.to.startsWith('support@')) ...`). This is what makes
|
|
29
|
+
per-purpose addresses free: you don't register `support@` anywhere, you just check for it.
|
|
30
|
+
|
|
31
|
+
A verified custom domain (and the app's `madewithremy.com` subdomain) also **sends** outbound mail, not
|
|
32
|
+
just receives — `sendEmail` picks the app's own-brand sender automatically, configured in the
|
|
33
|
+
dashboard's **Email** settings.
|
|
34
|
+
|
|
35
|
+
## Config (`interface.json`)
|
|
36
|
+
|
|
37
|
+
The top-level key must match the interface type (`email`):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"email": {
|
|
42
|
+
"method": "handle-inbound-email",
|
|
43
|
+
"approvedSenders": ["billing@vendor.com", "*@trusted-partner.com"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`approvedSenders` is optional. When set, only senders matching an exact address or `*@domain.com`
|
|
49
|
+
wildcard reach the method; everything else is rejected by the platform with `400 invalid_sender` before
|
|
50
|
+
the method runs (silently — the sender isn't bounced). Matching is case-insensitive. The same list
|
|
51
|
+
applies uniformly across all three address tiers.
|
|
52
|
+
|
|
53
|
+
Declare it in `mindstudio.json`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{ "type": "email", "path": "dist/interfaces/email/interface.json" }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Input shape
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
{
|
|
63
|
+
to: string; // full recipient address; localpart is arbitrary on catchall tiers
|
|
64
|
+
from: string; // bare sender address, extracted from "Name <a@b>" form
|
|
65
|
+
fromName: string | null; // sender display name, or null
|
|
66
|
+
subject: string; // 'No Subject' if missing
|
|
67
|
+
message: string; // plain-text body, falls back to HTML if text is missing; 'No Body' if neither was sent
|
|
68
|
+
html: string; // HTML body, or '' when text-only
|
|
69
|
+
attachments: string[]; // CDN URLs — already uploaded by the platform
|
|
70
|
+
messageId: string | null; // this email's Message-ID, angle-bracketed (<id@host>)
|
|
71
|
+
inReplyTo: string | null; // Message-ID this email is replying to, if any
|
|
72
|
+
references: string[]; // prior Message-IDs in the thread (angle-bracketed); [] if none
|
|
73
|
+
replyTo: string | null; // Reply-To address — reply here, not `from`, when set
|
|
74
|
+
cc: string[]; // Cc recipient addresses
|
|
75
|
+
date: string | null; // original send time, ISO-8601
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Replying in thread
|
|
80
|
+
|
|
81
|
+
Replies go out through the SDK's `sendEmail` action (its full parameter list is in the SDK actions
|
|
82
|
+
reference in your system prompt). Set `inReplyTo` to the incoming `messageId` and `references` to
|
|
83
|
+
`[...references, messageId]`. Send to `replyTo` when it's set, otherwise `from`.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
await mindstudio.sendEmail({
|
|
87
|
+
to: input.replyTo ?? input.from,
|
|
88
|
+
subject: `Re: ${input.subject}`,
|
|
89
|
+
body: reply,
|
|
90
|
+
inReplyTo: input.messageId ?? undefined,
|
|
91
|
+
references: input.messageId
|
|
92
|
+
? [...input.references, input.messageId]
|
|
93
|
+
: input.references,
|
|
94
|
+
cc: input.cc, // reply-all
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`sendEmail` returns `{ recipients, cc, bcc, from }` — who it sent to and the sender used. It does
|
|
99
|
+
**not** return the sent message's own `Message-ID`, so thread off *inbound* mail, never off messages
|
|
100
|
+
you sent.
|
|
101
|
+
|
|
102
|
+
## Attachments and size limits
|
|
103
|
+
|
|
104
|
+
`attachments[]` is an array of CDN URLs — the platform has already received and uploaded the files.
|
|
105
|
+
Fetch them server-side via the URL when you need the bytes; pass them through as URLs to UI or
|
|
106
|
+
downstream services.
|
|
107
|
+
|
|
108
|
+
Max inbound message size is 25 MB total (including all attachments). Oversized messages are rejected by
|
|
109
|
+
the platform before the method runs.
|
|
110
|
+
|
|
111
|
+
## Auth
|
|
112
|
+
|
|
113
|
+
Methods invoked through this interface run with `auth.roles: ['system']` — the platform is calling, not
|
|
114
|
+
a user session, so there's no user to impersonate. Use `auth.requireRole('system')` to gate methods that
|
|
115
|
+
should only be reachable via email. The auth reference in your system prompt covers the system role in
|
|
116
|
+
full.
|