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_atAutomatic compaction fraction in (0, 1]; default 0.8. Threshold also reserves max_output_tokens when that cap is smaller than the context window.
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. Transient failures also retry after partial output: the failed draft is discarded and a fresh response is generated from unchanged context. Completed tools are not replayed. Set max_attempts = 1 to disable retries. 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
Highlight earlier or later user messagesUp or Down while the input is empty
Return from a highlighted message to the latest outputEscape, Latest output, or Down past the newest message
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
View an image fullscreenClick the image; Escape or the close button returns
Inspect running tools, background shells, and timersActivity
Cancel a scheduled follow-upActivity → Timers → Cancel timer
Let a running shell call continue while the assistant moves onBackground on the tool or in Activity
Switch configured modelsModel selector between turns
Compact contextMenu (⋯) → Compact, or /compact
Open another sessionNew or a session link on the home page
Rename a sessionMenu (⋯) → Rename session, or Rename on its session-list row

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.

Ask the assistant to check back after a delay or at a specified time. Its timer tool schedules a follow-up that wakes the session automatically, even with every tab closed. Timers wait for the current tools to finish, preserve queued edits, and survive compaction. Keep the server running: restarting clears pending timers. See session timers for limits and controls.

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". Each completed model response is written to stdout; diagnostics and the saved session ID go to stderr. Interrupted drafts are discarded during retries. 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; scroll to the top of the conversation to read them. Live shells and editor read stamps continue through compaction, as do the title, links, and scratchpad.

Automatic compaction is enabled for every model. The per-model auto_compact_at fraction defaults to 0.8. Its resolved threshold also reserves the configured output cap when that cap is smaller than the context window. Before each model request, the server estimates new input, prelude changes, tool arguments, and settled tool results in addition to measured prompt usage. Missing usage falls back to a text estimate. Three UTF-8 bytes per token plus framing is a heuristic; images, static provider context, and tokenizer differences can still require request-size recovery. Restored usage is conservative because its exact input prefix is unavailable, so it may compact early. The agent continues after compaction. New input is included; selecting a model alone does not start work. 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. An oversized successor estimate suspends automatic compaction until the next provider usage report calibrates it. A report still above the threshold, or a compaction failure, disables automatic compaction until manual compaction succeeds or another session opens.

A request-size rejection (HTTP 413 or the configured request byte cap) also compacts and continues automatically, independently of the token threshold. This recovery replaces recent images with text references to their saved originals and preserves completed tool effects. Cancel interrupts it. If summarization fails or the smaller request is still rejected, the run stops retrying and removes the rejected submission from active context. Its original input and tool results remain in saved threads, and the session accepts new input.

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

Choose Archive from the session menu (⋯) or on the home page to hide a session without deleting it. Archived sessions show an Archived badge in the session header and browser list, alongside their activity status. Choose Archived sessions to find them; Restore is available in the list and the session menu. Restoring from the menu keeps your draft and stays in the session. Open tabs update their badges when you archive, restore, or undo. 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

Transient connection failures are retried automatically, including interruptions during output. HTTP/2 errors such as Reset(..., INTERNAL_ERROR, Remote) mean the remote server or gateway reset the stream; a surrounding Decode label does not imply invalid model JSON. The browser replaces the unfinished reply and shows “Retrying” during backoff. Completed tool results remain in context; tools from an interrupted draft never execute. Retries start a fresh model response from that context, rather than resuming the disconnected stream.

The default limit is three attempts. After exhaustion the session stops and still accepts new input. Malformed response data and authentication errors are not retried. 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, skills (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, skills) 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.

The recent tail retains up to two complete human turns, at most 64 messages and 64 KiB of serialized context. Image sidecars count at their resolved base64 size without loading their bytes; unavailable or unknown-size images omit that turn. A turn that exceeds either bound is represented by the summary, with its original history still available through session_history. The summary and latest runtime inventory are separate from this tail budget. These bounds limit retained history; the selected model's context and request-size limits still apply.

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.

The model-facing description is limited to 16 KiB across current and prior-runtime inventories, with 512-byte quoted previews for commands and errors. Included host, tool, handle, and process-instance identifiers remain complete. Omission counts make larger inventories explicit; use bash with action="list" on the configured host to inspect its handles. Saved runtime metadata retains the full observations.

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. After restoring writable storage, submit another message, compact, or select a model to recover the live session. Completed tool observations are saved before new work; a tool batch that never started is recorded as not executed. Tools are not automatically replayed.

After a restart or an abandoned running tool future, a pending batch has unknown outcomes: the calls may have taken effect before their results were saved. A hidden runtime notice records the interruption. 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 6, 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 5 are accepted and upgraded on read; loading alone does not rewrite their files. Older turns keep unknown timestamps. The optional auto_continue metadata field defaults to false in older files; it survives thread compaction but does not start work when a session loads. Binaries that predate schema 6 reject these files. 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, host_idle_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
host_idle_timeout_secs = 1800 # 0 disables; retained tools keep workers alive
# 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.

For builds, tests, and other finite commands, use exec with background_after in milliseconds to decide when to stop waiting without killing the job:

{"command":"cargo test --offline", "background_after":1000}

If the command is still running after that wait, Myco returns a bg-… session handle with its current output. Continue useful work, then use read on that handle and the same host to inspect progress or wait again. The process is not restarted, and returned output is not repeated. Use close to stop it and release the handle. Promotion retains already-running work even when the host's 8-session start admission limit is reached; close unused handles when finished. background_after is exec-only, accepts 1–1,800,000 milliseconds, and is omitted by default; omission retains the usual 60-second foreground exec timeout. If a timeout expires before or at the requested background wait, the command is killed instead of promoted.

