@mindstudio-ai/remy
v0.1.319
Published
Remy coding agent
Readme
Remy
A spec-building and coding agent for building apps.
Remy helps users design, spec, build, and iterate on app projects. It runs locally in a terminal or as a headless subprocess in the sandbox. It has tools for reading/writing specs and code, running shell commands, searching code, prompting users with structured forms, and (in the sandbox) TypeScript language server integration. LLM calls are routed through the Remy platform for billing and model routing.
Quick Start
# Make sure you're logged in (shares credentials with @mindstudio-ai/agent)
mindstudio login
# Navigate to your project
cd my-app
# Run remy
npx remyUsage
$ remy [options]
Options:
--api-key <key> API key (overrides env/config)
--base-url <url> Platform API base URL
--model <id> Model ID (defaults to org's default model)
--headless Run in headless mode (stdin/stdout JSON protocol)
--lsp-url <url> LSP sidecar URL (enables LSP tools when set)Slash Commands
| Command | Description |
|---------|-------------|
| /clear | Clear conversation history and start a fresh session |
| Escape | Cancel the current turn (while agent is running) |
Session Persistence
Remy saves conversation history to .remy-session.json in the working directory after each turn and before blocking on external tools. On restart, it picks up where you left off. Use /clear to start fresh.
Tools
The full tool set is always available — it is deliberately invariant (no onboarding-state gating) so the provider's tools-tier cache prefix stays identical across turns and sessions. The sections below are thematic groupings only.
Common Tools
| Tool | Description |
|------|-------------|
| setProjectOnboardingState | Advance the onboarding flow (intake → building → buildComplete → onboardingFinished) |
| setProjectName | Set the project name |
| promptUser | Ask the user structured questions (form or inline display) |
| confirmDestructiveAction | Confirm a destructive or irreversible action with the user |
| askMindStudioSdk | MindStudio SDK expert — answers questions about actions, models, connectors, and configuration (sub-agent) |
| fetchUrl | Fetch a URL and return its contents |
| searchGoogle | Search Google and return results |
| visualDesignExpert | Visual design expert for fonts, colors, palettes, gradients, layouts, imagery, and icons (sub-agent) |
| productVision | Owns the product roadmap — creates/updates/deletes roadmap items in src/roadmap/ (sub-agent) |
| codeSanityCheck | Quick readonly sanity check on architecture and package choices before building (sub-agent) |
| writeBuildOverview | Generate/refresh the Build Overview (src/overview.html) — a single-page plain-language reference of everything the app contains; you author the copy, the design expert lays it out |
Spec Tools
Available in all onboarding states. Used for authoring and editing MSFM specs in src/.
| Tool | Description |
|------|-------------|
| readSpec | Read a spec file with line numbers (paths must start with src/) |
| writeSpec | Create or overwrite a spec file (creates parent dirs) |
| editSpec | Heading-addressed edits (replace, insert, delete by heading path) |
| listSpecFiles | List all files in the src/ directory tree |
Code Tools
| Tool | Description |
|------|-------------|
| readFile | Read a file with line numbers |
| writeFile | Create or overwrite a file (creates parent dirs) |
| editFile | Targeted string replacement (must be unique match) |
| bash | Run a shell command |
| grep | Search file contents |
| glob | Find files by pattern |
| listDir | List directory contents |
| editsFinished | Signal that file edits are complete for live preview |
| runScenario | Run a scenario to seed the dev database with test data |
| runMethod | Run a method in the dev environment and return the result |
| testJewel | Run a method's jewel against a test input and return its pair record |
| screenshot | Capture a screenshot of the app preview and get a description of what's on screen |
| runAutomatedBrowserTest | Run an automated browser test against the live preview with DOM snapshots and interaction execution (sub-agent) |
LSP Tools (sandbox only)
Available when --lsp-url is passed.
| Tool | Description |
|------|-------------|
| lspDiagnostics | Type errors and warnings for a file, with suggested quick fixes |
| restartProcess | Restart a managed sandbox process (e.g., dev server after npm install) |
Development & Publishing Tools
| Tool | Description |
|------|-------------|
| presentPublishPlan | Present a publish changelog for user approval (streams content) |
Tool Streaming
Tools can opt into streaming via a streaming config on the tool definition:
- Content streaming (writeSpec, writeFile, presentPublishPlan): Streams
tool_input_deltaevents with progressive content as the LLM generates tool arguments. Tools can provide atransformfunction to customize the streamed output (e.g., writeSpec/writeFile compute a progressive diff). - Input streaming (promptUser): Streams progressive
tool_startevents withpartial: trueas structured input (like a questions array) builds up. - No streaming (all other tools):
tool_startfires once when the complete tool arguments are available.
Streaming is driven by tool_input_delta (Anthropic) or tool_input_args (Gemini) SSE events from the platform.
Architecture
User input
→ Agent loop (src/agent.ts)
→ POST /_internal/v2/agent/remy/chat (SSE stream)
← text, thinking, tool_input_delta, tool_input_args, tool_use events
→ Execute tools locally in parallel
→ External tools wait for sandbox response
→ Sub-agent tools run their own nested LLM loops
→ Send tool results back
→ Loop until done
→ Save session to .remy-session.jsonThe agent core (src/agent.ts) is a pure async function with no UI dependencies. The TUI (src/tui/) is an Ink + React layer on top. Headless mode (src/headless.ts) provides the same agent over a stdin/stdout JSON protocol for the sandbox.
Sub-Agents
Some tools are backed by sub-agents — they run their own nested LLM loops with specialized system prompts and tool subsets. Sub-agent events are tagged with parentToolId so the caller can associate them with the parent tool call.
| Sub-Agent | Tool Name | Location |
|-----------|-----------|----------|
| SDK Consultant | askMindStudioSdk | src/subagents/sdkConsultant/ |
| Design Expert | visualDesignExpert | src/subagents/designExpert/ |
| Product Vision | productVision | src/subagents/productVision/ |
| Code Sanity Check | codeSanityCheck | src/subagents/codeSanityCheck/ |
| Browser Automation | runAutomatedBrowserTest | src/subagents/browserAutomation/ |
External Tools
Some tools are resolved by the sandbox rather than executed locally. Remy emits tool_start, then waits for the sandbox to send back a tool_result via stdin:
promptUser— renders a form or inline prompt, blocks until user respondssetProjectOnboardingState— advances the onboarding flowsetProjectName— sets the project nameconfirmDestructiveAction— renders a confirmation dialogpresentPublishPlan— renders a full-screen changelog for user approval
Project Structure
src/
index.tsx CLI entry point
agent.ts Core tool-call loop (pure async, no UI)
api.ts SSE streaming client for platform API
types.ts Shared types (AgentEvent, StdinCommand, etc.)
headless.ts stdin/stdout JSON protocol for sandbox
session.ts .remy-session.json persistence
config.ts API key/URL resolution
errors.ts Friendly error message mapping
statusWatcher.ts Background status label polling
parsePartialJson.ts Partial JSON parser for streaming tool input
logger.ts Structured logging
prompt/
index.ts System prompt builder (static prefix + small dynamic tail)
static/ Behavioral instruction fragments
identity.md
intake.md
authoring.md
coding.md
instructions.md
team.md
lsp.md
projectContext.ts Reads project root, app identity, plan status at runtime
compiled/ Platform docs distilled for agent consumption
sources/ Prompt source material (hand-maintained)
tools/
index.ts Tool registry with streaming config interface
_helpers/
diff.ts Unified diff generator
lsp.ts LSP sidecar HTTP client
common/ Always-available tools
promptUser.ts
confirmDestructiveAction.ts
setProjectOnboardingState.ts
setProjectName.ts
fetchUrl.ts
searchGoogle.ts
spec/ Spec tools
readSpec.ts
writeSpec.ts
editSpec.ts
listSpecFiles.ts
clearSyncStatus.ts
presentSyncPlan.ts
presentPublishPlan.ts
presentPlan.ts
_helpers.ts Heading resolution, path validation
code/ Code tools (file editing, shell, search)
readFile.ts
writeFile.ts
editFile/
index.ts
_helpers.ts
bash.ts
grep.ts
glob.ts
listDir.ts
editsFinished.ts
runScenario.ts
runMethod.ts
screenshot.ts
lspDiagnostics.ts
restartProcess.ts
subagents/
runner.ts Sub-agent LLM loop runner
sdkConsultant/ MindStudio SDK expert
designExpert/ Visual design expert
productVision/ Product roadmap manager
codeSanityCheck/ Architecture sanity checker
browserAutomation/ Automated browser testing
tui/ Interactive terminal UI (Ink + React)
App.tsx
InputPrompt.tsx
MessageList.tsx
ThinkingBlock.tsx
ToolCall.tsxProject Instructions
Remy automatically loads project-level agent instructions on startup. It checks for these files in order (first match wins):
CLAUDE.md, claude.md, .claude/instructions.md, AGENTS.md, agents.md, .agents.md, COPILOT.md, copilot.md, .copilot-instructions.md, .github/copilot-instructions.md, REMY.md, remy.md, .cursorrules, .cursorules
Headless Mode
Run remy --headless for programmatic control via newline-delimited JSON. This is how the sandbox C&C server runs remy as a managed child process.
Protocol Overview
The headless IPC protocol uses request correlation and a unified response pattern:
- Every stdin command includes a caller-provided
requestId - Every stdout response to a command includes the same
requestId - System events (lifecycle, shutdown) never have a
requestId - Every command ends with exactly one
completedevent:{event:"completed", requestId, success, error?} - Messages sent while a turn is running are queued. When the turn ends, all contiguous queued user messages and background results are delivered together as one merged turn: the first queued message's
requestIdbecomes the turn's primary id (stamped onturn_startedand all streaming events), each absorbed message echoes its ownuser_messagewith its originalrequestIdandqueued: true, and at turn end the primarycompletedis emitted first, followed immediately by onecompleted {…same outcome, absorbed: true}per other absorbedrequestId. Automated-action (@@automated::…@@) messages and chain steps never merge — they always run one turn each. - A queued user message can be promoted to ASAP delivery via
setQueuedDelivery. ASAP items are pulled into the running turn at its next tool boundary (injected as plain user messages — no abort, no restart), echouser_messagewithqueued: trueand their ownrequestId, and get a{…, absorbed: true}completed with the turn's outcome at turn end. Promotion deliberately jumps ahead of anything else in the queue, including chain steps. If the turn ends before injection, the tag is ignored and the item drains in normal FIFO order. - Background tool completions have three delivery classes (per-tool
backgroundNotifyon the tool definition).wake(default): the result is queued as abackground_resultsmessage and may start a turn when the agent is idle.passive(e.g.specSync): the result never enters the queue and never wakes the agent — it parks in a persisted holding pen and rides the next real turn as a hiddenbackground_resultsentry (so it never appears inqueuedMessages, never affects queue-derived busy state, and never triggers resume-on-restart).silent(e.g.compactConversation): the tool block is updated for the UI and the model is never told — its outcome reaches the model by another mechanism. All classes emittool_background_completeimmediately.specSyncalso takes arefreshBuildOverviewflag (set post-deploy / post-milestone, prompted via the publish flow): the run gains thewriteBuildOverviewtool and re-authors the Build Overview from the freshly-reconciled spec — the design-expert render runs foreground within specSync's already-detached run, never nested-background. - Compaction lifecycle rides
compaction_started {blocking}/compaction_complete {error?}system events. Every compaction also renders as a normalcompactConversationtool call. The model-invoked tool is a real background tool (its block completes viatool_background_complete; the summary lands inbackgroundResult). User (/compact) and gate-initiated compactions instead get a synthesized UI-only foreground block in history — the user is actively waiting on these, so they follow the normal foreground lifecycle:tool_startwith no result, then a (late)tool_done {result, isError}carrying the summary or the error when the compaction finishes. Either way the block is excluded from every API payload — the model receives the summary via the checkpoint prefix instead. Every turn passes a compaction gate: it waits for any in-flight compaction and applies the finished checkpoint before running, so no turn ever bills at the uncompacted context. Messages received while a compaction is in flight are queued exactly like mid-turn messages (they appear inqueuedMessagesand drain — with merged-turn semantics — when the compaction completes, on success and failure alike). Acompactcommand received mid-turn queues too: it's acked immediately withcompleted {success: true, queued: true}(duplicate clicks coalesce onto the queued item), appears inqueuedMessagesas a removable@@automated::compact@@user item, and runs as its own drain step when the turn ends — everything queued behind it runs against the compacted history. - The caller distinguishes command responses from system events with a single check:
if (msg.requestId)
This enables a simple promise-based RPC layer: send a command with a unique ID, store a pending promise keyed by that ID, resolve it when you see completed with the matching ID.
Input Actions (stdin)
Send JSON commands, one per line. Every command should include a requestId.
message
Send a user message to the agent.
{"action": "message", "requestId": "r1", "text": "fix the bug in auth.ts", "onboardingState": "onboardingFinished"}Fields:
requestId— caller-provided correlation ID (echoed on all response events)text— the user message (required unlessrunCommandis set)onboardingState— the project's onboarding phase, reflected in the system prompt's dynamic tail and plan-status behavior. One of:intake,building,buildComplete,onboardingFinished(default:onboardingFinished)attachments— array of{ url, extractedTextUrl? }for file attachmentsrunCommand— triggers a built-in action prompt ("sync","publish","buildFromInitialSpec")
When runCommand is set, the message text is replaced with a built-in prompt and the user message is marked as hidden in conversation history (sent to the LLM but not shown in the UI).
tool_result
Send the result of an external tool back to the agent. Fire-and-forget — no completed event is emitted.
{"action": "tool_result", "id": "toolu_abc123", "result": "ok"}get_history
Return the full conversation history.
{"action": "get_history", "requestId": "r2"}Messages with hidden: true were generated by runCommand actions and should not be displayed in the UI.
The response also includes queuedMessages — the current pending-queue snapshot (possibly empty) — so a client can render the queue immediately on connect/reconnect. Live changes after that arrive via queue_changed.
cancel
Cancel the current turn. The cancel command gets completed(success:true); any queued messages are flushed and returned on it as cancelledMessages. The in-flight message command (if any) gets its own completed(success:false, error:"cancelled").
{"action": "cancel", "requestId": "r3"}cancelQueued
Cancel pending queued messages without touching the in-flight turn. Omit id to cancel all queued user messages, or pass id (the requestId of a queued message) to cancel just that one. Only user messages are cancellable — chained and background messages are part of a system chain and are never removed. Responds with completed(success:true, cancelledQueued:[...]) listing what was removed; a queue_changed event also fires with the updated queue.
{"action": "cancelQueued", "requestId": "r6", "id": "r2"}setQueuedDelivery
Change a queued message's delivery semantics: "asap" (inject into the running turn at its next tool boundary) or "afterTurn" (default — wait for the turn to end). id is the requestId of a queued message. Only plain queued user messages qualify; automated-action messages and chain/background items respond completed(success:false, error:"message not found or not promotable") — as does an item already consumed by the running turn. On success a queue_changed event carries the updated snapshot (each item's delivery field rides on it).
{"action": "setQueuedDelivery", "requestId": "r7", "id": "r2", "delivery": "asap"}clear
Clear conversation history and delete the session file.
{"action": "clear", "requestId": "r4"}changeModels
Change per-agent model picks without clearing history. models is a sparse map keyed by agent identifier (parent, visualDesignExpert, …); omit it (or send {}) to reset every agent to server defaults. Takes effect on the next turn. Rejected with completed(success:false, error:"cannot change models while a turn is running") if a turn is in flight — cancel first, then retry. Responds with models_changed. To start a fresh conversation on a different model, send clear then changeModels.
{"action": "changeModels", "requestId": "r5", "models": {"parent": "gemini-3.1-pro"}}Output Events (stdout)
Events are emitted as newline-delimited JSON. Command responses include requestId; system events do not.
System Events
| Event | Fields | Description |
|-------|--------|-------------|
| ready | | Headless mode initialized, ready for input |
| session_restored | messageCount | Previous session loaded |
| queue_changed | queuedMessages | Queue contents changed (any push/shift/drain/cancel/retag). Carries the full current snapshot — an empty array when the queue drains. Items carry delivery: "asap" when promoted (absent = after-turn). The live queue-state signal; for the initial snapshot on connect/reconnect, read queuedMessages from get_history. |
| stopping | | Shutdown initiated |
| stopped | | Shutdown complete |
Command Responses
All command responses include the requestId from the originating command.
| Event | Fields | Description |
|-------|--------|-------------|
| text | text, parentToolId? | Streaming text chunk |
| thinking | text, parentToolId? | Agent's internal reasoning |
| user_message | text, attachments?, queued?, hidden? | Echo of a user message entering the turn. Queue-delivered messages (including ASAP items injected mid-turn) carry queued: true and their own original requestId (a merged turn emits one per absorbed message); idle sends echo with the turn's requestId and no queued flag. hidden: true marks internal entries (e.g. passive background results) that should not render. |
| tool_start | id, name, input, partial?, parentToolId? | Tool execution started. partial: true means more tool_start events will follow for this id (progressive input streaming). |
| tool_input_delta | id, name, result, parentToolId? | Progressive tool content (streaming tools only) |
| tool_done | id, name, result, isError, parentToolId? | Tool execution completed. For a background tool this carries the synchronous ack; for a synthesized user/gate compaction block it arrives late, when the compaction finishes, carrying the summary (or error). |
| tool_background_complete | id, name, result, parentToolId? | A background tool's detached work finished; result belongs in the block's backgroundResult. Emitted for all backgroundNotify classes. |
| status | message | Contextual status label (e.g., "Writing files...") |
| error | error | Error message (may precede completed) |
| history | messages | Response to get_history |
| session_cleared | | Response to clear |
| models_changed | models?, modelSurfaces, allowedModelsByType | Response to changeModels |
| completed | success, error?, absorbed? | Terminal event — exactly one per command. For a merged turn, the primary requestId's completed comes first, then one {absorbed: true} completed per other absorbed requestId with the same outcome — consumers should resolve their pending request but skip turn-lifecycle handling for absorbed terminals. |
Example Session
← {"event":"ready"}
← {"event":"session_restored","messageCount":5}
→ {"action":"message","requestId":"r1","text":"fix the login bug"}
← {"event":"thinking","requestId":"r1","text":"Let me look at the auth code..."}
← {"event":"text","requestId":"r1","text":"I'll fix the login validation."}
← {"event":"tool_start","requestId":"r1","id":"tc_1","name":"editFile","input":{"path":"auth.ts","old":"...","new":"..."}}
← {"event":"tool_done","requestId":"r1","id":"tc_1","name":"editFile","result":"OK","isError":false}
← {"event":"completed","requestId":"r1","success":true}
→ {"action":"get_history","requestId":"r2"}
← {"event":"history","requestId":"r2","messages":[...]}
← {"event":"completed","requestId":"r2","success":true}
→ {"action":"cancel","requestId":"r3"}
← {"event":"completed","requestId":"r3","success":true}Logging
In headless mode, structured logs go to stderr. Stdout is reserved for the JSON protocol. Log levels: error, warn, info, debug.
In interactive mode, logs go to .remy-debug.log in the working directory (default level: error). Override with --log-level.
Design Data Dev Tool
A lightweight local tool for browsing and managing the design expert's font catalog and inspiration image library.
node src/subagents/designExpert/data/dev/serve.mjs
# Opens http://localhost:3333Three tabs:
- Fonts — browse all fonts rendered in their actual typefaces, search/filter by category and source, delete
- Pairings — heading + body font pairings rendered live, delete
- Inspiration — filmstrip browser for curated design reference screenshots with analyses, add new images with auto-analyze
Data compilation scripts
# Regenerate inspiration analyses from raw image URLs
bash src/subagents/designExpert/data/compile-inspiration.sh
# Generate typographic descriptions for all fonts from specimen images
bash src/subagents/designExpert/data/compile-font-descriptions.shSpecimen pages
For generating font specimen images (used by the font description pipeline):
http://localhost:3333/specimens/fonts?page=1— paginated font specimens (40 per page)http://localhost:3333/specimens/pairings?page=1— paginated pairing specimens (33 per page)
Screenshot these as full-page PNGs, then slice into individual images with the Python script in the session history. Specimen images live in src/subagents/designExpert/data/specimens/ and are hosted at https://i.mscdn.ai/remy-font-specimens/.
Development
npm install
npm run build # Build with tsup
npm run dev # Watch mode
npm run typecheck # Type check onlyConfig
Remy reads credentials from ~/.mindstudio-local-tunnel/config.json, using the active environment's apiKey and apiBaseUrl.
Resolution order for API key:
--api-keyflagMINDSTUDIO_API_KEYenvironment variable~/.mindstudio-local-tunnel/config.json(active environment)
