MaestroBot is a local Linux user-scoped agent service.

It runs as a systemd --user service, stores its state under ~/.maestrobot, uses Maestro for the agent loop, and supervises Myria as its memory service. The control plane and the daemon are the same binary:

  • maestrobot --daemon runs the daemon directly
  • maestrobot ... runs the control CLI
  • maestrobot daemon ... manages the installed user service

Install

For a normal user install:

bash install.sh

This builds maestrobot, installs it to ~/.local/bin/maestrobot, bootstraps ~/.maestrobot when needed, and installs the systemd --user unit. It also installs shell completions for the user’s default login shell when that shell is bash, zsh, or fish. If the installer is run outside a normal login session and cannot reach the systemd --user bus, it still installs the unit and prints the systemctl --user commands to run later from a real session.

To install shell completions at the same time:

bash install.sh --shell bash --shell zsh --shell fish

Supported shells are bash, zsh, and fish. The installer rejects unsupported shell names when they are passed explicitly.

Quick Start

Set the bootstrap values you want:

export OPENROUTER_API_KEY=...
export MAESTROBOT_MODEL=...

Install once:

bash install.sh

Then edit the two operator-owned files:

$EDITOR ~/.maestrobot/config.yaml
$EDITOR ~/.maestrobot/SOUL.md

config.yaml holds credentials, model presets, frontends, onboarding policy, and runtime settings. SOUL.md is the agent’s identity, voice, and standing preferences.

Run the setup doctor before starting:

maestrobot doctor

Start the service, create a local test channel, and send a message:

maestrobot daemon start --verbose
maestrobot add-channel local demo
maestrobot chat --channel demo "hello"
maestrobot chat --channel demo --verbose "inspect the current workspace"
maestrobot channel history channel-0001
maestrobot daemon logs
maestrobot daemon stop

For Telegram, configure the Telegram frontend in config.yaml, run maestrobot doctor, start the daemon, message the bot once, then use maestrobot user unknown to list the account and attach it to an internal user.

Use maestrobot daemon start --verbose --debug when you want both verbose daemon logs and frontend-visible gateway telemetry. --debug sends compact operator messages through configured frontends for tool calls, sanitized parameters, tool results, prompt compaction, cumulative LLM token usage, and sleep/wake bookkeeping. Those debug messages are not appended to the channel transcript or Myria.

The runtime root defaults to ~/.maestrobot. Override it on any command with --root PATH.

Before starting the daemon, MaestroBot runs a preset preflight:

maestrobot test

This checks each configured preset with a simple completion probe and a simple agent-compatibility probe.

Each preset in config.yaml can set an OpenRouter provider sort mode and a context_limit_tokens cap. The default context cap is 262144; lower it for models with smaller context windows.

  • default
  • exacto
  • price
  • throughput
  • latency

MaestroBot does not pin a concrete OpenRouter provider. Runtime calls let OpenRouter choose the route for the configured model and sort mode.

Prompt caching is provider-specific. MaestroBot arranges OpenRouter requests as a stable system prefix plus a dynamic user tail so providers can reuse cached input where supported. Use:

maestrobot models cache

to send a two-request cache probe for each preset and inspect cached input and cache-write token counts.

Frontends

MaestroBot now treats frontends as real platform adapters.

The runtime owns one canonical outbound content format: authored Markdown. Frontends are responsible for lowering that canonical content into platform-safe output.

The built-in frontends are:

  • local local CLI testing and transcript inspection
  • telegram Telegram Bot API delivery and inbound polling

Telegram output is not driven by Telegram-specific prompting. The pipeline is:

  1. Markdown input
  2. normalized Telegram-safe intermediate representation
  3. Telegram renderer

The Telegram frontend prefers text + entities, falls back to HTML parse mode when needed, and finally falls back to plain text chunking if format-preserving output fails.

While a Telegram channel run is actively executing, the frontend emits a Telegram typing indicator until the run sends its reply or finalizes.

Runtime Model

MaestroBot is channel-centric:

  • one persistent context per channel
  • one message queue per channel
  • one workspace per channel
  • one priority per channel
  • one durable identity map across channels

Each channel owns its own runtime state, workspace, paging metadata, current preset tier, and retained subagent state. Channels may run concurrently up to the configured worker limit, but a single channel is always serialized.

chat --verbose streams daemon-produced trace events for the current channel run. A trace covers one contiguous execution segment from the last finalized state to the next finalized state, including prompt construction, preset/model selection, LLM responses, tool results, and finalization.

User-visible replies are expected to stay grounded in tool results. Successful tool results are treated as factual observations. Failed tool results are also factual observations about what did not work. The embedded Maestro schema now carries that grounding rule explicitly so the agent reports failures instead of inventing successful outputs.

Runtime Root

The default runtime root looks like this:

~/.maestrobot/
  config.yaml
  runtime.yaml
  state.json
  SOUL.md
  maestrobot.sock
  logs/
    service.log
    myria.log
    subagents/
  bin/
    maestroc
    myria
  maestro/
    channel_loop.mstr
    subagent_loop.mstr
    agent_loop.mstro
  myria/
    myria.json
    myria.sqlite3
    openrouter-llm.template.json
  paging/
    <channel-id>.json
  transcripts/
    <channel-id>.jsonl
  users/
    <user-id>/
      profile.md
  workspaces/
    <channel-id>/

config.yaml is static operator-authored service config. runtime.yaml is mutable desired runtime config for internal users and future operator-managed resources. state.json holds daemon-owned observed state such as queues, wake timers, unknown identities, and external account mappings. Installer-managed runtime binaries live under bin/. Paging snapshots live outside workspaces.