Prefer background_after over shell timeout wrappers or setting timeout_ms just to regain control. An explicit timeout_ms is a hard deadline from command start, even after automatic backgrounding; later reads report when it kills the process group. With background_after set and timeout_ms omitted, there is no implicit kill deadline before or after promotion. Manual browser backgrounding keeps its existing behavior of dropping the original timeout. Cancelling an in-flight exec still kills it; after promotion, the retained handle owns the process and signal or close controls it.

Run the target program in the foreground of the command; let background_after or bash start own its background lifetime. Recognized nohup invocations in exec and start command fields are rejected before the whole command starts, with instructions for using managed jobs. The syntax check recognizes paths, quoting, common launch wrappers, and literal nested shell commands. Mentions in arguments, comments, and ordinary heredoc data remain usable.

This check prevents common misuse; it does not contain arbitrary programs. Dynamically constructed commands, external scripts, and bytes sent through stdin are not fully inspected. Avoid nohup, disown, daemon modes, and shell & for detached work. Myco inventories retained tool handles, not every process created by a program. Managed handles expose host, command, process identity, status and captured output; use list to rediscover them, read for further output, and signal or close to control them.

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. Worker startup uses the largest cap in the configured model catalog (myco --mode host --max-image-base64-bytes). Every tool call carries its session's selected model cap; workers enforce the smaller of that cap and their startup ceiling. Sessions with different models can share a worker, and switching models preserves live shells and editor read stamps. Tool descriptions quote the active limit in exact bytes.

Provider input is checked again against the selected model, including older inline images and saved image sidecars. Sidecars are checked before reading and read under a byte bound. An image retained from a larger model may require compaction, resizing, or selecting that model again. Remote image URLs have no locally known payload size; the provider validates their content. The whole serialized request remains subject to max_request_bytes.

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. A turn cut off while it was still only thinking is different: providers never receive thinking back, so that turn reaches the next request as nothing, and "continue where you stopped" would make the model redo the same reasoning until it hits the same cap. Its resume prompt (and, under auto-continue, its continuation notice) instead says the thinking was lost and asks for visible conclusions or a tool call early in the next response. Auto-compaction is enabled for every configured model through the session runner. auto_compact_at defaults to 0.8. The effective threshold is the smaller of floor(context_window * auto_compact_at) and context_window - max_output_tokens. The output reservation applies only when the output cap is smaller than the window; larger caps retain compatibility with existing small-window configurations. An explicit fraction of 1.0 still reserves usable output capacity. Fractions must be greater than 0 and at most 1. The default 20% headroom allows growth but cannot guarantee that a future tool result or response will fit. The system prompt reports the resolved threshold.

Before each model request, the runner counts newly accepted input, dynamic prelude notices, assistant tool arguments, and completed tool results. It adds estimated growth to the last measured input size, or estimates text from scratch when usage is absent. The heuristic uses three UTF-8 bytes per token plus message/tool framing; it excludes hidden metadata and thinking that providers do not receive. Images, tokenizer differences, and provider-owned instructions/schema remain outside this estimate and may require request-size recovery. Tracking updates incrementally without serializing the full history.

After restart, saved usage lacks its exact measured prefix, so sizing conservatively includes the restored text as well and may compact early. Model changes preserve sizing hints separately from reported usage. Selecting a model or opening a session alone does not compact or generate. Compaction includes newly accepted input and settled tool results.

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. An oversized successor estimate suspends automatic compaction until the next provider usage report calibrates that estimate.

Compaction with auto-continue off retries transient or malformed model responses within the same worker and its shared compaction_max_requests budget. Retry waits are visible and cancellable; completed writes are not replayed. Exhausting recovery stops the run with the original context retained; a later submission or manual compaction can try again. Authentication, request-budget, and persistence errors stop immediately.

With auto-continue enabled, generation, persistence, and automatic summarization errors retain the session's indefinite recovery policy, with waits of 1–5 seconds. Retries retain live state and any successfully produced summary; failed saves are repaired before work advances. Cancellation or disabling auto-continue stops retries. Manual /compact retains bounded retries and waits for the next user input after success. If the next usage report remains above the threshold, automatic compaction is disabled until manual compaction succeeds or another session is opened. Compaction workers do not run auto-compaction. Each committed successor retains the same live tool owner and the run's usage and truncation accounting.

Oversized requests trigger compaction and continuation in browser and CLI sessions independently of the token threshold. This covers HTTP 413, recognized provider size errors, and the local max_request_bytes cap. Recovery replaces retained images with text references to their originals in the saved predecessor thread, so the next request does not resend the same image payloads. Completed tools are not replayed. Cancel interrupts recovery. If summarization fails or the compacted request is still too large, the run reports the error and falls back to removing the rejected submission from active context; the saved threads keep the input and completed observations. The session still accepts new input. Another size recovery is allowed after a successful model response.

