Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

One conversation. Many hosts.

Myco is a coding agent for your local workspace and remote hosts over SSH. Its inference and agent libraries also work as building blocks for your own applications.

Start where you are

The user guide explains the workflow: selecting models, giving project guidance, working across hosts, and keeping useful context through long sessions.

The bundled manual is included verbatim from Myco's runtime articles. Agents have access to those articles too. Each manual page carries a note explaining where the installed copy lives.

Build with Myco covers the public Rust interfaces and their ownership and cancellation contracts. It includes runnable examples and links to the generated API reference. The evaluations section is reserved for work in progress.

About this edition

This site is built from main. It describes that source revision, which may be ahead of the latest published crate. For the runtime contract of your installed binary, use myco --version and myco --help overview.

Search the book with the search button or S. The theme menu includes light and dark reading modes. Rust's generated reference has its own symbol search.

Getting started

Myco runs the conversation and model requests on your computer. Tools run on the always-available local host or on a remote host you name in SSH config. You can start with only local tools.

Install

With stable Rust and Cargo installed:

cargo install myco --locked
myco --version

Have bash and uv available for shell work and Python tooling, and OpenSSH for remote hosts. git, gh, rg, and curl are useful programs for the agent to use through bash. Startup reports missing expected executables.

This book tracks main. To run the same source from a checkout:

git clone https://github.com/tsnl/myco.git
cd myco
cargo install --path . --locked

Configure one model

Myco has no built-in model catalog. Create the selected profile's config directory (the default profile is shown here):

mkdir -p ~/.myco/profiles/default

Save this as ~/.myco/profiles/default/config.toml. Replace the endpoint, api_id, and context window with values supported by your model server:

model = "local"

[gateways.local]
protocol = "openai-completions"
base_url = "http://localhost:11434/v1"

[models.local]
gateway = "local"
api_id = "YOUR_SERVED_MODEL_ID"
thinking = "none"
context_window = 32768

This example expects an already-running Chat Completions compatible server; Myco does not install or start that server. For a hosted gateway, set its base URL and add an authentication source, for example:

# Place inside the gateway table.
auth = { source = "env", var_name = "MY_MODEL_API_KEY" }

Use the protocol your endpoint implements. The configuration guide explains all three protocols and credential sources; the manual contains fuller catalog examples.

Start in your project

cd /path/to/your/project
myco

Myco serves HTTP on loopback only (127.0.0.1 by default, or --bind ::1 for IPv6). For a remote server, use an SSH tunnel from your computer:

ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:8766:127.0.0.1:8765 user@remote-host

Change the remote launch URL's address to http://127.0.0.1:8766, keeping its profile and session path. No browser login is needed. The tunnel encrypts traffic between your computer and the remote host. See the browser manual for tunneling, request-origin checks, and workspace-file access.

Open the URL printed by the server and choose New session. Ask for a bounded first task, such as “Explain the entry points in this repository.” Myco reads project guidance from AGENTS.md or CLAUDE.md at session start.

Enter submits a message; Shift-Enter or Alt-Enter inserts a newline. The Cancel button stops a running turn. Closing a tab leaves it running; Ctrl-C in the launching terminal stops the server and all its sessions. Next, read everyday use and sessions.

Models and configuration

A gateway describes an endpoint and its credentials. A model describes one entry you can select with --model. You can give the same wire model multiple catalog keys for different gateways or settings.

Profiles and selection

myco --profile work selects ~/.myco/profiles/work/. Profiles separate config, sessions, workspace/prelude, and exported manuals. Selection precedence is --profile, then MYCO_PROFILE, then default. MYCO_HOME changes the parent directory; it does not itself name a profile directory.

The config path comes from --config, then MYCO_CONFIG, then <profile>/config.toml. An explicitly selected file must exist. Select a model with --model KEY; otherwise Myco uses the top-level model setting, or the sole catalog entry. It reports an error when the choice is ambiguous.

Choose a protocol

protocolRequest path appended to base_urlThinking modes
anthropic-messages/v1/messagesadaptive, budget, none
openai-responses/responseseffort, none
openai-completions/chat/completionseffort, none

These names describe wire formats. Select the format your gateway serves. The Anthropic base URL normally excludes /v1; the OpenAI dialect base URL normally includes it. Set thinking = "none" if the served model does not accept reasoning settings.

model = "coding"
attach_timeout_secs = 10

[gateways.provider]
protocol = "openai-responses"
base_url = "https://YOUR_GATEWAY/v1"
auth = { source = "env", var_name = "MY_MODEL_API_KEY" }
max_request_bytes = 30_000_000

[models.coding]
gateway = "provider"
api_id = "YOUR_SERVED_MODEL_ID"
context_window = 200000
max_output_tokens = 8192
thinking = "effort"
auto_compact_at = 0.8

The endpoint and model ID above are placeholders. Match context_window to your served model; it drives context display and compaction policy. Top-level settings must precede TOML tables.

Credentials and overrides

Authentication can be a literal token, an environment source, a file source such as { source = "file", path = "~/.secrets/model-token" }, or { source = "none" }. Omitting auth also sends no authentication header. Myco loads .env at startup. Credential lookup errors are reported when the selected model is used; unknown config fields are rejected during startup.

Model fields override gateway fields. A model can inline protocol, base_url, and auth and omit gateway. A model's auth or retry table replaces the gateway's corresponding value rather than merging individual fields.

max_request_bytes sets a gateway's maximum serialized JSON request body (default 30,000,000 bytes / 30 MB; positive integers only). A model can override it, including when configured without a gateway. The limit counts the complete history, base64 images, system prompt, tool schemas, and JSON overhead. Oversized requests are rejected locally before upload and rewind the rejected turn. This is separate from the per-image limit below; images are never silently resized or removed to fit a request.

Control long runs

SettingPurpose
max_output_tokensOutput budget per model request; default 8192
max_truncated_resumesConsecutive continuations after output truncation; default 3, 0 disables
auto_compact_atOptional fraction of the context window; automatic compaction threshold
max_image_base64_bytesPer-image uploaded base64 limit; default 5 MiB
attach_timeout_secsRemote connection timeout; default 10, 0 disables

Retry settings belong in [gateways.NAME.retry] or [models.KEY.retry]. The default is three total attempts with bounded exponential backoff. Only transient failures before any response parts arrive are retried. Partial responses are not replayed. The bundled overview is the complete settings reference.

Everyday use

Start Myco in the repository or directory you want to work on. State the outcome, relevant constraints, and how you want the result verified. Tools execute on local by default; name a remote host when the work belongs there.

Give persistent project guidance

Myco discovers AGENTS.md / CLAUDE.md from your launch directory through the repository root at session start. Keep project conventions and verification commands there. The selected profile also has a workspace and a prelude: durable entries appended to every agent system prompt. Ask the agent to use the prelude tool for persistent personal guidance. Ordinary workspace files remain notes the agent can read as needed.

Work in the browser

ActionControl
Submit a messageEnter or Send
Insert a newlineShift-Enter or Alt-Enter
Queue a follow-up while busyEnter or Queue
Edit a queued message in placeEdit, then Save & send
Remove a pending messageUnqueue
Cancel the running turnCancel
Inspect tool input and outputExpand its tool block
Inspect running tools and background shellsActivity
Let a running shell call continue while the assistant moves onBackground on the tool or in Activity
Switch configured modelsModel selector between turns
Compact contextCompact or /compact
Open another sessionNew or a session link on the home page

Set reasoning effort with --effort when launching the server. A tab can be closed or refreshed while work continues. Open the session URL again to observe its output. Ctrl-C in the launching terminal stops the server.

Editing holds a message and the messages behind it until you save, discard the edit, or unqueue it. Your previous composer draft returns when editing finishes. After a reload, held messages remain in the queue; Edit opens their last saved content, and Resume sends it unchanged. Cancel stops the current turn and sends ready messages while leaving held edits paused.

Attach an image

Mention a local image as @./screenshot.png in your message. Supported extensions are PNG, JPEG, GIF, and WebP; the file bytes determine its actual format. Paths with spaces are not supported in mentions. Bad paths and oversized images fail before the model call.

Image limits apply to the uploaded base64 payload, which is about 4/3 the file size. The default per-image cap is 5 MiB, and attachments in one message have a separate 20 MiB budget. Downscale large images before attaching them. The agent can also call view_image on a selected host.

Automate sessions

For a single task, run myco -p "prompt" or git diff | myco -p "Review this". The answer streams to stdout; diagnostics and the saved session ID go to stderr. Use --resume ID to continue later. myco --mode cli provides a scrolling chat with line editing, tool activity, and /compact. The command-line manual describes input, cancellation, and exit codes.

Use the loopback server API to create sessions, submit work, observe output, compact, and cancel. Connect directly on localhost or through SSH forwarding; no login or token is needed. A successful submission means accepted; wait for an idle snapshot and inspect the result. The browser manual contains the request formats and a Python example.

Include parent_session to create a hidden child; add fork: true to seed it with saved parent context. Each child has its own runner and tools while sharing the server's profile. The overview describes context and ownership rules.

Sessions and context

A session holds a stable ID, title, links, scratchpad, and ordered threads. A thread is a linear conversation history. Only the latest thread accepts new messages.

Live tools have a separate lifetime. A bash session is a process on a host; its state belongs to the current session runtime, not to a saved message.

Find and resume work

Ask the agent to set the session title with session_meta; the page heading and tab title update with it. The home page lists visible sessions. Search filters by title, model, and ID. Open links in separate tabs or use /resume ID.

myco --profile work --resume SESSION_ID

Use the profile in which the session was created. For searches across stored message excerpts, scratchpads, and legacy transcript tails, the agent can use session_meta with a query.

Resume restores conversation memory. After process exit it cannot restore running shells, editor read stamps, or the remote filesystem as it was observed. Ask the agent to check current state before continuing work that depends on it.

Compact a long conversation

/compact creates a successor thread in the same session, containing a summary and bounded recent context. The predecessor keeps its original messages and tool results. Live shells and editor read stamps continue through compaction, as do the title, links, and scratchpad.

With a per-model auto_compact_at threshold, the server compacts at a settled boundary between tool rounds or after an answer and asks the agent to continue. Long tool loops can compact repeatedly as context grows. A completed answer can trigger at most one cycle per submission. Manual compaction and reopening a saved session wait for input. Failure or ineffective compaction disables automatic compaction until a manual compaction succeeds or another session opens.

The agent can inspect old threads with session_history, using threads, stats, and expand actions. Compaction bounds active model context; it does not delete the session's older threads from disk.

Organize saved work

Click Archive on the home page to hide a session without deleting it. Choose Archived sessions and click Restore to make one visible again. Opening an archived session URL does not restore it automatically. Archiving does not stop tools or archive children.

Archived sessions and their history, transcript, and summary files live under session/archived/ in the selected profile. Restore moves them back to the active store. Startup moves existing archived sessions into that folder too, skipping sessions open in another process. Ordinary browsing skips the archive folder, so old archives do not slow down the active session list.

/new saves the current session and starts a fresh one with fresh tool ownership. A running process owns its session's writer lock; use that process to change the session while it is open.

What is saved

Each session has a JSON document under the selected profile's session/ directory. Its threads hold messages, recorded tool outcomes, and human turn timestamps. Historical readline and console sidecars remain readable and move with archived sessions; the server writes no new terminal logs.

Use the browser and session tools to change metadata. The manual describes the persisted format and thread semantics in more detail.

Image storage

Session images live in content-addressed files under the profile's images/ directory; histories contain SHA-256 references. Keep that directory with session backups. Legacy inline images remain readable. Compaction and history inspection avoid loading archived image payloads; an active request fails clearly if a referenced file is missing or corrupt. Blobs are not automatically garbage-collected.

Remote hosts

Myco keeps model calls, credentials, and the conversation on the local host. A remote host runs tool services over SSH. The local worker is always in-process and requires no separate worker command.

Add an SSH alias

Remote hosts come from concrete aliases in ~/.ssh/config, including files referenced by Include. Put connection details there:

Host devbox
    HostName devbox.example.com
    User developer
    IdentityFile ~/.ssh/id_ed25519

Wildcard and negated aliases do not become Myco hosts. The name local is reserved. Myco uses non-interactive SSH with BatchMode=yes, so arrange authentication before starting it.

ssh -o BatchMode=yes devbox true
ssh -o BatchMode=yes devbox 'command -v myco; myco --version'

Install the same Myco package version on both hosts. Build on the remote host or use a binary matching its OS, CPU, and libc. An interactive login may have a different PATH from the non-interactive SSH command; verify the latter. The remote needs the programs its tools execute, including bash. It does not need your model catalog or API credentials.