users/<user-id>/profile.md stores concise durable user understanding: preferences, constraints, communication style, and stable facts. The agent can update this file through a host-validated profile tool for known channel participants only. It is not used for secrets, task history, or full conversation history.

Customization Surface

The main agent behavior is designed to be customized from the runtime root rather than by editing the repo.

The editable Maestro surface is:

  • ~/.maestrobot/maestro/channel_loop.mstr
  • ~/.maestrobot/maestro/subagent_loop.mstr

The daemon compiles the root-local Maestro files into ~/.maestrobot/maestro/agent_loop.mstro when the service starts and when those files change.

Prompt composition is owned by those root-local Maestro files, but identity is not. SOUL.md is the exclusive source for the agent’s identity, name, voice, and standing preferences. The Maestro files should stay identity-neutral: they define operating behavior, prompt layout, tool policy, and state flow. The runtime injects structured data such as channel context, working memory, queue summaries, tool contracts, and the current message.

Those Maestro files also choose the host surface for each state. A state can request tool groups such as @plan, @workspace, @myria, or an exact tool name, and can request only the context sections it wants in the prompt. The Go host expands and validates those requests, then owns provider calls and tool execution.

Workspaces and Tools

Each channel workspace is a real host directory under workspaces/<channel-id>/. The agent sees a controlled VFS:

  • / is the writable channel workspace
  • /.host-path/<n> are read-only mounts of host PATH directories

The built-in tool plane includes:

  • queue and state control
  • VFS inspection, editing, search, and replace
  • command execution and long-running command sessions
  • browser automation through Playwright
  • web fetch and web search
  • image metadata and OCR
  • user-note writes and subagent control
  • Myria retrieval tools
  • external MCP servers over stdio, streamable-http, and legacy sse

The first browser action installs Playwright Chromium into <root>/cache/playwright/.

Telegram

If config.yaml contains a telegram frontend with a bot token, the daemon starts a Telegram poller and normalizes inbound Telegram messages into per-chat channels.

Telegram DMs do not need to be predeclared. Unknown Telegram accounts are discovered and listed for the operator, but they cannot trigger the agent until attached to an internal user. Unknown accounts receive a deterministic onboarding notice up to the configured cap:

Please contact the admin of this MaestroBot deployment for onboarding.

After the cap is reached, the daemon silently ignores further messages from that account until it is attached. Unknown identities move out of the normal list after the configured archive window and can still be viewed with maestrobot user unknown --archive.

Outbound Telegram messages stay canonical inside the runtime and are rendered only at the frontend boundary. That means agent prompts can stay platform-neutral.

For direct frontend delivery tests without waiting for a model reply:

maestrobot add-channel telegram <telegram-chat-id>
maestrobot channel deliver channel-0001 "**hello** from MaestroBot"

channel deliver writes the canonical outbound transcript entry, appends the event to Myria, and sends the rendered message through the channel’s configured frontend.

Memory and Myria

Each channel is a complete local agent instance. It owns its local conversation transcript, structured working-memory notebook, current-run history, workspace, queues, and sleep/wake state. Recent same-channel continuity comes from the local transcript first, so follow-up questions such as “what did you just do?” do not require a Myria lookup.

MaestroBot also keeps a local structured working-memory notebook per channel. That notebook is the primary compact operational memory used during planning and reply generation. It is host-persisted in the channel paging snapshot and survives idle periods, restarts, and paging boundaries.

The notebook is intentionally lossy and task-oriented. It carries sections such as user profile, channel facts, active goal, current plan, open loops, workspace state, and handoff notes.

During one live channel run, the runtime also retains a current-run-capability view derived from the tools exposed in that run. That lets Maestro answer capability questions from the broader current-run context even if the current schema step is a narrowed reply-preparation state.

Myria is supervised by the daemon and is query-only from the agent’s perspective. The host runtime appends the truthful inbound and outbound events. Myria is Global Persistent Memory: the durable event substrate and auxiliary retrieval system for older, cross-channel, or uncertain recall. The channel transcript and notebook remain the live local mind.

The generated Myria config uses file-backed SQLite only as a convenience for local setup and testing:

  • storage.kind = "sqlite"
  • sqlite.path = "<root>/myria/myria.sqlite3"

myria/myria.json and the OpenRouter request template are generated from config.yaml during init, test, and daemon startup. Treat those files as derived runtime files rather than hand-authored config.

Common Commands

maestrobot daemon start --verbose
maestrobot daemon start --verbose --debug
maestrobot daemon status
maestrobot daemon logs --lines 100
maestrobot status
maestrobot config show
maestrobot doctor
maestrobot test
maestrobot chat --channel demo --verbose "summarize the last task"
maestrobot user onboard "Ada Lovelace"
maestrobot user unknown
maestrobot user attach usr_example unknown-0001
maestrobot channels list
maestrobot channels add telegram <telegram-chat-id>
maestrobot add-channel telegram <telegram-chat-id>
maestrobot channel deliver channel-0001 "**hello** from MaestroBot"
maestrobot channel history channel-0001
maestrobot tools list
maestrobot tools discover --path .
maestrobot tools inspect <name>
maestrobot runtime show
maestrobot runtime reload
maestrobot runtime pause
maestrobot runtime resume
maestrobot browser install
maestrobot browser probe https://example.com

For the full design and operating model, see:

  • docs/design.md
  • docs/configuration.md
  • docs/operations.md
  • docs/storage.md