Bounded generation retry applies when session auto-continue is off; explicit auto-continue uses the session recovery policy described in cli. The retry configuration 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. Transient connection and body-read failures, HTTP 408, 429, and 5xx (including Anthropic's 529) are retried even after partial output. Each attempt generates a fresh response from unchanged conversation history; it does not resume the broken stream. Failed drafts never enter model history or execute tools. Earlier completed tool calls stay in context and are not replayed. The browser replaces the interrupted draft; myco -p buffers each response until it validates. Terminal chat keeps streamed drafts visible, separated by retry diagnostics.

A 413 or recognized size rejection goes to the session's context recovery instead of retrying the unchanged request. Other 400 and 401 errors surface immediately. Malformed response data, including truncated tool JSON and incomplete streams, use the same bounded retry budget; no calls from an invalid response execute. A provider's Retry-After is honoured when it asks for longer than the computed backoff. If that minimum exceeds max_backoff_ms or the remaining recovery budget, automatic retry stops with a limit diagnostic; it never sends early by shortening the provider's requested wait. The agent owns retries; provider drivers perform one attempt and report failures. The browser shows “Retrying” during backoff and a notice with the next attempt and delay. Cancel stops the request, including retry waits. Notices are not added to model history.

For longer temporary outages, opt into an elapsed recovery budget in the same table and raise its attempt count:

[models.my-model.retry]
max_attempts = 24
initial_backoff_ms = 1000
max_backoff_ms = 30000
max_elapsed_ms = 300000

max_elapsed_ms accepts 1–300000 milliseconds (at most five minutes). It starts at the first retryable failure of one generation and includes all later waits and requests, including partial streams. It does not impose a deadline on the original request or the whole task. Exhaustion discards the unvalidated draft and stops at the committed conversation boundary. CLI and browser retry notices show the remaining budget. Cancel interrupts both waiting and streaming. A validated response resets the budget for the next generation; earlier completed tool effects are never restarted. Malformed drafts use the same finite attempt/time budget. Authentication and deterministic request errors still stop immediately.

Errors reported inside an HTTP 200 stream use that same recovery policy. Myco retries Anthropic overloaded_error, api_error, timeout_error, and rate_limit_error, and OpenAI server_error, rate_limit_exceeded, slow_down, and server_is_overloaded codes. Unknown codes, authentication errors, and invalid requests stop immediately; error-message wording never selects retries. Anthropic's rate_limit_error can also represent a spend cap, so its finite recovery budget may expire until account access is restored. Failed draft text and tool calls do not enter committed history or execute, even if they arrived before the error. A Retry-After response header remains a minimum delay.

Without max_elapsed_ms, existing attempt/backoff limits apply (three attempts by default). Model retry tables replace gateway retry tables, so a model override must repeat this field to retain the gateway's elapsed budget. Compaction's separate request cap and eval run budgets still apply. These are recovery limits, not a spending limit for an entire autonomous task.

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, skills) 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. Read project guidance (AGENTS.md/CLAUDE.md) and selected skill instructions with the editor. The skills tool discovers skill metadata; it does not search file contents.
  • 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.

Skills

A skill is a directory containing SKILL.md with frontmatter declaring a name and description, followed by instructions. Myco adds a bounded local catalog to the startup prompt. The model uses those descriptions and the user's request to choose relevant skills, then reads their instructions with the editor. Discovery does not load instruction bodies into the prompt or run skill scripts.

For a working directory, discovery checks .agents/skills, .claude/skills, and .grok/skills there and in its ancestors through the nearest Git root. Outside a Git checkout, only the supplied directory is checked. The same three layouts in the executing host user's home are also checked; MYCO_HOME does not relocate these user skill directories. Each layout has one immediate subdirectory per skill, such as .agents/skills/review/SKILL.md. Myco does not search the whole repository, follow symlinked skill directories or manifests, or discover plugin bundles. Duplicate names remain separate entries identified by their paths.

Successful editor and image calls scan the operated file's directory, or the operated directory itself. Successful bash calls scan the host worker's launch directory. Changed catalogs appear as notices attached to tool results, tagged with their host and directory. Observations are scoped to the session runtime; unchanged catalogs normally do not repeat within a thread. A new thread receives the catalog again when it next operates in that directory. Discovery has bounded directory, metadata, and notice limits. A partial catalog reports omissions or scan errors.

Use skills explicitly to inspect or refresh another directory:

{"path":"/work/project","host":"devbox"}

Omitting host selects local; omitting path selects that host worker's launch directory. Relative paths use that launch directory. Shell-internal cd commands and retained shell working directories are not inferred. After changing a shell's directory, pass its new path explicitly. Remote discovery and subsequent editor reads happen on the selected host; the startup catalog does not connect remotes.

Frontmatter supports plain, quoted, literal (|), and folded (>) scalar values for name and description; it is not a general YAML parser. disable-model-invocation: true marks a skill for explicit user requests only; the flag appears in the catalog and instructs the model not to select it implicitly. It accepts unquoted true or false. Other fields, including allowed-tools and user-invocable, are ignored; Myco does not implement skill slash commands or per-skill tool restrictions. Skill content is guidance: discovery grants no permissions, changes no approval policy, and does not override the user's instructions. Read supporting files only when the selected skill and task require them.

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).

Server sessions provide timer for durable one-shot follow-ups and inbox for immediate or scheduled event triggers. Normal events wait at a settled boundary; urgent and time_sensitive events background foreground bash before interrupting the turn. Optional expiry drops stale pending events. See browser for tool arguments, HTTP endpoints, persistence, and activity controls.

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. By default both run a local session runner without starting an HTTP server. Explicit --server attaches one-shot output to an existing server session. 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 write completed responses to stdout; bare -p reads stdin
--server URLAttach one-shot output to an existing /profiles/NAME service; requires a full --resume ID
--detachWith --server -p, return after acceptance and print a reconnect token
--observe INSTANCE:REQUESTWith --server, print retained output from a prior native turn
--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
--auto-continue[=true|false]CLI/one-shot only: persist automatic continuation for this session; omit to keep its current setting
--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 listen flags and --mode.