Direct work to a host

Start Myco and issue a tool call on the named host. An idle remote is normal: it attaches on its first tool call. Ask, for example, “On devbox, inspect the build logs under /srv/project.” Host tools accept a host field; omitting it selects local.

Bash working directories come from the host process. To run somewhere else, the agent uses a command such as cd /srv/project && cargo test; the bash tool has no separate working-directory field. Persistent shell state requires a started bash session. Session IDs are specific to both the host and their owning session runtime.

Operate and diagnose

The tool block reports connection failures. A remote attach failure is reported as a tool error; it does not make the local host unavailable. Remote workers connect lazily with ssh … myco --mode host and exchange newline-delimited JSON. Keep non-interactive startup output from interfering with that protocol.

If many nested local agents use the same remote, OpenSSH connection sharing can reuse authentication and transport. Configuration and installation recipes are in the harness operations manual.

After a remote process exits or SSH disconnects, its live tools are gone. Saved conversation history remains an account of earlier observations; reconnecting is not a shell-state restore.

Troubleshooting

Start with the error Myco reports, myco --version, and the selected profile. The installed myco --help overview describes your exact build's runtime contract; this website tracks main.

SymptomWhat to check
No models configuredCreate [gateways] / [models] in the config path printed in the error. Myco ships no model catalog.
Unknown model--model takes a catalog key, not necessarily the provider's wire ID. Set api_id separately.
Missing credentialRead the named environment variable or file source. Check the environment of the process launching Myco.
Provider rejects thinkingSet the model's thinking mode to one the endpoint supports, or none.
Remote is DOWNRun the non-interactive SSH checks in remote hosts; verify version, PATH, and authentication.
Session missingCheck the profile and archived-session filter. Nested and compaction worker sessions are hidden in ordinary listings.
Session is lockedAnother running process owns the session. Continue there or use a different session.
Shell missing after resumeConversation history persists; live tools do not survive process exit.
Unexpected submissionEnter sends; use Shift-Enter or Alt-Enter for newlines.

Oversized input

Myco checks per-image and per-message attachment budgets before sending input. Images already in the conversation also contribute to the whole request size. A request rejected for size triggers recovery into a successor thread without the last user turn; the predecessor preserves the rejected input and recorded tool actions. Resend smaller input, compact, or start a new session. Recovery does not undo tool side effects.

Repeated failures or interruption

Only transient failures before response parts arrive are retried automatically. An error after partial output is surfaced rather than replayed. The Cancel button cancels the turn, including retry waits; tools have their own cancellation cleanup. Inspect current files and processes before resubmitting work with side effects.

Recover useful evidence

Use session_meta to inspect session metadata and paths. The browser shows startup warnings, running errors, and tool outcomes; server diagnostics go to the launching terminal. Read saved threads through session_history for structured history. Legacy .console files remain available but are not extended by the server.

When reporting a bug, include the version, relevant config shape with credentials removed, host and protocol involved, and the smallest reproduction. The harness operations manual covers host diagnosis in greater depth.

Myco overview

myco is a coding agent server: one conversation can drive tools on your laptop and on remote machines over SSH. Tools run on hosts (local or remote); nested sessions use the server API (see below).

Architecture (one sentence)

Agents orchestrate; hosts run tools on machines. The local host is always enabled in-process (no subprocess). Remotes use ssh … myco --mode host over NDJSON. The same myco binary runs the server (the default mode), one-shot prompts (-p), terminal chat (--mode cli), and the remote host runtime (--mode host). Terminal modes use the same durable session runner; see myco --help cli.

myco server / chat adapter
  ├── Agent (model context and run loop)
  └── SessionRuntime (session binding + tool ownership)
      └── Harness (routing, config, root-configured services)
          ├── HostController "local"   → in-process HostWorker (always on)
          └── HostController "…"       → ssh … myco --mode host (lazy remote)
                └── bash, str_replace_based_edit_tool, view_image (per host)
  • Server process: model, conversation history, cancel, event sink, and the in-process local host worker (standard tools plus root-only services such as session_meta).
  • Remote host process (myco --mode host): standard host tool services (bash, editor, view_image) over NDJSON via SSH.
  • Nested agents: clients create a hidden child through POST /api/sessions with parent_session and optional fork: true. The child shares the addressed profile and model catalog, with its own runner and tools. Forks inherit saved context; unresolved parent tool calls receive unknown outcomes, never replay. The child's first submission stamps its own identity, including when it was created before a server restart. Remotes stay tool workers.

Sessions and threads

A session has a stable id, metadata, and an ordered set of threads. Each thread is a linear message history. Only the latest thread accepts new messages; an agent works on one thread at a time, and session turns and compaction share a writer gate.

/compact creates a successor thread in the same session. Its first message contains the summary, followed by bounded recent context. The predecessor retains its original messages and tool output. Title, links, and scratchpad remain attached to the same session.

Live bash shells and editor read stamps belong to the session runtime, shared across threads and any replacement agent using that runtime. Compaction does not reset them. A recorded tool result remains an observation from its original thread: a shell or file may have changed since then. /new or switching to another session uses fresh tool ownership. Resuming saved history after process exit does not restore tools.

Hidden runtime system parts record the runtime owner, observation time, model key, API model/protocol and effort when known, and owned tool resources. Inventory covers retained bash sessions (including exited processes with captured output) and editor read fingerprints. Local state is observed directly; connected remote hosts are queried with a bounded wait. Inventory never connects a lazy remote. Failed queries retain explicitly last-known data rather than claiming the host is empty. This is an inventory of tool handles, not every OS process or file created by a command.

A new runtime records which previously observed handles are unavailable here. External side effects may survive: inspect them before retrying work, and re-read files before editing. Model and effort changes produce a new notice. Compaction and rejected-input recovery carry the latest runtime facts forward; earlier threads retain the original observations. The session's top-level model is its initial catalog key; runtime records identify the model used afterward. These parts reach the model but are omitted from transcript replay, titles, and human acceptance timestamps.

State checkpoints fail closed: a save error stops further model/tool work. An interrupted tool batch is recovered with explicit unknown outcomes and a hidden runtime notice, since the calls may have taken effect before their results were saved. Inspect external state before retrying those actions. Stored histories remain readable for inspection, but malformed call/result pairs cannot be used as executable context.

Use session_history to read saved threads without loading all of them into context:

  • {"session_id":"…","action":"threads"} lists threads, newest first.
  • {"session_id":"…","thread_id":"…","action":"stats"} reports a thread and its predecessor.
  • {"session_id":"…","thread_id":"…","action":"expand","index":12} reads an original message.

Omitting thread_id selects the active thread. Older threads are read-only. Session files use schema version 5, including archive status, per-user-turn acceptance times, and structured system content. System parts carry model-visible runtime context without appearing in transcript replay. Formats 2 through 4 are accepted and upgraded on read; loading alone does not rewrite their files. Older turns keep unknown timestamps. Older binaries reject version 5. Existing predecessor/successor session links remain metadata; separate saved sessions are not automatically combined.

The browser’s Archive and Restore controls change a session's browsing visibility while retaining every thread and live tool. Archive status belongs to the named session only; children and legacy compaction-linked sessions are independent. See browser for archive filters and restoration.

Config & paths

Select a profile with myco --profile NAME, or set MYCO_PROFILE; the default name is default. Each profile has its own config, sessions, workspace/prelude, and exported manual under ~/.myco/profiles/NAME/. MYCO_HOME changes the parent installation directory, so test runs can use MYCO_HOME=/tmp/myco-test. Profile names contain letters, digits, hyphens, or underscores.

The browser server exposes an independent instance at /profiles/NAME/ for each existing profile, all on one port. --profile chooses the initial instance and the target of launch overrides. Local tool processes inherit MYCO_PROFILE, absolute MYCO_HOME, and their current instance's MYCO_SERVER_URL across working-directory changes. Use $MYCO_SERVER_URL/api/... for nested sessions. Remote hosts remain tool workers; config and credentials stay with the server.

For an existing installation, stop myco and move its config.toml, session/, and workspace/ into ~/.myco/profiles/default/ before restarting. Files are never moved automatically; missing profile config is reported at its new path. The manual is regenerated on startup. The paths below show the default profile.

PathRole
~/.ssh/configRemote hosts: every concrete Host alias (no */?/! patterns; Includes followed) is a remote host of the same name. Local is always on.
~/.myco/profiles/default/config.tomlModel catalog ([gateways] / [models], default model) + knobs (attach_timeout_secs, max_prelude_bytes). Override: $MYCO_CONFIG or myco --config.
~/.myco/profiles/default/session/{shard}/{id}.jsonOrdered threads + shared metadata (title, links, scratchpad), as minified single-line JSON — read it via the session_history tool or jq, not raw cat/grep. Not shell/file state. Worker runs (e.g. compact) use the same store with a non-user kind (hidden in default listings).
~/.myco/profiles/default/images/{shard}/{sha256}Immutable raw image sidecars, shared by all threads and sessions in this profile. Back up this directory together with session/.
~/.myco/profiles/default/session/{shard}/{id}.historyLegacy readline history, preserved when present.
~/.myco/profiles/default/session/archived/{shard}/Archived session JSON, thread summaries, and any legacy readline/console files. Restore moves these files back; writer locks stay at session/{shard}/{id}.lock. Startup moves archived sessions out of the active store when their writer lock is available.
~/.myco/profiles/default/manual/{version}/{commit}/These articles, copied to disk at startup for the running build (index.md plus one file per article). Read and search them like any other files; the agent system prompt names the directory. myco --help <id> prints the same text.
~/.myco/profiles/default/workspace/Free-form agent workspace: notes, drafts, anything, in any layout. workspace/prelude/ holds write-once prelude entries (edited via the root-only prelude tool); every entry is appended to every agent system prompt, followed by a bounded listing of the other workspace files (see below).

Minimal config shape (~/.myco/profiles/default/config.toml — hosts are not listed here; top-level keys must come before the tables, per TOML):

model = "grok-4.5-build"      # default model key (--model overrides)
# Per-remote connect timeout in seconds on first tool use (0 disables).
attach_timeout_secs = 10
# Hard cap on the rendered prelude in every agent system prompt (default 262144):
# oversized edits are refused, and startup exits against a prelude over it.
max_prelude_bytes = 262_144
# Model requests per compaction, including retries (positive; no duration limit).
compaction_max_requests = 64

[gateways.xai]
protocol = "openai-responses"
base_url = "https://api.x.ai/v1"
auth = { source = "env", var_name = "XAI_API_KEY" }

