Maestro is a thin, opinionated REPL layer built on top of OpenRouter. It turns the prompt‑based interface into a full‑featured terminal where you can run specialized agents, pause execution to ask the user for input, and attach to running tasks for interactive debugging.
- Tag‑based agent selection: refer to agents by a short
@nametag (e.g.,@sre status). - Background tasks: submit a task to run asynchronously; receive a task‑ID back and monitor progress via structured events.
- Pause & resume: agents can call
wait_for_inputand the task pauses until you send input via/send <task-id> <input>. - Interactive session: use
/attach <task-id>to enter a task‑specific REPL, view live streaming output, and interact with paused tasks. - Live event streaming: watch
task:output,task:log, andtask:statusevents with/events <task-id>. - Full conversation view: inspect every message and tool call that an agent has produced with
/view <task-id>. - Plugin‑friendly: new agents and tools are automatically discovered from
src/agents/andsrc/tools/(and from plugins).
bun install # install dependenciesbun run src/index.ts # start the Maestro REPL-
Submit a background task
/bg "npm install"You receive a task‑ID (e.g.,
a7f2). -
List background jobs
/tasks -
View a task’s conversation & output
/view a7f2 -
Send input to a waiting task
/send a7f2 "/etc/nginx/nginx.conf" -
Attach to a task for interactive debugging
/attach a7f2Inside the task’s sub‑REPL you can send more prompts, see live output, and type
detach(orexit) to return to the main prompt. -
Stream live events (10 s)
/events a7f2 -
Tag an agent in the main prompt
@sre status(Any
@agent-nametag routes to that agent – see/agentsfor available agents.)
| Directory | Contents |
|---|---|
src/core/ |
Core types, LLM client (src/core/llm.ts), TaskRunner, Agent state machine, Memory store. |
src/tools/ |
Built‑in tools (exec, read_file, write_file, web_fetch, think, wait_for_input). |
src/agents/ |
Agent definitions (.md files with name, description, systemPrompt). |
src/skills/ |
Markdown‑based skills that are RAG‑selected and injected into system prompts. |
src/mcp/ |
Model‑Context‑Protocol connector for external tools. |
src/index.ts |
Main REPL, slash‑command router, and the attachToTask sub‑REPL implementation. |
@<agent-name> <prompt>
The tag is parsed by the REPL’s line‑parser and routed to the matching AgentDef.
The TaskRunner stores a TaskRecord per background job, tracks its status (queued, running, waiting, completed, failed, cancelled), and emits structured events:
task:output– assistant message or tool resulttask:log– logs emitted by the runner or toolstask:status– status change (running,waiting,completed, etc.)task:waiting– agent calledwait_for_inputand is waiting for user input
Clients (/events, /view, /attach) subscribe to these events for live updates.
Any agent can call the built‑in wait_for_input tool:
{ "name": "wait_for_input", "arguments": "{\"question\": \"Which endpoint?\"}" }This blocks the agent until the user invokes /send <task-id> <input>.
attachToTask(taskId) opens a sub‑REPL with prompt task-<id> >.
All typed lines are sent to that task:
- If the task is waiting → the line is forwarded as user input (resumes the task).
- If the task is running/fresh → the line is submitted as a new user prompt.
Output from the task streams in real‑time, and you candetach(orexit) to return to the main prompt.
All commands start with / and are parsed by the REPL’s slash‑router.
| Command | Alias(es) | Description |
|---|---|---|
/bg <prompt> |
/submit <prompt> |
Submit a task in background (main agent). |
/tasks |
/jobs |
List all background tasks. |
/view <task-id> |
/attach <task-id> |
Show full conversation & status; open interactive session. |
/send <task-id> <input> |
– | Send input to a waiting task. |
/detach |
– | Leave the current sub‑REPL. |
/events <task-id> |
– | Stream events (10 s) for live debugging. |
/cancel <task-id> |
– | Cancel a running/failed task. |
/assign @<agent> <task> |
/assign-bg @<agent> <task> |
Manually assign a task to a named agent (sync/bg). |
/agents |
/list-agents |
List available agents (by name). |
/reload |
– | Reload plugins and tool definitions. |
/help |
– | Print full command list. |
Directories matching plugins/<plugin-name>/ with skills/, agents/, tools/ (or plugin.json) are automatically discovered and added to the global skill/agent/tool lists.
bun test # run the full test suite (68 tests, all pass)| Test file | Coverage |
|---|---|
tests/core/task-runner.test.ts |
Task submission, waiting, send, events, cancellation. |
tests/core/spawn.test.ts |
Shared spawnAgent helper and tag parsing. |
tests/tools/spawn-agent.test.ts |
wait_for_input tool behavior. |
tests/tools/exec.test.ts |
Shell command execution. |
tests/tools/truncator.test.ts |
Output truncation. |
tests/tools/spawn-agent.test.ts |
Original spawn_agent tool behavior. |
All tests pass with bun test and type‑check passes with bunx tsc --noEmit.
- Startup –
main()reads env vars, creates an LLM client (OpenRouter), loads skills/agents/tools, and constructs a globalAgentStaterepresenting the main agent. - REPL loop –
runRepl()presents a prompt>; input is parsed for slash commands or@<agent>tags. - Tag routing –
@agent-nametags are routed viaspawnAgentto the matchingAgentDef(sync or background if/bg//asyncsuffix is present). - Background tasks –
/bgsubmits aTaskRecordtoTaskRunner.submitTask, which kicks offexecuteTaskin a microtask.executeTaskcallsrunAgenton a copy of the originalAgentStatethat includes a reference to theTaskRunner. - Agent execution –
runAgentfetches memory, skills, and relevant context; emitstask:outputevents; and blocks onwait_for_inputwhen the tool is called. - Events – The
TaskRunnerforwards all events to subscribers (/events,/attach). - User input –
/send <task-id> <input>callsTaskRunner.sendInput, which resolves the waiting promise, and the agent resumes.
- OpenRouter‑first – The project uses OpenRouter as the LLM gateway, supporting any model OpenRouter exposes. Switching providers is a matter of changing
config.baseURLandLLM_MODELin.env. - Minimal runtime – All core logic lives in TypeScript; no heavy frameworks or runtime containers.
- Test‑first – Every new feature (waiting, events, attach/detach) has comprehensive unit tests before being wired into the REPL.
- Plugin‑agnostic – New agents/tools can be added without touching the core code; only
.mdor.tsfiles in plugin directories are required.
- Fork & create a feature branch.
- Add tests for any new behavior (existing tests all pass).
- Run
bun test– all tests must pass. - Submit a PR with a clear description and linked tests.
Please adhere to the existing code style (ES2022, strict types) and keep changes minimal.
| Command | Example |
|---|---|
/bg "npm install" |
Spawn background task |
/tasks |
List jobs |
/view a7f2 |
Show conversation output |
/send a7f2 "/etc/config" |
Resume a waiting task |
/attach a7f2 |
Enter interactive sub‑REPL |
/detach |
Return to main prompt |
/events a7f2 |
Stream live events (10 s) |
/assign @sre status |
Directly invoke an agent |
@linux-expert/logs |
Tag agent in main prompt |