Stdout contains assistant text, including narration between tool calls. Each model response is buffered until it completes and validates, so interrupted drafts cannot leak into scripts when generation retries. 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.

Attach to an existing service

myco --server http://localhost:8765/profiles/default --resume FULL_SESSION_ID -p "Continue"
myco --server http://localhost:8765/profiles/default --resume FULL_SESSION_ID -p "Work" --detach
myco --server http://localhost:8765/profiles/default --resume FULL_SESSION_ID --observe INSTANCE:REQUEST

Use the full 32-character session ID from the browser URL. The session must be idle with no queued input to accept a new native turn. This mode uses that server's existing runner, model, tools, and profile workspace; workspace=PATH is printed to stderr. Local config/profile environment variables are ignored, and explicit config, profile, model, effort, and auto-continue overrides are rejected. Explicit prompt image mentions are read from the client's working directory and uploaded; piped text remains literal. Ordinary -p and terminal chat retain their local working-directory and config behavior.

Client and server must have matching protocol, package version, and build commit. Unavailable or incompatible services fail without starting a local runner. URLs must include /profiles/NAME on a loopback HTTP origin; use SSH forwarding to attach remotely. There is no service discovery, private-service fallback, or interactive service client yet.

run=INSTANCE:REQUEST on stderr identifies an accepted or possibly accepted turn. Transport retries reuse this identity. --detach exits after acceptance; closing a client leaves the turn running. --observe reconnects to the same instance and prints its committed assistant output from the beginning. Output is published only after the response's history checkpoint succeeds; a later save failure retains earlier committed output and reports an error. Automatic reconnect within one invocation resumes after its last printed byte. Ctrl-C requests cancellation of that turn and waits for its recorded outcome. Browser follow-ups accepted during the run may join it under the usual queue policy.

Output receipts survive thread compaction but exist only for the current service process. The 16 most recent native turns are retained per session; an expired receipt returns an explicit error and is never re-executed. Each receipt holds at most 4 MiB of assistant text; exceeding this cancels the turn with an error directing you to saved history. Compact request fingerprints are retained for up to 4096 native turns per session instance; after that, finish active work and restart the service before accepting more. Saved conversation history is independent of these receipt limits.

After service restart, an old token reports an unknown outcome and never replays a request. Inspect the saved session and any external effects before submitting new work. Reconnecting with --observe is the safe response to a lost acceptance response; repeating -p creates a new request.

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
/auto-continue [on|off]Show or change automatic continuation for the current session
/quit, /exitExit

Assistant text streams to stdout; tool activity and diagnostics go to stderr. A transient generation failure starts a fresh attempt with a retry diagnostic separating it from the interrupted draft, which remains visible in the terminal. The retry table's optional max_elapsed_ms supports longer bounded recovery; notices show its remaining time, and Ctrl-C interrupts waits or active retries. See overview for the attempt, backoff, and elapsed-budget settings. 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.

Auto-continue is off by default. When enabled, a completed answer or exhausted output cap receives an internal continuation prompt until the agent calls session_meta action=disable_auto_continue. The prompt tells it to disable when the task is complete or it needs user input. Enabling the mode does not itself start work; submit a task normally. It survives compaction and restart, but does not propagate to child sessions. Restarting never starts a saved task by itself. Ctrl-C stops the current run, including automatic continuations; the setting remains enabled for later submissions. Use /auto-continue off or --auto-continue=false to disable it. While enabled, generation, persistence, and automatic compaction failures retry after 1, 2, 4, then 5 seconds, staying at 5 seconds until recovery. Each retry reports its cause. Failed saves retain the same live state and retry persistence before advancing, so accepted input and completed tools are not repeated. Cancellation and disabling the mode interrupt the retry wait. There is no retry-count, spending, or elapsed-time limit in this mode. With auto-continue off, provider retries retain their configured bounds and persistence errors return to the caller.

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.

Auto-continue is an opt-in per-session control in the session menu, off by default. When on, the top bar shows an Auto-continue badge and the runner supplies internal follow-up prompts after completed responses or exhausted output caps. The agent disables it with session_meta action=disable_auto_continue when finished or when it needs user input. The control works during a run: turning it off prevents further automatic follow-ups; Stop cancels the current run. The setting persists across compaction and restart, but enabling it or reopening a session does not start work. Generation, persistence, and automatic compaction errors retry after 1, 2, 4, then 5 seconds, remaining at 5 seconds until recovery. The latest error and wait appear in the transcript. Failed saves retain live state and retry before advancing, without repeating input or completed tools. Turning the control off or pressing Stop interrupts the wait. This mode has no retry count, spending, or elapsed-time limit.

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. Behind the well the sky is frosted: its light and soft cloud shapes show through, without stars, airplanes, or rain. A window no wider than the well shows only that frosted sky. Settings → Layout sets the well's width in columns of conversation text: 80, 100 (the default), 120, 150, 200, unbounded, or a custom count from 40 to 1000. The top bar and input bar follow it. The choice is saved in this browser and applies to every page and profile; a window narrower than the chosen width uses its full width. The top bar stays fixed, with its glass extending above the viewport to keep both scroll limits covered. Native conversation scrolling and bounce remain enabled. The input bar aligns with tool cards. Colored state borders stay solid and two pixels wide; matching shadows lift those panels from the glass. Activity animations add light without dimming the base border. 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.

