maestro / src-tauri
AGENTS.md10 starsChanged 4 months agoApache-2.0View on GitHub
Notes for working in this directory. The repository-wide rules are in the root AGENTS.md. The host end of the daemon's features (acp/host_tools.rs, acp/automation_tools.rs, templates.rs, prompts.rs, collections/, project/lock.rs) is described in maestro-server/AGENTS.md, beside the daemon half. - Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified. - Do not write organizational or comments that summarize the code. Comments should only be written in order to explain "why" the code is written in some way in the…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository4 other files it gives its agents.
- AGENTS.mdAGENTS.md
- maestro-server/AGENTS.mdAGENTS.md
- src/AGENTS.mdAGENTS.md
- website/AGENTS.mdAGENTS.md
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
13 rules. About 0 are common, 6 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 10
Do not write organizational or comments that summarize the code.
- Distinctive: line 11
Prefer implementing functionality in existing files unless it is a new logical component.
- Very specific: line 12
Avoid using functions that panic like unwrap(), instead use mechanisms like ?
- Very specific: line 14
Never silently discard errors with let _ = on fallible operations.
- Very specific: line 16
Use .log_err() or similar when you need to ignore errors but want visibility
- Very specific: line 17
Use explicit error handling with match or if let Err(...) when you need custom logic
- Distinctive: line 19
When implementing async operations that may fail, ensure errors propagate to the UI layer so users get meaningful feedback.
- Very specific: line 21
When creating new crates, prefer specifying the library root path in Cargo.toml using [lib] path = "...rs" instead of the default lib.rs, …
- Distinctive: line 22
Avoid creative additions unless explicitly requested
- Distinctive: line 23
Use full words for variable names (no abbreviations like "q" for "queue")
- Distinctive: line 24
Use variable shadowing to scope clones in async contexts for clarity, minimizing the lifetime of borrowed references.
- Very specific: line 100
Use the log crate — log::error!, warn!, info!, debug!, trace!
- Very specific: line 184
Read/write via project_storage.rs.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Tauri backend (src-tauri/)
Notes for working in this directory. The repository-wide rules are in the root AGENTS.md. The host end of the daemon's features (acp/host_tools.rs,
acp/automation_tools.rs, templates.rs, prompts.rs, collections/, project/lock.rs) is
described in maestro-server/AGENTS.md, beside the daemon half.
Rust coding guidelines
- Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified.
- Do not write organizational or comments that summarize the code. Comments should only be written in order to explain "why" the code is written in some way in the case there is a reason that is tricky / non-obvious.
- Prefer implementing functionality in existing files unless it is a new logical component. Avoid creating many small files.
- Avoid using functions that panic like
unwrap(), instead use mechanisms like?to propagate errors. - Be careful with operations like indexing which may panic if the indexes are out of bounds.
- Never silently discard errors with
let _ =on fallible operations. Always handle errors appropriately:- Propagate errors with
?when the calling function should handle them - Use
.log_err()or similar when you need to ignore errors but want visibility - Use explicit error handling with
matchorif let Err(...)when you need custom logic - Example: avoid
let _ = client.request(...).await?;- useclient.request(...).await?;instead
- Propagate errors with
- When implementing async operations that may fail, ensure errors propagate to the UI layer so users get meaningful feedback.
- For new modules, prefer flat files (
src/some_module.rs) oversrc/some_module/mod.rs. Existingmod.rsfiles are legacy — don't add new ones. - When creating new crates, prefer specifying the library root path in
Cargo.tomlusing[lib] path = "...rs"instead of the defaultlib.rs, to maintain consistent and descriptive naming (e.g.,gpui.rsormain.rs). - Avoid creative additions unless explicitly requested
- Use full words for variable names (no abbreviations like "q" for "queue")
- Use variable shadowing to scope clones in async contexts for clarity, minimizing the lifetime of borrowed references.
Example:
executor.spawn({ let task_ran = task_ran.clone(); async move { *task_ran.borrow_mut() = true; } });
Database Schema
SQLite with foreign key constraints enabled. Schema V34. Configured with WAL mode and 5s busy_timeout for concurrent access.
SCHEMA_VERSION lives in src-tauri/src/core/schema.rs — that constant is the source of truth; update this doc when you bump it.
initialize_schema() picks one of three paths based on PRAGMA user_version:
| Stored version | Behaviour |
0 (fresh install) | create SCHEMA_V32_FULL, then run the migrations after v32 |
>= 22 | apply incremental migrations in run_migrations() — data is preserved |
1..=21 (legacy) | drop every table and recreate — data is lost |
So bumping SCHEMA_VERSION does not wipe user data. Add a migrate_to_vN() function and
extend run_migrations() with a matching if from < N guard; use CREATE TABLE IF NOT EXISTS
and check pragma_table_info before ALTER TABLE so the step is safe to re-run. Only databases
predating v22 still take the drop path.
An unreleased version may be rewritten rather than superseded. The pipeline rework was built
as v25, v26 and v27 and collapsed back into a single v25, because none of them ever shipped —
v24 is what released builds carry. Three migrations for a state no database outside this
repository was ever in is three code paths maintained to serve nobody. This is only safe while
the versions in question are absent from main and from every tag; check both before doing it,
and expect to delete .maestro/dev-data/ on any machine that ran the intermediate builds.
Tables: projects, tasks, task_relationships, task_instructions, task_attachments, task_comments, worktrees, settings, task_reviews, review_comments, known_hosts, ssh_connections, wsl_connections, docker_connections, session_aliases, connection_settings, templates, prompts, and the usage mirror (usage_stores, usage_turns, usage_turn_models, usage_hourly, usage_hourly_models, with the views usage_facts and usage_model_facts; see usage/mirror.rs and docs/usage-view/design.md)
The app reads and writes projects, settings, the three connection tables, known_hosts,
templates and the shared rows of prompts. It reads connection_settings only to carry a
connection's agent limit into its daemon once (see "The daemon drives the task pipeline" in
maestro-server/AGENTS.md). The rest are no longer read or written: tasks and everything hanging
off them are the daemon's (see "Tasks live in the daemon" there), and a session's name is on the
daemon's row for it (see "The resident maestro-server" there). Those tables stay where they are,
holding what earlier builds wrote, until phase 4 of docs/daemon-store-plan.md imports their rows
into the daemon and drops them. Outside core/schema.rs no app code touches them, and nothing new
should.
SSH host keys are trusted on first use. known_hosts holds one pinned key per (host, port),
written after the first connect that authenticates. A later handshake presenting another key is
refused with SshError::HostKeyError, never retried, and reaches the frontend with the
host_key_changed: prefix; "Forget host key" deletes the pin. See connectivity/ssh/host_key.rs.
The dev data directory (MAESTRO_DATA_DIR)
bun run tauri:dev sets MAESTRO_DATA_DIR=$PWD/.maestro/dev-data, so a development build keeps
its maestro.db and its locks/ inside the checkout instead of the OS app-data directory.
This is not a convenience. A database is only readable by the build that wrote it or a newer one —
initialize_schema refuses a user_version above SCHEMA_VERSION. Without the override every
checkout shares one file, so the moment any worktree carrying a schema migration is launched, every
other build stops starting with "created by a newer version of Maestro". The same collision
happens over locks/, where a dev build and the installed app contend for the same project.
resolve_data_dir in src-tauri/src/main.rs reads it, creates the directory, and treats a blank
value as unset — the convention logging::resolve_log_dir already follows, because an empty string
used as a path drops the database in the process working directory. The path is taken literally, so
the script passes an absolute one ($PWD expands under Bun Shell on every platform); a relative
value would resolve against the spawned binary's cwd, which is src-tauri/, not the repo root.
.maestro/ is gitignored, so the dev database is never committed. Delete .maestro/dev-data/ to
start from an empty one. Release builds set nothing and use the OS location as before.
Rust logging
Use the log crate — log::error!, warn!, info!, debug!, trace!. Do not use tracing::,
and do not add eprintln! or println! for diagnostics: a bundled app has no terminal attached,
so anything on stderr is discarded and a user cannot send it to you.
tauri-plugin-log is wired up in core/logging.rs; main.rs only calls into it. It writes to
stderr and to Maestro.log in a directory the user can choose. Rotation is 5 MB per file keeping
two archives alongside the live one — three files, a ~15 MB ceiling, no age-based purge.
The default directory is Tauri's app_log_dir(), passed through untouched — note macOS has no
trailing logs segment, which is Tauri's convention and not something to "fix":
| Platform | Default |
| Linux | ~/.local/share/com.maestro.app/logs/ |
| macOS | ~/Library/Logs/com.maestro.app/ |
| Windows | %LOCALAPPDATA%\com.maestro.app\logs\ |
resolve_log_dir exists only to choose between that default and the user's override, and to treat
a blank setting as unset — a blank string used as a path would drop the log file in the process
working directory.
The logger is installed from setup(), not the builder chain, because the level and directory
come from the settings table and the database is only open by then. The cost is that records
emitted before setup — other plugins' initialisation — are dropped. Do not "fix" this by moving
the plugin back into the builder: it would take the user's configuration with it.
Level resolution is MAESTRO_LOG → the stored log_level setting → info, with an unparseable
value falling through rather than failing. Dependencies are pinned at warn, because at debug
keyring, rustls and reqwest bury everything we write.
The user-selected level applies without a restart, and the mechanism matters: fern's own
filtering is fixed once built, so core/logging.rs lets our crates through at trace via two
level_for entries (maestro for main.rs, maestro_lib for the rest) and does the real gating
with log::set_max_level. A new crate in the workspace needs its own level_for entry or it will
sit at warn. A directory change cannot work this way and needs a restart — get_log_directory
returns the active and configured paths separately so the UI can say so rather than pointing a
user at a folder that is still empty.
Picking a level:
| Level | Use for |
error | The user's action failed and the app cannot recover it |
warn | A best-effort step failed — a dropped event emit, an unreachable connection |
info | Once-per-run facts: startup, version, chosen paths |
debug | Session and connection lifecycle |
trace | Per-message and per-heartbeat detail |
Raw ACP frames stay at trace. transport_types.rs and reader_task.rs serialise whole
protocol messages, which carry prompt text, agent output and the contents of files the agent
read. trace is off by default and that is the only thing keeping that content out of a file
users attach to bug reports. Do not promote those sites, and do not log message bodies at a
level above trace.
maestro-server is a separate process, spawned with a null stderr on one transport path, so its
eprintln! output goes nowhere. Report from there with helpers::send_diag(level, message),
which forwards over the protocol's Diagnostic message; the host re-logs it at a matching level.
The mcp subcommand and --version are genuine CLI output and correctly use
println!/eprintln! — the shim speaks JSON-RPC on stdout to the agent that spawned it, not to
the host.
End-to-end tests and the wdio feature
tests/e2e/ drives the real binary through WebdriverIO — real Rust backend, real SQLite,
real webview. It needs a display and is not in CI, so it only runs when someone runs it. See
tests/e2e/README.md. Keep it thin: anything provable against mocked IPC belongs in a vitest
file, which does run per commit.
The suite depends on the wdio Cargo feature, which registers tauri_plugin_wdio_webdriver in
main.rs. That plugin exposes an automation server able to drive the UI and call every IPC
command, so it is optional and off by default — verify with
cargo tree -i tauri-plugin-wdio-webdriver, which finds nothing without --features wdio.
Never ship a binary built with that flag, and do not move the dependency out of [features]
to match upstream's example, which is a throwaway test app.
Project-Local Storage (.maestro/)
Each project has a .maestro/ folder in its root with:
settings.json—ProjectConfig(non-sensitive project settings)bin/— bundledmaestro-serverbinary for that projectattachments/— agent file attachments, and underattachments/tasks/<task_id>/the copies of a task's attachments
Read/write via project_storage.rs. Follow this pattern when adding new project-scoped config (e.g., ticketing config goes in .maestro/ticketing.json).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