[models."grok-4.5-build"]
gateway = "xai"
context_window = 500_000
  • The config is validated at startup, before any model call. Myco ships no models, so an empty catalog is an error: it names the config file and prints an entry to paste. Unknown top-level keys are rejected (a typo'd model would otherwise be silently ignored), as are unknown fields inside [gateways.*] / [models.*], an empty base_url, an unknown gateway reference, and a thinking mode the protocol does not support. Every config error names the file it came from.
  • A config path you name (--config, $MYCO_CONFIG) must exist — a typo there is an error, not an empty catalog. The defaulted ~/.myco/profiles/default/config.toml may be absent (that is a first run).
  • Remote hosts come from ~/.ssh/config: each concrete Host alias attaches as ssh -o BatchMode=yes <alias> myco --mode host. Include directives are followed. Put user / port / identity / ProxyJump in ~/.ssh/config; wildcard (*/?) and negated (!) patterns are ignored. The alias local is reserved (skipped).
  • Remotes need myco on the remote PATH used by non-interactive SSH (~/.local/bin and ~/.cargo/bin are common). Verify with ssh -o BatchMode=yes <alias> 'command -v myco; myco --version'; an interactive login can resolve a different binary than the host worker.
  • Missing files → local-only (safe default). There is no default_host setting; default is always local.

Bash exec and start inherit the host process's working directory. Use cd /path && command to run elsewhere; quote paths as shell arguments. An exec directory change lasts only for that call. To keep shell state across calls, start a shell (for example, cd /path && bash --noprofile --norc) and send commands through write. The bash tool has no separate working-directory argument; unsupported fields are rejected before execution.

Models & credentials (the catalog)

Myco ships no built-in models: the [gateways] / [models] tables in config.toml are the entire catalog. A gateway is a place models are served from (protocol + base_url + auth); a model entry is the key you pass to --model (and what sessions record). Model-level fields override the referenced gateway; a model may also inline all three and skip gateway.

[gateways.anthropic]
protocol = "anthropic-messages"        # {base_url}/v1/messages
base_url = "https://api.anthropic.com"
auth = { source = "env", var_name = "ANTHROPIC_API_KEY" }

[gateways.openrouter]
protocol = "openai-responses"          # requests go to {base_url}/responses
base_url = "https://openrouter.ai/api/v1"
auth = { source = "env", var_name = "OPENROUTER_API_KEY" }

[gateways.ollama]
protocol = "openai-completions"        # requests go to {base_url}/chat/completions
base_url = "http://localhost:11434/v1"

[gateways.anthropic.retry]             # optional; per gateway
max_attempts = 5                       # total tries, including the first
initial_backoff_ms = 500
max_backoff_ms = 60_000
backoff_multiplier = 2.0

[models.claude-opus-4-8]
gateway = "anthropic"
context_window = 1_000_000             # required on every model
auto_compact_at = 0.8                  # compact at 80% of the window

[models.claude-haiku-4-5]
gateway = "anthropic"
thinking = "budget"                    # older models reject adaptive thinking
context_window = 200_000

[models.kimi-k3]
gateway = "openrouter"
api_id = "moonshotai/kimi-k3"          # wire id; defaults to the key
context_window = 1_000_000

[models.local-qwen]                    # inline, no gateway ref; no auth
protocol = "openai-completions"
base_url = "http://localhost:11434/v1"
api_id = "qwen3:8b"
thinking = "none"                      # no reasoning: don't send an effort
context_window = 32768

Pick the protocol by what the endpoint serves: openai-responses for the Responses API ({base_url}/responses — OpenAI, xAI, OpenRouter), openai-completions for the older Chat Completions dialect ({base_url}/chat/completions) that llama.cpp, Ollama, vLLM, LM Studio, DeepSeek, Groq and friends speak. Chat Completions has no reasoning-summary channel, so thinking there comes from the provider's reasoning_content / reasoning deltas (nothing shown when a server sends neither), the output cap goes out as max_completion_tokens, and a tool result's images follow in a user message because a tool message may only carry text.

Per-model fields: api_id (wire id, defaults to the key), required context_window (drives USER n/m + auto-compact), thinking (anthropic-messages: adaptive (default) | budget | none; openai-responses / openai-completions: effort (default) | none — use none for models that reject a reasoning effort), max_output_tokens (default 8192), max_image_base64_bytes (largest image the model accepts, as the name says measured on the uploaded base64 payload — 4/3 of the file on disk; default 5 MiB, matching Anthropic's per-image cap). The image cap is enforced locally by view_image and by browser @path attachments, so an oversized image fails with a clear message naming both sizes instead of a provider 400. Remote hosts are spawned with the selected model's value (myco --mode host --max-image-base64-bytes), which keeps every host in a session on the same limit.

max_request_bytes on [gateways.NAME] caps the entire serialized JSON request body, including the system prompt, tool schemas, conversation history, and base64 image data from attachments and view_image. It defaults to 30,000,000 bytes (30 MB) for every protocol and must be a positive integer. For example, put max_request_bytes = 20_000_000 in a gateway table for a 20 MB endpoint. A [models.KEY] value overrides its gateway's cap and also works for models configured without a gateway.

The exact body size is checked before upload. A request over the cap fails locally without transient retries and follows the normal rejected-turn rewind; earlier completed turns remain active, and the predecessor thread preserves the rejected turn's observations. Errors name the actual and configured byte counts. Reduce attachments or compact the session before retrying, or raise the configured cap if the endpoint supports it. This cap does not resize images or replace max_image_base64_bytes: several individually acceptable images can still exceed the request cap as they accumulate in history.

max_truncated_resumes (default 3, 0 to opt out) caps how many consecutive max_tokens stops one turn resumes through before handing control back. Truncation is not a dead end: a turn cut off mid-tool-call already ends on the tool results, so it simply continues, and one cut off mid-sentence is continued by a user turn asking for the rest (the assistant's own cut-off message cannot be resent — that is the prefill shape current Anthropic models reject). Both count against the cap, which exists because a model whose output cap is too low for how much it writes would otherwise resume all night; any turn that ends for another reason clears the count. Per model because the right ceiling depends on that model's max_output_tokens versus how much it tends to write. Auto-compaction runs through the server’s session runner. auto_compact_at = 0.8 triggers when reported prompt size reaches 80% of context_window, at a settled boundary between tool rounds or after a normal answer. The system prompt tells the agent this threshold. Unset (the default) disables automatic compaction; the fraction must be greater than 0 and less than 1.

It runs the same compaction as /compact, creating a successor thread in the same session with live tools intact. After success, a # Resumption message asks the agent to continue the pending task from the summary and retained context, or stop if the task is complete or needs user input. This message is stored in the conversation without a human acceptance timestamp. It is a continuation, not startup: completed actions should not be repeated. Opening a saved session with --resume or /resume still waits for user input and does not restore live tools from a previous process.

Long tool loops can compact repeatedly when the context shrinks then grows again. A completed answer triggers at most one compact-and-continue cycle per submission. If the next usage report remains above the threshold, or summarization fails, automatic compaction is disabled until manual compaction succeeds or another session is opened. Failed generation, cancellation, refusal, and an exhausted truncation cap do not start automatic continuation. Manual /compact waits for the next user input. Compaction workers do not run auto-compaction. Each committed successor retains the same live tool owner and the run's usage and truncation accounting.

Retry is per gateway — what is being tuned is one endpoint's tolerance for blips and its rate-limit behaviour — in a [gateways.NAME.retry] table: max_attempts (default 3, counting the first; 1 disables), initial_backoff_ms (500), max_backoff_ms (30 000), backoff_multiplier (2.0). Each unset field keeps its default, so setting one knob does not reset the others. A model entry may carry its own [models.KEY.retry] — the only way for a gateway-less model to configure retry — and, like auth, it replaces the gateway's table rather than merging with it. Only failures that happen before any of the response has streamed are retried (connection errors, 408, 429, and 5xx including Anthropic's 529); a 400, a 401 or a 413 fails the same way however often it is sent, so it surfaces immediately. A failure mid-stream is never retried either, because the already-emitted parts would be replayed as duplicates. A provider's Retry-After is honoured when it asks for longer than the computed backoff, still bounded by max_backoff_ms. The agent starts a fresh generation attempt for each retry; provider drivers perform one attempt and report failures. The browser shows a notice describing the failure and whether it will retry. Cancel stops the request, including retry waits. Notices are not added to model history.

Auth is per gateway, overridable per model. The auth value is either the credential itself (auth = "sk-…") or a source table: { source = "env", var_name = "…" } reads the process environment (dotenvy loads a .env from the cwd at startup); { source = "file", path = "…" } reads the file's trimmed contents (~/ expands; keeps secrets out of a shareable config); { source = "none" } — or omitting auth — sends no auth header (local servers). A credential that fails to look up does not fail startup resolution — the error (naming the env var / file) surfaces when the model is used.

Default model: --model → config.toml model → the sole [models] entry. Anything else is a startup error listing the configured keys. Rerouting a model through a different gateway is a config edit (e.g. point a claude-opus-4-8 entry at gateway = "openrouter" with api_id = "anthropic/claude-opus-4.8") — note the native Anthropic gateway keeps prompt caching and adaptive thinking, which generic Responses gateways do not.

All resolution happens in one startup step (myco::config::Config), which also loads the config file (--config → $MYCO_CONFIG → ~/.myco/profiles/default/config.toml).

Host routing

  • Host tools (bash, str_replace_based_edit_tool, view_image) accept optional input field host.
  • Omitted host → local (always in-process).
  • Bash session_ids are per host and owned by a session runtime. Do not assume a session on local exists on devbox.
  • Local is always ready. Remotes are lazy: SSH workers spawn on first tool use.
  • Connect failures surface in tool output. Check the remote with the non-interactive SSH commands in harness-ops before retrying.
  • view_image (per host): returns a png/jpeg/gif/webp file as an image the model can actually look at — screenshots, diagrams, rendered output. Size is capped at the running model's max_image_base64_bytes (default 5 MiB, measured on the base64 payload); the tool's own description quotes the live limit, and going over fails that tool use. The format is read from the file's magic number, so the extension may be wrong or missing (user @path attachments share the same detection and cap). Text files stay with the editor.
  • Text search: bash + rg/grep on the target host. myco ships no search tools of its own; project guidance (AGENTS.md/CLAUDE.md, skill packs) is read with the editor or rg like any other file.
  • Editor views: whole-file reads, view_range slices, and directory listings reject output over 256 KiB. A single long line also counts toward the cap. Read a smaller range, or use bash with bounded output for long lines and large directories. A rejected view does not authorize subsequent edits.

Nested agents (the recipe)

Use the server API on the local host. Read browser.md for loopback access, request envelopes, and polling or streaming results. Remote clients connect through an SSH tunnel; no browser login is required.

  1. Create a session with a fresh request_id and parent_session set to your session ID, available in the newest # Session block or session_meta get.
  2. For shared context, add fork: true. It seeds the child with the parent's saved conversation. Use the same model key to preserve prompt-cache reuse; send select_model before the first submission if the server default differs.
  3. Submit the bounded task through the child's action endpoint. Poll its snapshot or consume /api/events; busy: false marks the end of accepted work.
  4. Cancel through the child's cancel endpoint when necessary. Closing a client connection does not stop the worker. Submit further turns to the same ID.
  5. Read the child's output and verify the requested result. It remains hidden from ordinary listings; open its URL or use session_meta with include_hidden: true to inspect it later.

Give each child a bounded task, constraints, expected result, and whether it may delegate further. Ask for completion evidence or a specific blocker. Keep bulk output in files and return their paths with a concise summary. A completed turn alone does not prove that the task is complete.

Forks copy checkpointed observations, including pending operations. Unfinished tool calls get unknown outcomes before the child generates; they are never replayed. Forks have their own tool ownership and cannot inherit live parent shells. Child sessions share the profile addressed by their API URL and reach remotes through SSH; remote workers need no model keys or session store.

Agent workspace

workspace/ under the selected profile root is the agents' own directory — free-form files maintained with the ordinary tools (no required format), persistent across sessions and shared by every agent using that profile. workspace/prelude/ is the one special place: it holds the agent's prelude as maildir-style entries — one write-once *.md file each, never edited in place. Every visible entry is rendered, in filename order under a [prelude entry <name>] label, into the # Prelude section of every agent system prompt, read at model build time (session start, model switch, worker spawn).

Running agents scan the selected profile's prelude before each model step, including between tool rounds in a long turn. When visible entry contents change, myco appends a small [myco: Prelude changes] note listing added, modified, and removed filenames as internal system content to the latest user input or tool result and checkpoints it before the next request. These updates reach the model but remain hidden in the browser conversation, including notices saved by older versions as text parts. The agent can read changed files on the local host or use prelude action=list; current entries supersede the prompt snapshot, and removed entries no longer apply. This covers edits from other sessions as well as the agent's own prelude tool calls. The system prompt stays fixed so its cached prefix remains reusable.

Scanning happens at model-step boundaries: it does not interrupt an in-flight request or tool call, and an idle session picks up changes when it next runs. Hidden temporary files, non-Markdown files, and empty entries are ignored. Failed scans keep the last known snapshot and are retried at the next step. If rewind or compaction drops a notice, or the agent moves to another thread after receiving updates, the next step asks it to reload the full live prelude.

The root-only prelude tool (local in-process worker, like session_meta) is the edit path: add a new entry, replace an entry (the replacement lands as a new file before the old id is dropped), remove one, or list the live state. The write-once discipline is what makes concurrent agents safe, even on a weakly consistent network filesystem: adds cannot collide (fresh timestamped names), and two agents replacing the same entry leave two candidate entries — a duplicate the next curation pass merges — never a lost one. No locks, no in-place edits. Distinct from the per-session session_meta scratchpad.

The prompt fragment makes the prelude the default home for durable information — agents record findings eagerly and reserve workspace files for cold material (rarely relevant, or high-volume lookup-only data). Prompt-resident text is cached, so a big prelude is cheaper than the mid-task exploration it replaces.

max_prelude_bytes (config.toml; default 262144 = 256 KiB) bounds the rendered prelude, and it is enforced at both ends rather than applied to the prompt: the prelude tool refuses an add/replace that would cross it, and startup exits against a directory already over it, naming the sizes and the two fixes (prune entries by hand, or raise the knob). A prompt therefore always carries the prelude whole — a shortened one is indistinguishable, from inside the prompt, from knowledge that was never recorded, which is exactly the failure the cap exists to prevent.

The rest of the workspace is listed, not quoted: a # Workspace Files section gives each visible file's path (relative to workspace/), the UTC day it last changed, and its title (first markdown heading, else first non-empty line). Hidden names, symlinks, prelude/ itself, and binary titles are skipped; the walk and the rendered block are bounded (4 levels, 200 files, 8 KiB). A marker reports known omissions, but the listing is not exhaustive even without one: search the profile's workspace/ when an expected note is missing. The prompt's appended blocks run least to most volatile — project guidance, then the prelude, then this listing. Guidance leads because it changes only when the repo's own file does, while agents are asked to record into the prelude eagerly; ordering it that way keeps a recorded finding from invalidating the cached guidance block for every agent that follows. The listing likewise uses days rather than timestamps and path order rather than recency, so ordinary workspace writes leave the shared prompt prefix intact for same-model forks.

Product limits (V1)

  • No heartbeat: remote liveness is next tool error; local is always in-process.
  • No mid-flight cancel over the host pipe yet; Ctrl-C cancels the agent turn locally.
  • You cannot invoke slash-commands; tell the user which to run.
  • Conversation resume ≠ restored bash sessions or editor state.
  • Bash sessions die when the host process exits (server exit, host crash, SSH drop). Local in-process sessions also end when their owning session runtime is released (for example, /new).

Command line

Run myco and open its printed launch URL. Each browser profile uses its own workspace/ for local tools and served files. The server owns sessions and tools; the browser supplies conversation controls. Use -p for a one-shot prompt or --mode cli for scrolling terminal chat. Both run a local session runner without starting an HTTP server. The launcher also provides the internal SSH host worker. myco-eval remains a separate evaluation utility.

OptionMeaning
-p [PROMPT], --print [PROMPT]Run one prompt and stream answer text to stdout; bare -p reads stdin
--mode cliScrolling terminal chat (--mode interactive is an alias)
--port PORTLoopback HTTP port, default 8765; 0 chooses a free port
--bind ADDRLoopback IP or localhost, default 127.0.0.1; ::1 selects IPv6
--profile NAMESelect profile, overriding MYCO_PROFILE (default default); server mode opens it first
--config PATHConfig path, overriding MYCO_CONFIG and the profile default
--model KEYDefault model from the config catalog
--effort LEVELReasoning effort: low, medium, high, max; default high
--resume IDResume a saved session or unique prefix; in server mode, open it from the launch URL
--debug-dump-api-requestsWrite provider request bodies to stderr
--help [ARTICLE]Launcher help or the embedded manual article
--versionPackage and build identity
--mode hostInternal SSH worker speaking NDJSON on stdin/stdout

--web [PORT] and --web-bind ADDR remain aliases for --port and --bind. Non-loopback addresses, including wildcard binds, are rejected. Use an SSH tunnel for remote access; Myco has no HTTPS listener or certificate options. Tunneling, request-origin checks, and the read-only /files/ workspace routes are described in browser. Host workers accept --name and --max-image-base64-bytes, supplied by the server when it attaches a remote. The local host is always in-process.

Profiles put config, sessions, images, workspace, and manual under $MYCO_HOME/profiles/NAME/; MYCO_HOME defaults to ~/.myco. Local tool processes inherit absolute MYCO_HOME and the selected MYCO_PROFILE even when they change directories. In server mode, every existing profile has an independent instance under /profiles/NAME/, sharing one loopback port. Launch overrides apply only to the selected profile; other instances use their own config. Local tools also receive MYCO_SERVER_URL for their instance's API. Child sessions created at that URL share its profile. Remote workers need no model credentials. CLI modes continue to run only the selected profile and inherit the directory where you launched the command.

.env in the launch directory is loaded at startup. Configure at least one model before starting; the overview describes the catalog. Browser controls, attachments, archived sessions, and automation are documented in browser.

Ctrl-C stops the server, cancels active turns, and saves their recorded outcomes. Closing a browser tab leaves its session running. Reopen session URLs directly after restarting; no login is needed. Saved session URLs keep the same port; use a fixed port for bookmarks.

One-shot prompts

myco -p "Review this repository"
git diff | myco -p "Review this diff"
cat task.txt | myco -p
myco -p "Continue the review" --resume SESSION_ID

With an explicit prompt, piped stdin is prepended as context. Empty input fails with exit code 2. Only explicit prompt text expands @./image.png attachments; piped text is treated literally. -p conflicts with explicit server flags and --mode.

Stdout contains streamed assistant text, including narration between tool calls. Thinking, tool output, compactor output, and session metadata are excluded. Diagnostics and session=ID go to stderr. Exit codes are 0 for success, 1 for runtime/provider/output errors, 2 for input/config errors, and 130 for Ctrl-C. Cancellation settles tool results and persists the session before exiting.

Terminal chat

myco --mode cli
myco --mode cli --resume SESSION_ID
myco --mode cli --model MODEL_KEY

Enter submits; Alt-Enter inserts a newline. The line editor supports navigation and in-memory input history. Ctrl-C clears the input or cancels the running turn; Ctrl-D exits at an empty prompt. Piped input submits one line per turn. A failed or cancelled turn returns to the prompt.

CommandAction
/helpShow terminal controls
/sessionShow the session ID and model
/compactCompact into a new thread in this session; return to the prompt
/quit, /exitExit

Assistant text goes to stdout; tool activity and diagnostics go to stderr. Tool inputs show each top-level field separately. Long tool output is abbreviated; complete observations remain in saved session history. Use the browser to browse old messages, manage sessions, or switch models during a conversation.

Both terminal modes share the browser's checkpoints, writer locks, attachment limits, and automatic compaction/continuation. --resume uses the saved model unless --model overrides it. A session already open in another process cannot be written concurrently. Resume restores conversation history, not live shells or editor state; terminal processes own their tools until exit.

Browser UI

Start the server, then open the URL it prints:

myco
myco --port 8766 --profile research
myco --port 0 --config /path/to/config.toml --resume <session-id>
myco --bind ::1

The HTTP server uses port 8765 by default; --port 0 picks a free port. The server binds to 127.0.0.1 unless --bind names another loopback IP (localhost selects IPv4). Non-loopback addresses, including 0.0.0.0 and ::, are rejected. Open the printed URL directly; there is no login, token, or browser cookie. Assets and Markdown rendering are bundled with myco, with no frontend build step or CDN. Stop the server with Ctrl-C in the launching terminal.

For remote access, start Myco on the remote host, then open an SSH tunnel from the computer running your browser:

ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:8766:127.0.0.1:8765 user@remote-host

This forwards local port 8766 to the remote server's default port 8765. Change the printed launch URL's address to http://127.0.0.1:8766, keeping its profile and session path. Keep SSH running while using Myco. If either port differs, adjust the command and browser URL to match. For a remote server bound to ::1, use [::1]:8765 as the forwarding destination.

Myco does not serve HTTPS or manage certificates. SSH provides encryption and host authentication between the two computers; HTTP stays on each loopback connection. Keep the forwarding listener on 127.0.0.1 or ::1. Files, actions, and live event streams work through the tunnel, including when its local and remote ports differ. Requests must address localhost or a loopback IP; LAN names and other hostnames are refused.

Profiles

One server hosts every existing profile under $MYCO_HOME/profiles/, plus the profile selected at launch. Each has its own URL prefix: /profiles/default/, /profiles/research/, and so on. Names contain only letters, digits, hyphens, or underscores. The selector beside myco switches profiles; /profiles/ opens a chooser. Opening an unknown name does not create it.

Each profile has an independent config, session and image store, prelude, and tool runtime. Its instance starts when first opened, in a separate worker process connected over a private Unix socket. The browser shares one live event connection across profiles and tabs. A failed profile does not stop another profile's work; its next request restarts the instance, and open session tabs reconnect. Recorded history survives, but live tools and interrupted runs are not automatically replayed. Ctrl-C stops every instance and saves active sessions.

--profile (or MYCO_PROFILE, default default) selects the initial profile; it does not restrict the available profiles. --config, MYCO_CONFIG, --model, --effort, --resume, and request-debugging overrides apply only to that profile. Other instances load their own config.toml with the normal defaults. The launch directory's .env is loaded once by the server. Configuration changes require a restart. Relative --config and MYCO_CONFIG paths resolve from the server's launch directory. Relative credential-file paths inside a config resolve from that profile's workspace; absolute paths and ~/ retain their meaning.

Each instance uses $MYCO_HOME/profiles/NAME/workspace/ for local tools and served files; MYCO_HOME defaults to ~/.myco. Missing workspace directories are created on first use. Existing workspace contents stay in place. If the directory cannot be opened, that profile reports a startup error while other profiles remain available. Terminal chat and myco -p keep their launch directory. Profiles separate application state; they are not operating-system sandboxes or access-control boundaries.

URLs generated by the browser, including attachments, Markdown files, new tabs, and resumed sessions, stay under their profile prefix. Page titles include the profile name. Existing unprefixed API and file URLs still address the selected launch profile; old session URLs redirect there. Prefer prefixed URLs in bookmarks and automation so their meaning does not depend on the next launch's --profile.

Appearance and session browser

The browser uses translucent panels with square corners over a locally rendered sky. Their tint follows the same light: cool blue in daylight, warm rose and amber around dawn and dusk, and deeper blue at night. Overcast weather softens the tint. The conversation stays in a central well, with the sky visible on both sides; its glass background continues below the viewport during native scroll bounce. Floating controls use background blur. The conversation well is the lightest surface; the input bar and top banner share darker translucent glass, with dialogs darkest in front. Translucent clouds drift slowly in three layers: fine high wisps near the top, soft middle billows below them, and broader low clouds near the horizon. Broken banks vary in shape and height; increasing cover adds thin, broad veils. Their feathered edges and overlapping layers let the sky show through. Gentle directional light travels across the clouds through the day, softening in overcast weather. Colors follow daylight, sunset, and night; stars twinkle behind the clouds. An occasional distant airplane crosses the sky, with a faint contrail or navigation lights after dark. These are decorative flybys, with at most two visible at once. Wet weather adds fine rain streaks at several depths; wind influences their slant, and heavier precipitation increases their density. Overcast skies darken the clouds and obscure stars and airplanes. Reduced-motion settings keep clouds and stars still and disable airplanes and falling rain. Hidden tabs pause animation and airplane arrivals. The bundled renderer is adapted from Horizon, with its MIT license retained in the served source.

The Settings icon in the top bar opens a centered modal. Its Sky section contains the weather controls. Choose a city or explicitly select Use my location to reflect its current cloud cover, rain, showers, and wind from Open-Meteo. The panel names the reported weather condition. The layers roughly represent below 3 km, 3–8 km, and above 8 km. Surface wind influences the deliberately slow drift; the art is an impression of modelled current conditions, not a view of individual real clouds. Rain intensity accounts for the feed's accumulation interval; a drizzle or rain code can still produce light streaks when its amount rounds to zero. Snow-only reports do not produce rain. The approximate day/night cycle and direction of light follow that location's clock and update once a minute; they do not calculate seasonal sunrise, sunset, or the position of the moon.

The location is saved in this browser per profile and shared across that profile's tabs. A previously saved location is retained for default. Coordinates are rounded to two decimal places before Myco forwards them to Open-Meteo. City searches also pass through Myco, with location data from GeoNames. The browser contacts only Myco. Weather refreshes every 15 minutes while visible; the server coalesces requests and keeps bounded caches. The free weather service is for non-commercial use.

With no selected location, Illustrated sky uses decorative clouds and the browser's clock without weather requests or location permission. This is also the fallback when weather is unavailable; settings label any retained conditions as last available, and discard them after two hours. Device location requires browser permission and a loopback browser URL; city search does not need device permission. Weather failures do not affect conversations.

SSH controls remote access. Any process or user able to reach the loopback port can use the sessions, tools, and workspace files; there is no per-user permission model. The server retains Host and browser-origin checks to reject requests from other websites. Restarting does not require signing in again.

The home page lists your visible, unarchived sessions, with search, recent update times, and live activity indicators. A pulsing square beside Running in the session header and browser list means a run is in progress, including time waiting for model output and executing tools. Background tasks has a steady indicator when tools remain open after the run. Ready, Stopped, and Saved are idle; Reconnecting… means the current state is unknown. Reduced-motion preferences disable the pulse. Status changes arrive through the shared event stream; the browser also polls for changes made by other server processes.

New session opens a separate tab, creates a session, and navigates that tab to its /profiles/NAME/sessions/<id> URL. Session links work with bookmarks, middle-click, and browser tab groups. Click the myco name to return home. --resume <id> opens that session directly from the launch URL.

Click Archive in the session toolbar to return to that profile's session browser. The browser shows Session Archived. with an Undo button that restores the session to the active list. Undo remains available after a refresh. Archiving from a row in the session browser shows the same confirmation. Choose Archived sessions above the list to find archived sessions and Restore them. Archiving preserves history, the session URL, and running tools; an open tab can continue its turn. It does not archive child sessions. A session held by another myco process must be archived from that process or after it closes. The list refreshes while the home page is visible; changes made outside this server can take up to ten seconds to appear.

The launcher options are documented in cli.

Conversation controls

The floating input bar stays pinned while the conversation scrolls. Enter sends; Shift-Enter or Alt-Enter inserts a newline. Use Attach, paste an image from the clipboard, or drop images onto the composer. Preview and remove images before sending; an image-only message is allowed. PNG, JPEG, GIF, and WebP are supported, with up to 20 selected or pasted images per message. Bytes determine the media type. The browser sends the images when you press Send or Queue; rejected sends keep the draft and attachments for retry.

Mention @path/to/image.png to attach a file on the server instead. Each image is limited by the model’s max_image_base64_bytes (default 5 MiB of base64), and all attachments in one message share a 20 MiB budget, including @path images. Bad paths or oversized images fail before the model request. Image paths with spaces are not supported in @path mentions; selected files may have spaces in their names. Accepted uploads use the profile's image store and remain available in saved sessions after restart. Unsent attachments stay in the current tab's draft.

Submitted messages enter one server-owned queue, drained immediately when the session is idle. During a turn, Queue accepts follow-ups in submission order. The composer shows pending messages. They join the next model request after the current tool batch finishes, alongside its recorded results; if no tools are running, they are sent when the current response finishes. Up to 20 messages can wait per session, shared across its tabs and preserved when a tab refreshes or closes.

Edit holds a queued message in its original position and opens its text and images in the composer. Save & send releases the edited message for delivery; Discard edits releases the original. Both restore any draft you had before editing. Messages behind a held edit wait until it is saved, resumed, or removed. Unqueue removes a pending message. A message already claimed for delivery cannot be edited or removed.

Held messages remain visible after a tab closes or reloads; use Edit to continue from the last saved text, or Resume to send it unchanged. Unsaved text and image changes stay in the current tab. Changes from another tab never overwrite your local edits; you can explicitly send those edits as a new message or discard them. If a response is lost, retrying a save does not send it twice.

Cancel & send queued stops the current run, records cancelled tool results, and sends ready messages with a fresh cancellation token. Held edits stay paused. Queues live in the running server and are not restored after a server restart. A rejected submission keeps its draft. Queued image thumbnails are visible across session tabs and after a tab reload.

The page heading and browser tab title follow the session title, including the first-message title and agent renames during a running turn. Hover over a truncated heading to read its full title. The top bar aligns with the conversation and keeps its controls on a separate row on narrow screens. Activity counts appear only while tools or background sessions are running.

Each tool appears as soon as execution starts, with a truncated argument preview in its collapsed header. Running calls are cyan; completed calls turn green, and failures red. Expand a block to inspect its complete recorded input, output, images, and outcome. Input shows each top-level key as a bold label above its value. Strings retain their newlines without JSON quoting; nested objects and arrays retain JSON structure. There is no browser verbose mode.

Thinking traces appear in muted italic text with a vertical bar on the left. Expand Thinking to read a trace; click anywhere in the expanded trace to collapse it. Its heading also supports Enter and Space for keyboard control.

Tool calls show elapsed execution time to tenths of a second in their headers and in the Activity drawer. Running timers update every 0.1 seconds and survive page refreshes; completion, failure, or cancellation freezes the final duration. Durations are observed by the running browser server and retained while viewing the same thread. Saved history opened after a server restart has no timing data.

Activity opens a right-hand drawer with separate sections for active tool calls and local background tasks, such as bash sessions that continue between turns. The drawer starts closed; its button shows the current activity count. Click an active call to close the drawer and open its block. Close the drawer with its close button, Escape, or a click outside it. Background-task summaries refresh every second without consuming tool output, and disappear when the task ends. Background summaries cover the local host; active calls include remote tools too.

Running shell calls offer Background in their tool header and in Activity. It releases that call's foreground wait, so the assistant can continue once any other calls in the same batch finish. The process keeps running and its result records a session_id for later bash reads or close on the same host. A backgrounded one-shot exec keeps its original closed stdin; use read, signal, or close, not write. Backgrounding an existing shell-session read or write returns its current output without stopping that session.

Background is separate from Cancel: subsequent turn cancellation does not stop an already backgrounded process. Its original exec timeout no longer applies; the process runs until it exits, is closed, or its owning runtime/host ends. Backgrounding does not start another process, so it may retain an already running exec even when the admission limit for new shell sessions is full. Tabs reconnect to the same background tasks, but server restarts do not restore processes. Local tasks remain visible in Activity; remote handles are recorded in their tool result and can be queried on that host.

Manual and automatic compaction update the activity indicator to Compacting. Compaction cards, summaries, internal resumption instructions, and prelude-change notices are hidden from the conversation, including after reload. Automatic compaction continues the task; manual compaction returns to ready. Failures remain visible so a stopped or unsuccessful operation can be diagnosed.

Assistant responses render Markdown headings, lists, tables, task lists, blockquote text, code blocks, links, footnotes, and images. Adjacent text parts in an assistant response are rendered together, keeping Markdown intact after streaming and page reloads. Footnotes stay within their message and open in the current tab. Text uses one font size; headings use weight and underlines. Tables size to their contents, wrap long descriptions, and keep Markdown's column alignment. Wide tables scroll within the conversation; focus a table to scroll it with the keyboard. Plain HTTP(S) and www. URLs become clickable in messages, queued messages, and tool output, and open in a new tab. Inline and fenced Markdown code stays literal. Bare <br>, <br/>, and <br /> tags create line breaks, including inside table cells. Other raw HTML is displayed as text. Markdown images can reference HTTP(S) URLs, supported image data URLs, or local files. The server rewrites workspace image and file links to /files/ URLs, including browser formats such as SVG and AVIF. Relative paths resolve from the addressed profile's workspace; absolute paths, ~/, and file:// URLs inside that directory also map to /files/. URL-encode spaces and special characters, or use Markdown's angle-bracket syntax for paths with spaces. Saved images use the profile's image store. Explicit PNG, JPEG, GIF, and WebP paths outside the workspace use the /api/image endpoint.

GET /files/path/to/file and HEAD expose regular files below the addressed profile's workspace, including dotfiles. Anyone able to reach the loopback port can read them. Traversal and symlinks that escape that directory are refused. Files stream without loading the whole document into memory; a single byte range supports media seeking on GET requests. HEAD always describes the complete file. With If-Range, the server returns the full file because it does not issue validators. A directory with index.html displays that file; directories without an index are not listed. Workspace files have no upload or write route; message attachments are stored separately in the profile's image store.

Workspace HTML and SVG have their own restrictive content policy. Static HTML, relative images, and styles render; scripts, forms, and embedded frames are disabled. This keeps generated or checked-out content from executing with the conversation UI's access to the local API. Interactive applications need a separate preview origin instead of weakening this policy on the Myco origin.

USER and ASSISTANT headers show the browser's local date and time on the next line, followed by elapsed whole minutes in parentheses, such as (2 minutes ago). The age updates while the page is open and when returning to a hidden tab; hover over the timestamp to see the full local date, time, and time zone. Saved timestamps remain UTC. A reply that starts with tools gets its ASSISTANT header before those calls, live and on replay. Recorded messages use the turn's saved acceptance time; older turns without one show unknown. The input box has no timestamp.

The model selector in the input bar lists the active configuration's model keys. Choose a model between turns to use it for subsequent requests and compaction. Switching preserves conversation history and live tool sessions, and records the change in the session. An unavailable model leaves the current selection unchanged and shows an error. The selector does not change the configured startup default.

The input bar shows Context used versus the selected model's capacity and the Input, Output, and Cached token counts. Context and Input use the latest provider-reported input count, including cached tokens; Cached is a subset, not an additional cost to the context window. Output accumulates across the current or most recent run's model requests, including tool round trips. These are recorded measurements, not a live estimate of the next prompt: output and newly added messages are not included in Context until the next request reports them. Hover over a count for its exact value and meaning.

Counts refresh during a run after each completed model request is checkpointed, and survive page reloads and server restarts. A dash means no measurement is available. Compaction and model changes invalidate the old counts until another request reports usage. A failed or cancelled request retains the last recorded measurement if its context is unchanged. These counts are not session-wide billing totals.

New opens a fresh session in a new tab; the current tab, draft, and running turn stay in place. Compact creates a successor thread without changing the session URL. These controls also accept /new, /compact, and /resume <id> in the input; /resume navigates only the current tab. Other slash commands are not available.

Automatic compaction is enabled per model with auto_compact_at, a fraction of its context window (for example, 0.8). Without this setting it is disabled. When the threshold is reached, the runner settles pending tools, saves a summary in a successor thread, and continues the task automatically. Queued follow-ups join that continued context. Manual Compact finishes after creating the thread and waits for your next message. Failed or ineffective automatic compaction shows a warning and disables further attempts until manual compaction or a session change; cancellation preserves the source thread.

Running and resuming

One server can run several sessions concurrently. Each has its own runner, model selection, cancellation, tool state, and writer lock. Tabs on different session URLs work independently; tabs on the same URL observe the same run. Changing a model or compacting is disabled during that session's turn. Another browser server cannot write an opened session while this server holds its writer lock. An unavailable or locked session shows an error without interrupting other tabs.

Refreshing, navigating away, or closing a tab does not cancel its active turn. Reopening its session URL reconnects to its output and live tools. Cancel run affects only that session; Ctrl-C in the server terminal cancels all active turns. Opened sessions and their locks stay alive until the server stops. Request retries do not submit the same action or create the same session twice. If submission fails, the input draft remains available. Tabs share a live-output connection through a browser SharedWorker so a tab group does not exhaust HTTP connections. Only tabs viewing a session fetch its full history. Live events carry deltas and small refresh notices; a slow connection resynchronizes subscribed views without replaying every running session's history. Activity bursts coalesce into a session list refresh, and each session view allows one snapshot request at a time. Reload open tabs after upgrading the server to use the updated browser worker.

Restarting the server and resuming a saved session restores conversation history, not old bash processes or editor state. Uncertain tool outcomes from an interrupted process are recorded without rerunning their effects. Compaction within a running session preserves live tools. After a server restart, reopen your saved session URLs directly. Keep the same port to reuse bookmarks (--port 0 chooses a new port each time).

Server API and automation

Automated clients use the same loopback HTTP API as the browser. No credential or login request is needed. Use the local forwarding address when connecting over SSH. POST bodies use Content-Type: application/json.

The paths below are relative to /profiles/NAME. For example, list research sessions with GET /profiles/research/api/sessions. GET /api/profiles lists available names and URLs. The browser's global /api/profile-events stream tags updates with their profile; the scoped /api/events stream retains its session-only protocol for automation.

Local tools receive MYCO_SERVER_URL, the current instance's full URL without a trailing slash. Use $MYCO_SERVER_URL/api/... for nested sessions so they stay in the parent's profile. Read this value from the tool environment instead of saving a port number in conversation history; a restart can change the port.

Browser requests must come from the same origin. A supplied Origin must match the loopback address and port in Host; cross-site and cross-port browser requests are rejected, and no CORS access is granted. Native clients can omit Origin and Fetch Metadata. Access to this API includes session tools and shell execution.

RequestBody / result
POST /api/sessions{"request_id":"UUID"} → {"id":"SESSION_ID"}
GET /api/sessionsVisible sessions with busy and status; add ?archived=true for archives
GET /api/sessions/IDSnapshot at change.snapshot, including busy, status, blocks, queued, usage, and context_window_tokens
POST /api/sessions/ID/action{"request_id":"UUID","session_id":"ID","action":{"kind":"submit","text":"PROMPT"}} → 202 accepted
POST /api/sessions/ID/actionThe same envelope with {"kind":"compact"} or {"kind":"select_model","key":"KEY"}
POST /api/sessions/ID/actionThe same envelope with {"kind":"update_queued","message_id":"UUID","revision":0,"update":{"kind":"edit"}}
POST /api/sessions/ID/cancel{"session_id":"ID"} → 204
POST /api/sessions/ID/background{"session_id":"ID","call_id":"UUID"} → 202; use the running tool block's background_id
POST /api/sessions/ID/archive{"session_id":"ID","archived":true} → 204; false restores
GET /api/eventsServer-sent events with session IDs, revisions, and changes
GET /files/PATH, HEAD /files/PATHProfile workspace files; GET supports a single Range: bytes=START-END

Use a fresh UUID per operation and reuse it when retrying that operation. Submit actions may include images, an array of base64 image data URLs or existing image-store references from this profile. text may be omitted when images are present. The server validates image type and size, then keeps image-store references in its queue and history. Snapshots include attachment_limits for the selected model; queued messages include their image references. The action route allows up to 22 MiB of JSON for the image budget and text envelope; other JSON routes retain their 2 MiB limit.

Each queued entry has request_id, revision, and state (ready, editing, or sending). An update_queued action names that entry and its current revision. Its update.kind is edit (hold), save (replace text and images and release), resume (release unchanged), or remove. Each accepted edit, save, or resume increments the entry revision. Saved messages retain their original acceptance time and queue position. Stale revisions, removed entries, and messages already claimed for delivery return 409. Reuse the mutation's request UUID to retry it; the message UUID remains its original submission ID.

Creation uses the UUID as the session ID and survives restart without creating a duplicate. Action deduplication lasts for the running session worker; after restart, inspect the saved snapshot before deciding whether to submit again. A 202 response means accepted, not finished. Wait until the snapshot is idle, then inspect its output and errors and verify the task's artifacts.

To create a hidden child, include "parent_session":"PARENT_ID" in the creation body. The parent must exist in the addressed profile. Add "fork":true to copy its saved context; a fork requires a parent. Children have independent runners and tool state, remain hidden from both normal and archived listings, and can be opened directly by ID. Retrying creation keeps its original context. Model selection is a separate action before submission.

For example, a Python client can create a session using only the standard library. Supply the launch URL through MYCO_LAUNCH_URL; when using a tunnel, first change its address to the local forwarding address:

import json, os, urllib.parse, urllib.request, uuid

launch = os.environ["MYCO_LAUNCH_URL"]
url = urllib.parse.urlsplit(launch)
origin = f"{url.scheme}://{url.netloc}"
parts = url.path.split("/")
assert len(parts) >= 3 and parts[1] == "profiles", "Use the printed profile URL"
base = origin + "/profiles/" + parts[2]
client = urllib.request.build_opener(urllib.request.ProxyHandler({}))

def post(path, payload):
    request = urllib.request.Request(base + path,
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"})
    with client.open(request) as response:
        return response.read()

session = json.loads(post("/api/sessions", {"request_id": str(uuid.uuid4())}))["id"]
post(f"/api/sessions/{session}/action", {
    "request_id": str(uuid.uuid4()), "session_id": session,
    "action": {"kind": "submit", "text": "Explain this repository"}})

API-created sessions use the addressed profile's workspace and runtime; no new process or model configuration is needed per child.

Harness ops

How to inspect, install, and repair this runtime (agent + host pool). Use when tools fail, hosts look wrong, or the user asks you to update myco / explain the harness.

Host PATH prerequisites

Same executables as the README Install section (extra binaries on PATH). Install on each machine that runs the agent and/or host tools; remotes need what their host tools spawn, not only the agent laptop.

Required

  • ssh — attaches remotes (ssh … myco --mode host over NDJSON) and is used for install/diagnose over SSH.
  • uv — hermetic Python runs (agent computer-use norm: scripts and deps without polluting the system).
  • bash — host bash tool (one-shot exec and multi-turn shell sessions).

Recommended

  • git — worktrees/branches, repo inspection, and git archive when shipping local source to remotes.
  • gh — GitHub CLI for PRs, issues, and release workflows the agent often drives.
  • curl — downloading release source tarballs.
  • ck — semantic / hybrid code search (cargo install ck-search); agents use it via bash where installed, alongside rg.

Also needed when building from source: stable Rust / cargo (and curl as above).

Finding configured hosts

  • Local is always present (in-process); it is never configured.

  • Remotes are the concrete Host aliases in ~/.ssh/config (Includes are followed; wildcard */? and negated ! patterns are ignored; alias local is reserved). Host name == alias == SSH destination.

  • ~/.myco/profiles/default/config.toml (or $MYCO_CONFIG / myco --config) holds knobs only: attach_timeout_secs, max_prelude_bytes.

  • Read ~/.ssh/config with tools when you need remote names or SSH destinations.

  • Connection sharing: every myco process (the supervisor and each nested agent) opens its own ssh <alias> myco --mode host per remote it touches. OpenSSH multiplexes them over one authenticated connection per host with:

    Host *
        ControlMaster auto
        ControlPath ~/.ssh/cm-%r@%h:%p
        ControlPersist 10m
    
  • Inspect tool errors and use the SSH checks below to diagnose remote attachment.

  • Host tool field host must match a configured name (local or a remote name). Omitted → local.

Updating / installing myco

Local uses the agent process binary / in-process worker — rebuild/reinstall the interactive myco on this machine and restart the server.

For remotes, prefer a same-platform binary (release asset or build on the target) when you are not actively developing myco; only build from a local git tree when you are working on the myco codebase itself (unreleased commits, dirty worktree, or a feature branch that must ship). Do not scp/rsync a prebuilt binary across mismatched OS, CPU arch, or glibc (e.g. newer glibc → older cluster fails with GLIBC_X.Y not found); for those targets, compile on the machine or use a matching release.

Choose: release snapshot vs local source tree

  1. Inspect the running agent binary (this process):
    • session_meta with action: "executable_path" → absolute path of the agent myco.
    • Then via bash: "$path" --version (package version from clap / CARGO_PKG_VERSION).
  2. If you are currently working on myco (cwd is a myco checkout, or the user asked to deploy local unreleased changes): build from that git tree (see Snapshot the repo below) so remotes match the working tree.
  3. Otherwise (normal install / update): download a source snapshot from GitHub Releases for the version you want (usually the same as the local binary's --version, or a newer release the user named):
https://github.com/tsnl/myco/releases

Typical assets / URLs (GitHub):

# Prefer the release tag that matches (or is newer than) local `myco --version`.
# Source tarball for tag v0.1.0 (example):
VER=0.1.0
curl -fsSL -o /tmp/myco-src.tgz \
  "https://github.com/tsnl/myco/archive/refs/tags/v${VER}.tar.gz"
# Or the auto "Source code (tar.gz)" asset on the release page for that tag.

Unpack on each remote and cargo install there (next section). If no suitable release exists yet, fall back to archiving the local git tree.

Snapshot the repo (local git tree only)

Use this when deploying work-in-progress or unreleased commits from a myco checkout.

From the myco git root, create a source snapshot with git archive (tree only — tracked files at a commit/tree; not untracked files, and not uncommitted dirty work unless you archive a commit that includes them):

# Clean committed snapshot of HEAD (usual case):
git archive --format=tar.gz -o /tmp/myco-src.tgz HEAD

# Include current dirty tracked changes: temporary tree object, then archive it:
REF=$(git stash create)   # empty if worktree is clean — fall back to HEAD
git archive --format=tar.gz -o /tmp/myco-src.tgz "${REF:-HEAD}"

git archive is the right “tarball of this repo state” tool. Commit (or use git stash create) first if the install must include uncommitted edits. Untracked files are never in the archive; add/commit them if they are required to build.

A release only needs platform-matched binaries. Do not scp binaries across mismatched OS/arch/glibc; for those hosts, build there or use a matching release asset. Builds are fully offline beyond the crates.io dependency fetch.

Install on each remote host (build from source)

HOST=devbox   # Host alias from ~/.ssh/config (== myco host name)
# /tmp/myco-src.tgz is either a GitHub release source tarball or a local git archive.
scp -o BatchMode=yes /tmp/myco-src.tgz "$HOST:/tmp/myco-src.tgz"
ssh -o BatchMode=yes "$HOST" 'set -euo pipefail
  rm -rf ~/src/myco-src ~/src/myco-src-extract
  mkdir -p ~/src/myco-src-extract ~/.local/bin
  tar -xzf /tmp/myco-src.tgz -C ~/src/myco-src-extract
  rm -f /tmp/myco-src.tgz
  # GitHub release tarballs nest under myco-<tag>/; `git archive` is usually flat.
  entries=(~/src/myco-src-extract/*)
  if [ ${#entries[@]} -eq 1 ] && [ -d "${entries[0]}" ]; then
    mv "${entries[0]}" ~/src/myco-src
    rmdir ~/src/myco-src-extract 2>/dev/null || rm -rf ~/src/myco-src-extract
  else
    mv ~/src/myco-src-extract ~/src/myco-src
  fi
  export PATH="$HOME/.cargo/bin:$PATH"
  command -v cargo >/dev/null || { echo "cargo/rustc required on host"; exit 1; }
  cargo install --path ~/src/myco-src --force --locked --root "$HOME/.local"
  # ~/.local/bin is the usual remote install path in multi-host setups
  ~/.local/bin/myco --version
'
  • Require Rust/cargo when building from source. Prefer a prebuilt same-platform binary when available.
  • Remotes need myco on the remote PATH used by non-interactive SSH (BatchMode); ~/.local/bin or ~/.cargo/bin are common — verify with ssh -o BatchMode=yes <alias> 'command -v myco; myco --version'. An interactive login can resolve a different binary; check the non-interactive command used by the worker.
  • After replacing binaries, the server must be restarted to load a new agent binary; remote host workers respawn on next tool use.
  • Ask before destructive remote installs; prefer installing into user prefixes (~/.local, ~/.cargo) over system paths.

Diagnosis checklist

When tools fail or the user asks why something is broken, investigate with tools:

  1. Host down / unavailable

    • Local never needs a host subprocess; if local tools fail, debug the agent process itself.
    • Read ~/.ssh/config for Host aliases (remote names == destinations); the selected profile's config.toml (or $MYCO_CONFIG) only for knobs.
    • On remote: ssh -o BatchMode=yes <alias> 'command -v myco; myco --version' via the local host's bash. Compare that path and version with the expected install; connection requires matching package and host-protocol versions. A protocol mismatch names the host to rebuild before tool calls can run. An interactive login may use a different PATH. If missing/outdated: install a binary built for that platform (matching release asset), or build on that host from source. Do not copy binaries across mismatched OS/arch/glibc.
    • Confirm SSH alias works: ssh -o BatchMode=yes <alias> true.
    • Startup checks expected executables on the agent machine (bash, ssh/ssh-add/ssh-keygen when remotes are configured) and reports missing ones in the startup WARNING block — the user must install them and restart myco. Remote hosts report missing programs as tool errors at call time.
    • If auth fails with BatchMode: check ssh-add -l and the startup ssh-agent preflight (silent when clean; problems open a WARNING block before the first USER block). Unlock with ssh-add / ssh-add --apple-use-keychain <key> (myco cannot prompt on the NDJSON pipe). Restart myco after loading keys.
    • Retry the tool after fixes; restart the server if its configuration changed.
  2. Wrong machine / wrong files

    • Check whether host was set; default is always local.
    • bash uname -n / pwd / hostname on the intended host.
  3. Session / state confusion

    • Conversation resume ≠ restored bash sessions or editor state.
    • Bash sessions die when the host process exits (server exit, host crash, SSH drop). Local in-process sessions die with the agent process.
  4. Explain product limits honestly

    • No heartbeat in V1: remote liveness is next tool error; local is always in-process.
    • No mid-flight cancel over the host pipe yet; Ctrl-C cancels the agent turn locally.
    • You cannot invoke slash-commands; tell the user which to run.

When helping the user change config, prefer surgical edits to ~/.ssh/config (hosts) or ~/.myco/profiles/default/config.toml (knobs) and show a minimal diff. Ask before destructive remote installs.

Task evals and prelude optimization

myco-eval creates frozen task cases and runs them through the same SessionRunner as myco. It is a separate process: run it from a terminal, cron, or a service without leaving an interactive conversation open.

Build a case from a session

An agent can use session_meta list or list_recent to find sessions, then session_history range to locate the human request to replay. Choose its zero-based message index, before the answer or tool actions that solve it. Write an independent Python grader before exporting:

myco-eval --profile default from-session SESSION_ID /private/evals/fix-parser \
  --user-message 12 --thread THREAD_ID \
  --repo /path/to/repo --revision COMMIT_BEFORE_THE_FIX \
  --grader /private/grader.py --split test

The command copies that thread's prefix through the chosen request, referenced images, the grader, and the pinned workspace recipe. It excludes later answers, other threads, and session metadata. Source sessions are not modified. Inspect context.json before adding a case to a dataset: earlier context or compaction summaries may already contain a solution. Source history is private data; keep cases and results outside public repositories unless reviewed for publication.

A session is conversation context, not a filesystem snapshot. Pick the starting commit explicitly. Earlier shell handles and editor state are not restored. Adapt tasks that depend on external services or unavailable files. Git workspaces require the local repository and commit to remain available; submodules, dependency installation, and external fixtures need preparation.

For a new task, or a small directory snapshot:

myco-eval create /private/evals/example --task-file task.md \
  --workspace ./fixture --grader grader.py --split train

Fixture copying excludes .git, .myco, target, and __pycache__, and rejects symlinks. Runs initialize a fresh Git repository around the copied fixture so project guidance from the eval runner's parent directories is not inherited. Use a pinned Git case for source trees with symlinks.

Grade actual work

A case is a directory containing case.json, context.json, grader.py, and optional workspace/ and images/. Manifest version 1 specifies the name, split (train, validation, or test), workspace recipe, and grader argv. The default command is python3 {case}/grader.py; edit grader in case.json to use another program. Arguments are passed directly, without a shell.

The grader runs in the completed workspace. MYCO_EVAL_WORKSPACE gives that path and MYCO_EVAL_RUN points to the run artifacts (including agent.json). It must exit 0 and print exactly one JSON object:

{"score": 0.75, "feedback": "Three of four checks pass; the empty input case fails."}

Scores range from 0 to 1. Test artifacts, repository tests, or explicit task criteria; do not score the assistant's claim that it succeeded. Missing expected artifacts should produce score 0 with feedback. A crashed, timed-out, or malformed grader is an infrastructure error, recorded separately from an agent failure. The frozen inputs and grader are checked for changes before and after grading.

Run and compare

myco-eval run /private/evals --config ~/.myco/profiles/openrouter-free/config.toml \
  --model ling-free --free-only --output /private/results/baseline \
  --prelude candidate.md --repeat 3 --jobs 1 \
  --max-requests 40 --timeout-secs 600 --grader-timeout-secs 60
myco-eval report /private/results/baseline --min-success-rate 0.8

--model can be repeated for comparisons. --split selects a dataset split. --free-only accepts only explicit OpenRouter model IDs ending in :free, at OpenRouter's API URL, with no router or paid-model fallback. Without it, the selected configured models determine spending. Each worker has its own request budget and deadline; multiply these by cases, models, and repetitions when planning an unattended run. Limit concurrency to match provider rate limits.

Each attempt has a fresh workspace and profile. The supplied prelude is the entire candidate; the user's profile prelude is not inherited or edited. Local standard tools and session tools are available. Remote hosts and nested model runs are not configured. Tools retain ordinary Myco computer access: these workspaces and separate graders are organizational boundaries, not containment. Use your own container/environment when needed.

Runs keep job.json, result.json, agent.json, events.jsonl, worker/grader logs, the workspace, and a normal session store with image sidecars. Credentials are read from the named config/auth sources and are not copied into artifacts. Do not put credentials in gateway URLs. Full traces can still contain private work data or secrets encountered by the evaluated task.

Re-running the same command reuses finished results whose case, model, prelude, limits, repetition, and Myco build fingerprints match. Interrupted attempts are retained and retried in fresh workspaces; a still-running worker prevents reuse. Use a new output directory for fresh stochastic samples. Model aliases and external services can change independently of these local fingerprints.

Reports separate cohorts with different tasks/settings and group by model, prelude, and split. Success means a normally completed run with score 1. The mean score reports artifact quality, including partial results. Infrastructure errors are separate; a threshold check also fails when they are present.

Token counts sum every reported request, including retries and compaction; requests_without_usage exposes missing reports. Tool counters describe the main agent; tool_errors counts tool errors, while process exit codes are recorded separately in the trace. Latency covers the agent run, including compaction, not workspace setup or grading. Free-only runs report zero estimated model cost. Other costs are unknown unless report --prices prices.json supplies USD per million tokens:

{"model-key":{"input_per_million":1,"cached_input_per_million":0.1,"output_per_million":3}}

The estimate separates cached input from total input. Missing request usage makes the estimate unknown. Provider billing, cache writes, and non-model services can differ; preserve the original token counts alongside estimates. Use repeated cases, held-out tasks, and your own success floor before selecting models for long-running work. The included examples verify the infrastructure; they are not a model leaderboard or evidence about long-task reliability.

GEPA

The repository's evals/gepa/myco_gepa.py adapter optimizes one component: {"prelude": "..."}. GEPA evaluates candidates with myco-eval, reflects on training failures and tool traces, and selects candidates using a separate validation set. Both task execution and reflection use bounded Myco runs and are guarded by --free-only by default. The server has no Python/GEPA dependency.

python3 -m venv .venv-gepa
.venv-gepa/bin/pip install -r evals/gepa/requirements.txt
.venv-gepa/bin/python evals/gepa/myco_gepa.py /private/evals \
  --binary /path/to/myco-eval \
  --config ~/.myco/profiles/openrouter-free/config.toml --model ling-free \
  --seed-prelude evals/seed-prelude.md --output /private/gepa-run \
  --max-metric-calls 12 --max-reflections 4 \
  --max-requests 40 --reflection-requests 8 --timeout-secs 600

The adapter requires separate train and validation cases and rejects the same source session across splits. It does not run or reflect on test cases. Review best-prelude.md, then run it against the held-out test split with myco-eval run --split test. Keep graders fixed across candidates and group related task variants into the same split. Validation helps select a prelude; only fresh held-out tasks measure how well it generalizes. Usage, cost estimates, and traces remain available for each candidate and reflection. The default optimization score is task quality; costs are measured, not hidden in that score.

GEPA checkpoints live in the output directory. Reusing it resumes optimization when inputs match; changed tasks/configuration, seed prelude, or binary require a fresh output directory. The adapter caps reflection calls separately. Budget exhaustion or infrastructure errors stop with diagnostic artifacts; they do not count as an improved candidate. See the upstream adapter interface.

Choose a crate

Myco's workspace has three packages. Start with the smallest one that owns the behavior you need:

You want to…UseBring yourself
Call a model and consume streamed responsesmyco-modelEndpoint, credentials, model settings, messages, retry policy
Run a model/tool loop without the servermyco-agentA GenerativeModel, ToolExecutor, event sink, input, persistence
Reuse Myco's hosts, sessions, and tool servicesmycoApplication startup and resource ownership

Dependencies flow from the application to myco-agent, then to myco-model. Neither lower crate depends on the application. You do not need SSH, terminal rendering, a Myco profile, or a session store to embed the agent.

Use the workspace or a registry release

Within a checkout, examples use the local packages:

cargo run --locked -p myco-agent --example headless

In another project, select a published version available for both libraries:

[dependencies]
myco-model = "0.3"
myco-agent = "0.3"
tokio = { version = "1", features = ["macros", "rt"] }

The example sources also use futures and serde_json; include those when copying the examples. To try unreleased changes, use path dependencies to crates/myco-model and crates/myco-agent in the same checkout, or Git dependencies pinned to the same commit.

These are evolving APIs. Workspace packages share a version and the agent pins its model dependency exactly. Keep both libraries on a compatible release or the same source revision. This site's API reference is built from the same commit as the guide.

A useful reading order

  1. Inference: messages, stream events, and one-attempt drivers.
  2. Agents: tool execution, history, cancellation, and checkpoints.
  3. Application architecture: session and host composition.
  4. Evaluations: a reserved home for the future eval workflow.

Inference API

myco-model provides backend-independent messages and streaming drivers for Anthropic Messages, OpenAI Responses, and OpenAI Chat Completions. It has no dependency on Myco's server, profile configuration, tool runtime, or session store.

Start with GenerativeModel, GenerativeModelConfig, and Message.

Construct a driver

myco_model::new takes a ModelSpec, matching BackendConfig, system prompt, and tool catalog. ModelSpec::key is your application's name for a model; api_id is what the endpoint receives. The backend variant must match the spec's protocol. Credentials and endpoint settings are supplied directly; the library does not read ~/.myco or load .env for you.

This complete example sends one prompt to a Chat Completions compatible endpoint, streams text, and validates the completed response:

use std::io::{self, Write};

use futures::StreamExt;
use myco_model::{
    BackendConfig, Content, ContentDelta, GenerativeModelConfig, Message, MessageAccumulator,
    MessagePart, ModelSpec, OpenAIBackendConfig, Protocol, ThinkingMode,
};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let model = myco_model::new(GenerativeModelConfig {
        model: ModelSpec {
            key: "example".into(),
            api_id: std::env::var("MYCO_EXAMPLE_MODEL")?,
            protocol: Protocol::OpenAICompletions,
            thinking: ThinkingMode::None,
            context_window_tokens: 32768,
            max_image_base64_bytes: 5 * 1024 * 1024,
            max_truncated_resumes: 0,
            auto_compact_at_tokens: None,
        },
        tools: vec![],
        system_prompt: "Give a brief, helpful answer.".into(),
        backend_config: BackendConfig::OpenAICompletions(OpenAIBackendConfig {
            base_url: std::env::var("MYCO_EXAMPLE_BASE_URL")?,
            auth_token: std::env::var("MYCO_EXAMPLE_API_KEY").unwrap_or_default(),
            effort: None,
            max_output_tokens: Some(512),
            ..Default::default()
        }),
    })?;
    let history = vec![Message::UserMessage {
        content: vec![Content::Text {
            text: "What makes a good library interface?".into(),
        }],
    }];

    let mut stream = model.generate(&history);
    let mut response = MessageAccumulator::default();
    while let Some(event) = stream.next().await {
        // This example performs one attempt; errors propagate to the caller.
        let part = event.into_result()?;
        response.push(&part)?;
        if let MessagePart::ContentDelta(ContentDelta::Text { delta, .. }) = part {
            print!("{delta}");
            io::stdout().flush()?;
        }
    }
    let output = response.finish()?;
    println!();
    eprintln!(
        "stop: {:?}; usage: {:?}",
        output.turn_end_reason, output.usage
    );
    Ok(())
}

Run it from the repository with a server and model you have configured:

MYCO_EXAMPLE_BASE_URL=http://localhost:11434/v1 \
MYCO_EXAMPLE_MODEL=YOUR_SERVED_MODEL_ID \
cargo run --locked -p myco-model --example inference

For endpoints requiring authentication, set MYCO_EXAMPLE_API_KEY in the environment. This example makes a real model request. Its model window and output budget are illustrative; adjust them for your endpoint.

Consume the stream

Each generate(&history) call is one attempt. Its stream yields GenerationEvent::Part(MessagePart) or terminates with a GenerationEvent::Failure(GenerationFailure). Dropping the stream cancels the in-flight request.

MessagePart carries message start, content starts/deltas, tool starts/JSON deltas, token usage, and a stop reason. Feed all parts to MessageAccumulator, then call finish() to validate and obtain GenerateOutput. Text shown during streaming is provisional until the whole attempt succeeds.

When incremental output is unnecessary, GenerateOutput::from_generation(model.generate(&history)).await performs that accumulation for you. It returns the error cause but discards retry metadata; inspect GenerationEvent::Failure yourself if you need that metadata.

Own policy above the driver

Drivers do not retry. A failure's retryable flag and optional retry_after are inputs to caller policy. Retry only if no response parts were emitted, with an attempt limit and bounded delay. myco-agent implements that policy; passing retry settings to a backend alone does not create a retry loop.

GenerateError::recovery() distinguishes ordinary retry eligibility at the history level from Recovery::OmitLastMessage, used when input must shrink. It is not a promise that retrying will succeed or an instruction to retry all errors indefinitely.

Preserve message structure

Messages contain user input, assistant output with optional tool calls, or tool results. Results pair positionally: result j answers call j of the immediately preceding assistant message. Keep the ordering and counts when storing, slicing, or replaying history. Drivers generate protocol-specific wire IDs; callers do not need to store provider call IDs.

A tool specification advertises a name, description, and JSON input schema. The model crate never executes the tool. Use the agent crate, or implement your own dispatch loop that preserves the message contract.

Content supports text, images, and thinking blocks. answer_content selects text and image blocks for an answer. For images, provide a URL or a typed data URL; raw base64 is treated as PNG. Token usage may be absent. Cached input tokens are a subset of input tokens, not an extra quantity to add.

Agent API

myco-agent drives model context through generation and tool rounds. The extracted crate is usable today; higher-level service and orchestration interfaces are still evolving. It does not start a server, create sessions, read project guidance, install tools, or persist anything by itself.

The core interfaces are Agent, ToolExecutor, and EventSink.

Supply an environment

An executor advertises tools and dispatches a call asynchronously. It owns resource scope and cleanup. Unknown tools, invalid input, and ordinary tool failures should return ToolResult::err; an advertised JSON schema does not replace validation inside your implementation.

The headless example uses an in-memory echo tool:

struct EchoTools;

impl ToolExecutor for EchoTools {
    fn tool_specs(&self) -> Vec<ToolSpec> {
        vec![ToolSpec {
            name: "echo".into(),
            description: "Return the supplied text.".into(),
            input_schema: json!({
                "type": "object",
                "properties": {"text": {"type": "string"}},
                "required": ["text"],
                "additionalProperties": false
            }),
        }]
    }

    fn dispatch(
        self: Arc<Self>,
        tool: ToolUse,
        cancel: CancelToken,
        _background: CancelToken,
    ) -> Async<ToolResult> {
        Box::pin(async move {
            if cancel.is_cancelled() {
                return ToolResult::err("cancelled");
            }
            if tool.name != "echo" {
                return ToolResult::err("unknown tool");
            }
            match tool.input.as_object() {
                Some(input) if input.len() == 1 => match input.get("text").and_then(|v| v.as_str())
                {
                    Some(text) => ToolResult::text(text),
                    None => ToolResult::err("echo requires a string field: text"),
                },
                _ => ToolResult::err("echo expects an object containing only text"),
            }
        })
    }
}

Calls in one tool round execute concurrently, while recorded results retain call order. Synchronize any shared mutable state in the executor. Implement cancellation for external work; dropping a future alone does not necessarily stop a subprocess or undo a remote operation.

Assemble and run

The example's DemoModel implements GenerativeModel with a deterministic stream: it requests the echo tool, then turns the result into an answer. It makes no network calls. You can replace it with a real driver from myco_model::new, supplying tools.tool_specs() in that driver's config.

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let tools = Arc::new(EchoTools);
    let mut agent = Agent::new(Arc::new(DemoModel), tools, Arc::new(NullEventSink));
    agent.append_input(Message::UserMessage {
        content: vec![Content::Text {
            text: "Use echo to say hello.".into(),
        }],
    })?;

    let answer = agent.run(CancelToken::new()).await?;
    for content in answer {
        if let Content::Text { text } = content {
            println!("{text}");
        }
    }
    // Persist agent.history() here if your application needs durable history.
    Ok(())
}

Run the complete, compiled example:

cargo run --locked -p myco-agent --example headless

The full source is crates/myco-agent/examples/headless.rs. It imports only the two library crates and ordinary async/JSON dependencies.

append_input appends and checkpoints input. replace_context(history, usage) installs existing context without changing tool state or emitting a checkpoint. run(cancel) advances the existing context until the turn ends; it does not add a user message. Its answer is the final generation's text/image content, while history() retains the intervening tool rounds.

Configure policy explicitly

When embedding a configured driver, propagate settings into the agent:

  • set_retry_policy(backend.retry_policy()) for the gateway's retry policy.
  • set_context_window_tokens(spec.context_window_tokens) for the budget exposed to callers; this setter does not enforce a limit or compact history.
  • set_max_truncated_resumes(spec.max_truncated_resumes) for bounded output continuation. This is not a cap on the number of tool rounds.

The model's advertised catalog and your executor must agree. set_tools changes execution only; it does not update a previously constructed model's tool schemas. Rebuild and replace the model when its advertised tools change.

Persist effects and observations

set_checkpoint installs a synchronous callback receiving AgentState and returning Result<(), String>. It runs before effects begin and after their observations settle, including the final answer. Persistence failure stops execution; callbacks should complete their atomic write before returning. Your storage layer owns locking, schema versioning, and stale-writer rejection.

Persist the history, usage, and pending_operation together. A checkpoint with pending tool calls records uncertain external effects and is not valid model input. recover_checkpoint records unknown outcomes for those calls; replace_context validates complete call/result pairs before accepting history. start_run and step expose individual generations and tool batches for callers that need to compact or schedule work at settled boundaries. Myco's SessionRunner supplies the application persistence and recovery workflow.

Cancellation and observation

Cancel a clone of CancelToken and await the run future so cleanup can finish. During tool execution the agent gives dispatches a short cleanup grace period, then supplies cancelled results for unfinished calls. This preserves tool-call/result pairing. Forcefully aborting the Tokio task bypasses this cooperative completion path.

An EventSink receives text/thinking deltas, tool starts and outcomes, failure/retry notices, and TurnFinished. TraceContext attributes events to an agent and optional session/thread labels. The sink is synchronous, so avoid blocking I/O in emit. TurnFinished is emitted after run completes on success, error, or cooperative cancellation; inspect the returned result to determine which occurred.

Tool outcomes include the returned result and factual process status. Events are not a durable replay format; preserve complete history for replay. For Myco's existing persistent composition, see application architecture.

Application architecture

The myco crate assembles the reusable libraries with profiles, prompts, sessions, browser/terminal rendering, and host tools. Depend on it when you need that composition; myco-model and myco-agent remain sufficient for a custom environment.

Browser / CLI → SessionRunner
  ├── Agent (myco-agent)
  │     └── GenerativeModel (myco-model)
  └── SessionRuntime (ToolExecutor + live resource ownership)
        └── Harness (host routing)
              ├── local → in-process HostWorker
              └── remote → ssh … myco --mode host

Keep the lifetimes separate

ObjectOwnsLifetime
AgentModel context, usage, model and tool handles, sinkOne live agent instance
SessionMetadata and ordered threadsPersistent document
ThreadLinear history and usage estimateOne context within a session
SessionRuntimeSession binding, harness, resource ownerShared across thread or agent replacements
Host tool serviceProcesses, output buffers, read stampsHost-local resources scoped by owner

Retaining a SessionRuntime keeps live tools across compaction or agent replacement. Starting a different session creates different tool ownership. Reloading a saved document after process exit does not recreate those resources.

SessionRuntime::bind_agent binds the active thread and attribution and clears the previous checkpoint. The chat adapter wires persistence back in. For a durable application turn, use SessionRunner: it owns the agent, session writer coordination, input submission, recovery, and compaction policy. It preserves live tools when threads change and rejects stale checkpoints. Lower-level chat::run_session_turn submits one durable turn; chat::interact only appends user input and runs the agent.

Extend tools at the right layer

Implement the agent's ToolExecutor for a custom environment independent of Myco hosts. Implement the application's ToolService when adding a tool to its existing host runtime. The harness adds the optional host field to routed tool schemas and sends calls to the selected host.

Local is always in-process. Remotes attach lazily and use a version-checked, concurrent NDJSON protocol. Session metadata and prelude tools are installed only on the local worker. Model credentials stay with the application process.

Find code by responsibility

AreaEntry point
Server, CLI, and host-worker startupsrc/bin/myco.rs
Browser frontend, HTTP actions, and transcript projectionsrc/bin/browser/
One-shot prompts and scrolling terminal chatsrc/bin/cli/
Profiles, models, authenticationsrc/config/, src/core/fs.rs
Agent executioncrates/myco-agent/src/lib.rs, generation.rs
Provider translation and streamingcrates/myco-model/src/
Session turns and compactionsrc/chat/, src/session/
Live resource bindingsrc/session_runtime.rs
Host transport and routingsrc/host/, src/harness/
Host toolssrc/tool_services/

The browser server accepts HTTP only on loopback; remote access uses SSH port forwarding. profiles.rs discovers profiles and routes /profiles/NAME/ to lazy worker processes managed by profile_worker.rs. Each worker inherits its profile environment once and owns its config, stores, sessions, and tools. Private Unix sockets keep all public traffic on one port. The supervisor merges profile events into one browser stream; the SharedWorker scopes subscriptions by profile and session ID. A profile crash can be recovered without restarting its neighbors. Parent shutdown closes worker stdin pipes and stops their sessions.

origin.rs applies loopback Host and browser-origin checks to UI assets, event streams, images, files, and actions. files.rs holds a directory capability for read-only workspace access and streams regular files. Markdown rendering maps local links to those routes on the server. Workspace documents have a stricter content policy than the application: their scripts cannot execute with the conversation UI's privileges.

The repository's guided code tour follows complete execution paths and points to integration tests. The generated reference provides signatures and source links.

Evaluations

myco-eval freezes task cases from existing sessions or new prompts, then runs them through the same SessionRunner as the server. Each attempt uses a fresh workspace and profile. An independent grader checks the resulting artifacts.

myco-eval from-session SESSION_ID /private/evals/fix-parser \
  --user-message 12 --repo /path/to/repo --revision STARTING_COMMIT \
  --grader grader.py --split test
myco-eval run /private/evals --config /path/to/config.toml \
  --model MODEL_KEY --output /private/results --repeat 3 \
  --max-requests 40 --timeout-secs 600
myco-eval report /private/results --min-success-rate 0.8

Choose the starting revision explicitly: a conversation is not a filesystem snapshot. Export only the prefix before the task's answer, and review earlier context for answer leakage. Keep session exports and results private unless reviewed for publication.

Reports distinguish full task success, partial artifact quality, timeout, and infrastructure failure. They retain request/token counts, tool observations, latency, and cost estimates. Repetitions and held-out tasks help measure whether a model reliably completes your work.

The optional GEPA adapter optimizes the prelude using bounded Myco task and reflection runs. It separates train/validation/test cases and defaults to explicit free OpenRouter models. It requires no additional dependency in the Rust CLI. See the task eval manual for case formats, graders, spending guards, resume behavior, and the GEPA command.

For a custom embedding, use SessionRunner or the lower-level agent API.

Rust API reference

The reference is generated from the same workspace revision as this book. Each crate has symbol search, method signatures, trait implementations, and links to the corresponding Rust source.

The book search covers prose and the bundled manual. Inside the Rust reference, use rustdoc's search to find symbols across all three workspace crates.

To generate only the reference locally:

cargo doc --locked --workspace --no-deps --lib --open

To preview the complete book with working API links, follow the documentation build instructions.

Contributing to the docs

The site combines an mdBook guide and rustdoc for the three workspace libraries. It uses GitHub Actions to publish a single artifact to GitHub Pages.

Build and preview

Install stable Rust, Python 3.11 or later, and the pinned mdBook version:

cargo install mdbook --locked --version 0.5.4
bash scripts/build-docs.sh
python3 -m http.server 8000 --directory target/site

Open http://localhost:8000. The build checks book links and anchors, including links into rustdoc, and verifies that every registered runtime manual article appears in the book with the agent-access note. Generated files stay in the ignored target/ directory. Rustdoc builds with warnings denied to catch broken API documentation links; its generated JavaScript navigation is not traversed by the book link checker.

For fast prose edits, mdbook serve docs --open rebuilds the book automatically. That command previews the book; use the full build and static server above for the combined API reference. Rust examples are included directly from their source:

cargo check --locked -p myco-model -p myco-agent --examples
cargo run --locked -p myco-agent --example headless

The headless example is offline. The inference example requires an endpoint and credentials you supply and is compiled, but not run, in CI.

Edit the right source

ContentSource
Navigationdocs/src/SUMMARY.md
User guidedocs/src/guide/
Reuse and architecture guidesdocs/src/developers/
Bundled runtime manualsrc/manual/articles/
API contractsRust doc comments in crates/ and src/
Runnable examplesEach library's examples/ directory
Theme and book settingsdocs/theme/myco.css, docs/book.toml

Manual chapters are thin wrappers: an agent-access note followed by a full {{#include}} of the original article. Keep edits in the runtime source so the binary and website stay aligned. When registering a new runtime article, add its wrapper and navigation entry too. Website-only explanations belong in the guide. Do not copy manual text into a second maintained version.

GitHub Pages

The repository's Pages publishing source must be GitHub Actions (Settings → Pages → Build and deployment). The workflow in .github/workflows/docs.yml validates pull requests and attaches a downloadable myco-docs artifact. Only main pushes or manual runs on main deploy to the github-pages environment. A pull request does not publish a website.

The expected site is https://tsnl.github.io/myco/. Document-relative links work there and under a local preview; site-url = "/myco/" also gives the generated 404 page the correct asset paths. Update that setting if the hosting prefix changes. Restrict the deployment environment to main in repository settings if it has additional deployment rules.

The build uses mdBook's built-in includes and GitHub's Pages workflow actions. It needs no JavaScript package manager, separate publishing branch, or runtime server.