Settings in the top bar's menu 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 generation or a tool process is running. Shells remain Running between model turns; you can submit a new message while an existing process continues. 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.

Hidden tabs defer live transcript rendering to save browser CPU. Generation, tools, and timers continue on the server. Returning to a tab loads the current transcript and activity state while preserving your unsent draft.

The input border also shows the session's state. Running and Compacting carry a travelling highlight and soft glow using the sky's current light color. Ready keeps the neutral glass border; Stopped is steady red. Cancelling and Reconnecting… are amber. Live shell processes use the same sky glow between turns. A stopped run keeps its red border while tools remain open. Hidden tabs pause the animation; reduced-motion preferences keep it still. The text status remains available in every state.

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.

Choose Archive from the session menu 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. Archived sessions have an Archived badge in the list and session header, alongside their activity status. The session menu offers Restore, which restores the session in place and keeps your draft. The badge updates in other open tabs when you archive, restore, or undo. Opening an archived session does not restore it. 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.

Use Rename session in the session menu, or Rename on a session-browser row, to edit its name. Enter or Save applies it; Escape or Cancel discards the edit. Names must be non-empty and use the existing 120-character title limit. The title updates in open session tabs and the browser list. Renaming works while tools run and for archived sessions; it preserves the conversation, URL, and archive status. Saved sessions are renamed without starting a runtime. If saving fails, the dialog keeps your proposed name so you can retry.

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.

Click an image in a message, tool result, queued message, or attachment preview to open the fullscreen viewer. The image fits the viewport without changing its aspect ratio. Press Escape, use Close image viewer, or click the space around the image to return. Images also open with Enter or Space when focused. Viewing an image does not send a message or interrupt running work.

When the input is empty, Up highlights the newest user message; press Up again for earlier messages and Down for later ones. The input keeps focus and stays empty. Down past the newest message, Escape, or Latest output returns to the live output. Typing clears the highlight, and arrows edit text normally whenever the input contains a draft.

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 gives the title the room beside the status, Activity, and the menu (⋯). The menu holds Rename session, Compact, Auto-continue, Archive, Settings, and Feedback; Escape or a click outside closes it. Feedback opens a new issue for myco on GitHub in another tab; nothing is sent until you submit it. Narrow screens keep the title on its own row above the controls. Activity counts appear only while tools, background sessions, or timers are running.

Each tool appears as soon as execution starts, with a truncated argument preview in its collapsed header. Running calls are white; 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. Its text selects and copies like the rest of the conversation. Click the heading or the bar to collapse it; the 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 containing all running tools in one list. Scheduled follow-ups appear in a separate Timers section with their own countdowns and cancellation controls. Every entry opens its transcript card. Shells launched with bash start and commands released with background_after or Background remain running in that card, with an advancing timer and pulsing border, until the process and its output streams finish. The same border appears on Activity entries. Reduced-motion preferences keep the borders steady; hidden or disconnected tabs pause their animation. Calls still blocking the assistant appear first, followed by retained processes, with the same controls and styling throughout the list.

Process status refreshes every second without consuming output. Already connected remote hosts are included; polling never connects a lazy host. Failed observations show state unknown rather than reporting success. A process whose original call is outside the active thread retains a reachable card through compaction. The drawer starts closed; its button shows the activity count. Close it with its close button, Escape, or a click outside it.

The assistant collects additional output with bash read; status polling does not drain it. Visibility covers Myco-owned handles. Launch the target program directly with background_after or bash start to retain one. Recognized nohup invocations in those command fields are rejected with this guidance. Arbitrary daemonized descendants are not independently discovered or adopted.

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. Saved cards without a current handle show not running. Process identity distinguishes repeated uses of the same session_id and keeps hosts and owning sessions separate. Tool results retain an optional resource reference (id and instance_id); older stored results remain readable. Live status is a browser observation and does not rewrite the original result or consume output.

Session timers and event inbox

Ask the assistant to check back later, for example, “Check the build in two minutes.” In server sessions, the local timer tool supports:

ActionInput
Set a delay{"action":"set","after_seconds":120,"message":"Check the build and report its result."}
Set a time{"action":"set","at":"2026-09-25T09:00:00-07:00","message":"Check the deployment."}
List pending timers{"action":"list"}
Cancel before delivery{"action":"cancel","timer_id":"UUID"}

Use exactly one of after_seconds or at. Absolute timestamps require a timezone. Timers accept delays from 0.1 seconds to 30 days; the worker checks deadlines every second. There can be up to 20 pending or running timers per session, with messages up to 8000 characters. Scheduling returns immediately and does not keep a model request open.

When a normal-priority timer becomes due, its message joins the same FIFO as user follow-ups. An idle session wakes automatically; a busy session receives it at the next settled tool/model boundary. A full queue retains the overdue timer until space is available. Held edits keep their queue position. Due timer messages can be edited or unqueued using the normal controls, and appear under SYSTEM · Timer fired when delivered. Their origin remains visible after refresh and in saved history.

Activity → Timers shows local due times, countdowns, and Cancel timer. Countdowns update every 0.1 seconds below one minute. An idle session with a scheduled timer shows Waiting in its page and the session browser. Cancel applies only to that timer; once delivery has started, use the normal run controls.

Timers belong to their session and profile. They survive tab closes, model changes, and compaction. Archiving preserves them; Cancel run stops the current turn but does not cancel scheduled timers. Timers and event triggers are saved before acceptance. Starting the server restores profiles and sessions with pending inbox entries, including archived sessions, without requiring a browser tab. Overdue events run unless their expiry has passed. Ordinary queued user messages are still held in memory. Timers are one-shot; recurring schedules and terminal-only timers are not supported.

The inbox tool supports post, list, and cancel. Posting requires message; at or after_seconds schedules it, otherwise delivery is immediate. event_id is an optional stable UUID for retrying the same post. Scheduled posts with a stable ID require at rather than a relative delay. Cancellation requires that UUID. list includes pending, delivering, running, and recent completed, failed, interrupted, expired, and cancelled events.

Both timer set and inbox post accept priority (normal, urgent, or time_sensitive) and expires_at (RFC 3339 with timezone). Normal delivery backgrounds active bash calls and waits for a settled tool/model boundary. Urgent and time-sensitive delivery takes priority over ordinary queued input, including held edits. It first requests backgrounding for every active bash call, waits for their results, then cancels the current turn and starts a fresh turn with the event. Other active tools receive normal cancellation. Retained background processes remain owned by the same session. Cancellation already requested by the user settles before any event starts.

Expiry applies only before delivery starts; it never kills a running turn or process. The Activity → Event inbox section lists events awaiting delivery with their text, status, priority, due time, expiry, and cancellation, soonest first. Delivered events move under Read; cancelled and expired events move under Not delivered. Both lists start collapsed and show the newest first. A delivered event's full text stays in the conversation. Scheduled timers keep their countdowns in Timers. Event text remains ordinary input; priority does not grant approval or additional tool authority.

Each session accepts up to 100 active events in total and retains its last 100 terminal outcomes. Idempotency lasts while the entry is retained. Reusing an ID with different input is rejected. Pending event edits and held delivery survive restart. A delivery claim is reconciled against saved input across all session threads: input already committed is marked interrupted after a crash and is never replayed; an uncommitted claim becomes pending again. Running effects are not replayed or represented as successfully completed after a restart.

External triggers use the profile API (the same origin checks as other actions):

curl -X POST "$MYCO_SERVER_URL/api/sessions/$SESSION_ID/inbox" \
  -H 'Content-Type: application/json' \
  -d '{"event_id":"4c2f8996-5963-4c56-b495-d2c9c2166411","message":"Build finished; review its result.","priority":"urgent"}'

event_id is required for HTTP posts; generate a fresh UUID for each distinct event and reuse it when retrying. Optional at and expires_at are absolute RFC 3339 timestamps. A successful POST returns HTTP 202 and the accepted entry. GET /api/sessions/{id}/inbox returns {"events":[...]}; POST /api/sessions/{id}/inbox/{event_id}/cancel cancels pending delivery. Session and profile ownership apply to every operation. Inbox files live under the profile's inbox/ directory with their own versioned format; the conversation file format is unchanged.

Compaction activity

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. Loaded transcripts arrive with server-rendered HTML, so opening or reloading a session does not briefly display raw Markdown or request formatting once per message. Streaming updates also render on the server, with requests coalesced per message; the browser keeps existing formatted output while waiting for the next render. 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.

In server sessions, the assistant can call getlink with a path to obtain a URL for an existing workspace file. Pass a literal absolute or workspace-relative path; the tool encodes spaces and special characters. For example, path: "charts/rain.png" returns /profiles/NAME/files/charts/rain.png. Use that URL in ![Rain](URL) or [Open chart](URL); Markdown uses the same file URL mapping. URLs are relative to the browser's origin, so SSH tunnels can use a different local port. Missing files, paths outside the workspace, and escaping symlinks return a tool error. Copy artifacts from other hosts or directories into the profile workspace before requesting a link. The tool does not upload or copy files; the server serves their current contents.

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 for every model. auto_compact_at is a fraction of its context window in (0, 1], defaulting to 1.0; use 0.8 to compact earlier. Before sending a message, the runner checks the last known prompt size against the selected model's threshold. This sizing estimate survives model changes and restarts, while the usage display waits for a report from the new model. Selecting a model alone does not compact. New input is included when compaction runs. Reopened sessions restore their last recorded model. If it is no longer configured, the server uses its default and shows a notice. 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. Transient compaction response failures retry within the worker's existing request budget, with visible, cancellable waits. The source thread and completed tool observations survive. Exhausted retries or terminal errors stop the run; a later message can try again. An ineffective summary disables further automatic compaction until manual compaction or a session change. Cancellation preserves the source thread.

After compaction the conversation opens at the successor thread, which begins with the recent turns it carried over. Scroll to the top, or use Show earlier messages, to load the thread before it above them; keep scrolling for older ones. Each loads once and keeps your place. A Compacted rule marks where the assistant continued from a summary, and each turn appears once, in its original thread or as carried over. Up-arrow message navigation reaches loaded messages. Earlier threads show their recorded results, not live process state. A reload starts from the successor again. A submission removed after a size rejection is kept in its saved thread for session_history but is not shown again.

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 archived, busy, status, blocks, queued, timers, usage, and context_window_tokens
GET /api/sessions/ID/threads/THREADA replaced thread from the snapshot's earlier_threads: replaced_at and the blocks its successor did not carry over
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/actionThe same envelope with {"kind":"cancel_timer","timer_id":"UUID"}
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
POST /api/sessions/ID/rename{"session_id":"ID","title":"New name"} → 204; blank titles are rejected
POST /api/sessions/ID/auto-continuePersist the mode with {"session_id":"ID","enabled":true} (or false); allowed during a run
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

In HTTP snapshots, non-user message blocks include html alongside their original text. The HTML uses the same escaped Markdown and profile workspace URL rewriting as streamed output. It describes that snapshot's text; clients applying later text deltas must discard the cached HTML. Live events remain compact deltas and refresh notices, without rendering unobserved conversations.

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, host_idle_timeout_secs, max_prelude_bytes.

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

  • Connection sharing: each session runtime opens its own ssh <alias> myco --mode host worker per remote it touches. OpenSSH can multiplex the SSH transports over one authenticated connection per host; the tool workers and their owned resources still remain separate:

    Host *
        ControlMaster auto
        ControlPath ~/.ssh/cm-%r@%h:%p
        ControlPersist 10m
    
  • Idle workers: host_idle_timeout_secs defaults to 1800 (30 minutes); 0 disables reaping. Unused connections with no outstanding calls or retained processes/output close automatically and reconnect on the next host= call. Inventory polls do not keep them alive. Close unused bash handles explicitly, including exited processes whose output is still retained. Editor read fingerprints expire with the connection; read the file again before editing after reconnect. Each host worker uses two async runtime threads; blocking I/O may use additional threads while needed.

  • Resource ownership: reusable shells survive user turns, live compaction, and archival. Archival changes visibility; it does not cancel active work or discard retained handles. Reaping applies to archived sessions under the same rules. Ending the owning runtime releases its tools.

  • Lazy connection status: “connects on first tool use” is normal, not an outage. Runtime records report the last failure timestamp; live status also shows its age. After a transport failure, retry host=<alias> once before falling back to raw SSH. If the previous call may have changed something, inspect its effects before repeating that command.

  • Call timeouts: one missing tool response requests cancellation of that call and probes the worker. A responsive worker keeps its connection and other bash sessions. A failed health check resets the connection and reports lost remote sessions using the last available inventory (which may be incomplete).

  • No pseudo-terminal: bash action=start uses pipes on local and remote hosts. Prefer host=<alias> with command: "bash" or "bash -l" for long-lived remote shells. Many allocators and REPLs work without a TTY; check their noninteractive options before using ssh -tt. Programs that require a controlling terminal or job control are not supported by these pipe sessions.

  • Shell handles: an in-flight operation keeps its original input/output and owner even if another shell reuses the same handle name. Closing or reaping a handle still stops its process group while reads or writes are outstanding.

  • Shell cleanup: closing a retained bash handle, ending its owning runtime, or gracefully shutting down its host kills the owned process group, including children that outlive the shell after redirecting their output. An exited shell remains waitable until the handle closes, reserving its process-group identity for safe cleanup; close unused handles to release that process-table entry. Children that deliberately leave the process group (for example with setsid) are outside this ownership boundary. Ordinary one-shot exec completion retains its existing behavior.

  • 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. Host protocol 5 includes skills in the standard host catalog; older workers must be rebuilt before use, even when the package versions match. Each call also carries its selected model image limit. A missing call limit retains the worker startup ceiling; no call can raise that ceiling. 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.
    • For missing skills, call skills with the intended directory and host. Relative paths use the worker's launch directory, even after a bash cd. Check .agents/skills, .claude/skills, or .grok/skills in that directory, its ancestors through the nearest Git root, and that host user's home. Outside Git, ancestors are not scanned. Each skill needs its own directory and a regular SKILL.md with name and description frontmatter.
    • Read discovery warnings: symlinks, malformed metadata, unreadable paths, and scan limits can exclude entries. Explicit discovery refreshes the catalog; successful file operations also discover their operated directory. Read a selected skill with the editor on the same host. Identical names in different paths remain distinct skills.
  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

    • Remote health is checked after a missed call deadline; local is always in-process.
    • Cancellation is forwarded over the host pipe. A lost acknowledgement leaves effects unknown; inspect state before repeating work.
    • 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. The initial Git run requires the local repository and pinned commit. Each run then freezes that commit and its ancestors in case/source.bundle, with a relative path in case/case.json. The run workspace owns its Git objects and does not borrow the original repository's object store. Submodules, dependency installation, and external fixtures still need separate 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.

Generation failure traces include attempt counts, the next retry delay, and recovery_remaining_ms for an optional elapsed retry budget (null when inactive).

provenance.json records format version 1, build revision, creation time, task and model fingerprints, OS/architecture, effective model/runtime settings, limits, the initial main-agent system prompt, and tool schemas. Its artifact paths are relative to the run directory so inspection survives moving the bundle. Backend settings use an explicit allowlist excluding authentication; endpoint user info, query strings, and fragments are removed. Prompts and tool content are retained as task evidence, so review them before sharing. Git snapshots include committed source history reachable from the pinned commit, excluding unrelated refs and the source repository's local configuration, hooks, and authentication settings. case_hash identifies the original recipe; frozen_case_hash identifies the portable snapshot checked for modification during execution and grading.

To replay a moved run, pass its frozen case and separately supplied configuration:

myco-eval run /moved/run/case --config /new/config.toml \
  --model MODEL_KEY --output /new/results

job.json records the original execution paths; it is not a relocation script. The bundle preserves task inputs, not an operating system or installed tools. Use the same build, dependencies, model settings, and candidate prelude for a controlled comparison. Git recipes accept an absolute repository path or a relative path inside the case; relative paths cannot escape via ...

Each new trace line has version: 1, a one-based sequence, UTC timestamp, and monotonic elapsed_ms. request_started and request_finished share a request_id; completions distinguish finished, failed, and abandoned streams. A finished stream is not itself proof of valid model output: generation_accepted records validation by the main agent. Retry failures include attempt counts and retry_in_ms; tool starts/finishes share a call_id. Compaction boundaries and the final run status are recorded too. The session store retains tool content and accepted conversation history. A trace write failure is an evaluator error, even if the task produced a useful artifact.

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. Runtime policy such as max_prelude_bytes and compaction_max_requests is part of the configuration fingerprint; changing either produces a fresh run.

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. The headless runner does not infer human interventions, or whether one was needed, from an error, deadline, or cancellation. Reports can separately join explicit observer annotations as described below.

Observer-reported interventions

An experiment observer can supply counts after a run without editing its result, trace, session, or machine metrics:

myco-eval report /private/results > /private/result-identities.json
myco-eval report /private/results --interventions /private/interventions.json

The first report's results array lists each relative result_path, its run fingerprint, and the SHA-256 of the exact result.json bytes. Copy both identity fields into a version 1 annotation document. The values below are placeholders:

{
  "version": 1,
  "annotations": [
    {
      "fingerprint": "FINGERPRINT_FROM_REPORT",
      "result_sha256": "RESULT_SHA256_FROM_REPORT",
      "human_interventions": 0,
      "observer": "experiment operator",
      "annotated_at": "2026-10-06T12:00:00Z",
      "note": "Observed the entire run; no human steering occurred.",
      "evidence": ["experiment-notes.md#run-1"]
    }
  ]
}

Count deliberate human actions that steer an ongoing evaluated run, such as supplying guidance, changing its workspace, or manually cancelling it. Exclude initial task setup, passive observation, and subsequent grading. Describe the observed actions and any counting assumptions in note. An explicit zero asserts that the observer knows no such action occurred during the entire run. Omit an entry when the count is unknown; never manufacture zero from an absence of signals, errors, or intervention records.

Counts must be unsigned integers. Observer and note are required nonempty strings; annotated_at is the annotation time in RFC 3339 format. Optional evidence strings name the observer's references and must be nonempty. Myco does not fetch or verify those references, authenticate the observer, or establish the truth of the assertions. The hash binds an annotation to result bytes; it does not make the observer's account a machine measurement. Annotation files must be regular files of at most 4 MiB.

Each group's observer_reported_human_interventions is the checked sum only when every included result has an annotation, including failed or cancelled runs. Otherwise it is null. intervention_annotated_runs and intervention_unannotated_runs expose coverage independently of the total. Without an annotation file, all intervention totals remain unknown. Existing success, cost, token, and tool metrics are unaffected; annotations do not change grading or optimizer selection.

The report's intervention_annotations records the source file's SHA-256 and the supplied observer metadata. Report version 1 gains these additive fields; saved run formats are unchanged. Keep the report and annotation file with the experiment evidence. Reports include supplied notes and references, so review them before sharing.

Both fingerprint and result hash must match. Different fresh attempts can share a fingerprint; different result hashes identify their separate annotations. Duplicate exact identity pairs in results or annotations are rejected, as are unknown results, stale or mismatched hashes, unsupported annotation versions, unknown fields, and overflowing totals. Even reformatting result.json changes its hash. Moving a bundle without changing its bytes preserves the binding. Annotations for excluded interrupted directories or another report's results are unknown entries and must be removed from the supplied annotation document.

Comparing runs

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 and reserves each attempt on disk before launching it. Failed attempts and interruptions consume that allowance, even when resuming an older optimizer checkpoint. Existing proposal directories count conservatively toward the allowance; keep them and reflection-budget when resuming. 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.

Myco's SessionRunner handles RequestTooLargeError before that fallback: it compacts saved context, omits retained image payloads, and continues at the failed generation boundary without resetting run accounting or replaying tools. Another size recovery requires a successful model response; a repeated size rejection uses the fallback. Other failures keep their existing retry policy.

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.

After a failed checkpoint, continue_run retries the preserved operation without repeating completed effects. A new submission, manual compaction, or model change instead settles the stopped run before proceeding. recover_interrupted knows whether a live tool batch began: undispatched calls receive not-executed results; dropped in-flight calls receive unknown outcomes. On cold resume, saved intent alone cannot establish whether dispatch began, so recover_checkpoint remains conservative. Every recovery must be saved before further model or tool work.

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. core::WorkspaceFiles owns the directory capability and URL mapping shared by Markdown rendering, the getlink tool, and static file reads. The browser composition installs getlink with that profile's capability; files.rs handles HTTP streaming, ranges, and response headers. 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.

Run artifacts include versioned provenance.json with effective settings and the main-agent prompt, plus timestamped events.jsonl with request/tool identity and failed or abandoned attempts. Authentication values are excluded from the configuration snapshot. Git runs include a self-contained pinned source bundle and a relative frozen recipe; they can replay after moving the run and removing the original repository, using separately supplied configuration and tools. The task eval manual documents artifact portability, event fields, and fingerprint reuse.

cargo test --locked --offline --test evals exercises the executable against local scripted providers: isolated fixtures, cache invalidation after policy changes, grader failures, request limits, broken streams, transient recovery without repeating completed tool effects, and deadline cancellation during backoff. These exact runtime checks need no model credentials; they do not measure real-model completion across repeated compactions.

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.