Mesh Configuration - Dark-Alex-17/coyote GitHub Wiki
The mesh: block of config.yaml says how this Coyote joins and behaves on the Coyote Mesh. This page gives
you the block with every key commented, a table of the keys, the errors a bad value produces, and a closer look at the
keys whose behavior does not fit on one line. Every key has a default, so a config.yaml without a mesh: block is
the same as the block below with enabled: false. The wire format these keys drive is specified separately as SCOPE;
the full specification lives in the scope-protocol repository, https://github.com/Dark-Alex-17/scope-protocol. You do
not need it to set these keys.
A few Reticulum words come up in the block and the tables. An announce is the periodic "I am here" broadcast by which nodes learn of each other. A relay is a node that forwards traffic between others. rnsd is the Reticulum daemon, a small background program that owns the network interfaces so several programs on one machine can share them; its AutoInterface finds other machines on the same local network by itself. Loopback is 127.0.0.1, the machine itself. A propagation node holds a message for a recipient who is offline until they fetch it; that is what "store-and-forward" means below. The envoy is the built-in agent that answers trusted peers for you.
mesh:
enabled: false # Join the mesh whenever this REPL has a session: at start with --session, or when you enter one with .session (default: false);
# .mesh off keeps it off for the rest of the run; one-shot runs (--macro, a prompt on the command line) never join. Nothing
# leaves the machine while this is false
announce: true # Announce this node so peers can see it (default: true); false = outbound-only, see peers without being seen; also turns the
# automatic propagation sync off (`.mesh sync` still works)
display_name: null # Opt-in plaintext label carried in the announce (default: null = nothing but the protocol version is announced)
display_name_on_public: false # Also carry display_name when any interface is type public (default: false; public presence is world-visible)
about: null # One human-written line (up to 200 characters) on what this node's envoy can help with; carried on the status card to trusted
# peers only, never in the announce (default: null = nothing)
interfaces: # Interfaces the node joins. Exactly what is listed here and nothing else; there is no auto-detection and no fallback between
# types (default: a single private entry pointing at the local rnsd on 127.0.0.1:4242)
- type: private # private = TCP client to a relay. The default dials the local Reticulum daemon (rnsd) on loopback; run scripts/mesh-relay.sh
# once per host
host: 127.0.0.1
port: 4242
# - type: lan # lan = link-local only (Reticulum AutoInterface), the no-daemon fallback. Does not cross sbx, WireGuard, Tailscale or
# OpenVPN; only one process per host can bind it
# - type: private # private = TCP client to a relay your team runs; requires host + port
# host: relay.example.com
# port: 4242
# - type: public # public = same wire shape as private, but the relay is a community node so your presence is world-visible; requires host + port
# host: node.example.com
# port: 4242
# Other transports (LoRa/RNode, I2P, UDP, serial) are configured in rnsd and reached through the loopback hop above.
brief: auto # What the envoy serves to trusted peers (default: auto): auto = status card + session digest + user brief;
# manual = card + user brief only, no model spend; off = card only
digest_prompt: null # Prompt used to build the shareable session digest. If null or blank, uses built-in default
brief_model: null # Model used to build the digest (e.g. 'openai:gpt-4o-mini') (default: null = the session's current model)
envoy_model: null # Model the envoy answers peers with (default: null = the configured model_id; the envoy never follows a `.model` switch)
envoy_escalation_timeout: 0 # Seconds the envoy holds a peer's question open for the human before handing it off (default: 0 = hand off at once; the
# question stays open for `.mesh answer`; a hold longer than the 120 s run ceiling is cut to it)
knock_retention_hours: 24 # How long unanswered knocks from known identities are kept (default: 24; 1 or more)
collision_protection: false # Whether an identity trusted for all destinations is refused when it announces or claims an instance id recorded under another
# identity, or heard in the peer table under one trusted for all destinations (default: false = served, with a warning to you;
# true = refused with an error until you trust its new destination with `.mesh trust`; a destination you trusted explicitly is
# served either way)
peer_max_concurrent: 1 # Envoy runs one sending identity can have queued or running at once (default: 1; 0 = unlimited)
peer_max_messages_per_hour: 60 # Messages accepted from one sending identity per hour, in fixed hourly windows kept in memory (default: 60; 0 = unlimited)
peer_max_tokens_per_hour: 100000 # Model tokens one sending identity can cost per hour, counted after each envoy run (default: 100000; 0 = unlimited)
peer_max_cost_usd_per_hour: 0 # USD one sending identity can cost per hour, enforced only when the envoy model's prices are known (default: 0 = no cost ceiling)
request_timeout_secs: null # Seconds to wait for a peer's answer on /message, /knock, /list, /access and /status; raises a path's built-in deadline (15 s; 30 s for /status) and never lowers one, so under 15 is refused; /fetch keeps its own 120 s and the mesh__peers status sweep its 5 s (default: null = each path's built-in deadline; 15 to 31536000)
link_timeout_secs: null # Seconds to wait for the link to open and identify before a request on the same five paths; raises the built-in 10 s and never lowers it, so under 10 is refused (default: null = 10; 10 to 31536000)
propagation_sync_interval_secs: 300 # Seconds between automatic fetches of the messages a propagation node holds for this node; the first fetch runs once a
# propagation node is heard after the node joins (default: 300; 0 = fetch only on `.mesh sync`; at most 31536000); off
# while `announce` is false
fetch: # File sharing between trusted peers
inline_max_bytes: 65536 # Largest file a peer may attach inline to a message; larger files are dropped by the receiver (default: 65536; must be 1 to
# 98304, the per-message total)
max_bytes: 4194304 # Largest file this node serves to a peer that fetches it; a larger one is refused (default: 4194304; must be 1 to 4194304, 4 MiB; served limit is currently capped near 1 MiB by a transport workaround, see below)
inbox_dir: null # Absolute directory where files peers send inline, and files this node fetches, are staged, under <inbox_dir>/<instance id>;
# keep it outside any workspace you share, or peers could fetch each other's staged files
# (default: null = <cache dir>/mesh/inbox/<instance id>)
envoy_memory: # What the envoy remembers of a trusted peer's thread between messages; off by default, so the envoy carries nothing a peer
# said into its next run (the inbox still files every message and reply)
enabled: false # Keep the peer's turns and the envoy's answers per (sender identity, thread) so a follow-up in the same thread is answered
# in context; kept under <cache dir>/mesh/envoy-sessions/<instance id>, owner-only (default: false)
max_sessions: 256 # Most threads remembered across all peers; the least recently used go first (default: 256; must be 1 or more)
max_per_identity: 16 # Most threads remembered for one peer identity; the least recently used go first (default: 16; must be 1 or more)
max_turns: 40 # Most turns kept per thread; the oldest exchange goes first (default: 40; must be 1 or more)
max_bytes: 65536 # Most bytes of turn text kept per thread; the oldest exchange goes first, and kept turns that do not fit the envoy model's
# context are left out of the run, oldest first, and stay remembered (default: 65536; must be 1 or more)
ttl_hours: 168 # Hours a thread is remembered after its last written exchange (default: 168; must be 1 or more)| Key | Type | Default | Meaning | Checked |
|---|---|---|---|---|
enabled |
bool | false |
Joins the mesh when the REPL starts with a session or when you enter one (the .mesh on --yes path); validates the block at load and arms the function-calling guard |
- |
announce |
bool | true |
Announce this node. false = outbound-only: see peers without being seen, and no automatic propagation sync |
- |
display_name |
string or null | null |
Plaintext label in the announce and the status card. At most 64 bytes, no control or invisible characters | at .mesh on
|
display_name_on_public |
bool | false |
Carry display_name in announces even when a public interface is configured |
- |
about |
string or null | null |
One human-written line on what this node's envoy can help with; carried on the status card to trusted peers only, never in the announce | <= 200 characters when enabled |
interfaces |
list | [{type: private, host: 127.0.0.1, port: 4242}] |
{type: private|public, host, port} with port 1-65535 (the default is the local rnsd on loopback), or {type: lan} (no host or port; the no-daemon fallback) |
at parse; count when enabled |
brief |
auto, manual, off
|
auto |
What the envoy serves; see The brief | at parse |
digest_prompt |
string or null | null |
Prompt for the session digest; null or blank uses the built-in prompt | - |
brief_model |
string or null | null |
Model for the digest; null = the session's current model | - |
envoy_model |
string or null | null |
Model the envoy answers with; null = the configured model. Never follows .model
|
- |
envoy_escalation_timeout |
seconds | 0 |
How long the envoy holds a peer's question for your .mesh answer before handing off; cut to the 120 s run ceiling |
- |
knock_retention_hours |
hours | 24 |
How long knocks are kept | >= 1 when enabled |
collision_protection |
bool | false |
Refuse an identity trusted for all destinations when it claims an instance recorded under another identity, or heard in the peer table under one trusted for all destinations; false = serve it with a warning. See collision_protection
|
- |
peer_max_concurrent |
count | 1 |
Envoy runs one identity can have queued or running at once; 0 = unlimited |
- |
peer_max_messages_per_hour |
count | 60 |
Messages accepted from one identity per hour; 0 = unlimited |
- |
peer_max_tokens_per_hour |
count | 100000 |
Model tokens one identity can cost per hour; 0 = unlimited |
- |
peer_max_cost_usd_per_hour |
number | 0 |
USD one identity can cost per hour; 0 = no ceiling |
finite and >= 0 when enabled |
request_timeout_secs |
seconds or null | null |
How long to wait for a peer's answer on /message, /knock, /list, /access and /status; raises a path's built-in deadline (15 s; 30 s for /status) and never lowers one; null = each path's own; /fetch and the mesh__peers status sweep keep theirs. See Timers
|
15 to 31536000 when enabled |
link_timeout_secs |
seconds or null | null |
How long to wait for the link to open and identify before a request on the same five paths; raises the built-in 10 s and never lowers it; null = 10. See Timers
|
10 to 31536000 when enabled |
propagation_sync_interval_secs |
seconds | 300 |
Seconds between automatic fetches from a propagation node; the first runs once a node is heard after the join; 0 = only on .mesh sync; off while announce is false |
<= 31536000 when enabled |
fetch.inline_max_bytes |
bytes | 65536 |
Largest file a peer may attach inline to a message; larger files are dropped by the receiver | 1 to 98304 when enabled |
fetch.max_bytes |
bytes | 4194304 |
Largest file this node serves to a peer that fetches it; a larger one is refused. See fetch for the ceiling currently applied under it | 1 to 4194304 when enabled |
fetch.inbox_dir |
absolute path or null | null |
Where files peers send inline and files this node fetches are staged, under <inbox_dir>/<instance id>; null = <cache dir>/mesh/inbox/<instance id>
|
absolute when set and enabled |
envoy_memory.enabled |
bool | false |
Remember a trusted peer's thread between messages: the peer's turns and what the node sent back, per (proved sender identity, thread), owner-only under <cache dir>/mesh/envoy-sessions/<instance id>; off by default, so nothing a peer says is carried into its next run. See envoy_memory
|
- |
envoy_memory.max_sessions |
count | 256 |
Most threads remembered across all peers; the least recently used go first | >= 1 when enabled |
envoy_memory.max_per_identity |
count | 16 |
Most threads remembered for one peer identity; the least recently used go first | >= 1 when enabled |
envoy_memory.max_turns |
count | 40 |
Most turns kept per thread; the oldest exchange goes first | >= 1 when enabled |
envoy_memory.max_bytes |
bytes | 65536 |
Most bytes of turn text kept per thread; the oldest exchange goes first. Kept turns that do not fit the envoy model's context are left out of the run, oldest first, and stay remembered | >= 1 when enabled |
envoy_memory.ttl_hours |
hours | 168 |
Hours a thread is remembered after its last written exchange | >= 1 when enabled |
.mesh info prints every row as loaded, about, fetch.inline_max_bytes, fetch.max_bytes, fetch.inbox_dir and
the six envoy_memory.* keys included; digest_prompt shows default or custom (the prompt body is never printed) and a zero cost ceiling shows
0 (off); a zero sync interval shows 0 (manual), and one made inert by announce: false shows
300 (off: announce is false). With the node on, an envoy memory row follows the node facts: off, or how many
conversations of how many identities this instance's store holds.
Unknown keys inside mesh: are ignored, like unknown keys elsewhere in config.yaml. Interface entries are the
exception: an unknown key inside one is refused.
Interface entries are checked whenever the config is parsed, enabled or not:
| Input | Error |
|---|---|
type: lan with host or port
|
type 'lan' takes no host or port; use 'private' or 'public' to attach to a relay |
type: private or public missing one |
type 'private' requires both host and port |
blank host
|
type 'private' requires a non-empty host |
port: 0 |
port 0 is out of range; use 1-65535 |
type: auto |
type 'auto' was removed and is no longer valid; use 'lan' for link-local discovery (the likely intent), or 'private'/'public' with host and port for a relay |
any other type
|
unknown type '<other>'; valid values are lan, private, public |
| a bare string instead of a mapping | a mapping like {type: lan} or {type: private, host: relay.example.com, port: 4242} |
The rest is checked only when enabled is true: at load, at .mesh on, and at node start:
| Condition | Error |
|---|---|
function_calling_support: false |
see Function calling |
interfaces: [] |
mesh.interfaces is empty; list at least one interface (the default is a single {type: private, host: 127.0.0.1, port: 4242} entry, the local rnsd) |
two lan entries |
mesh.interfaces lists lan more than once; only one lan interface can be bound per process |
knock_retention_hours: 0 |
mesh.knock_retention_hours is 0, which is out of range; use 1 or more |
| a negative or non-finite cost ceiling | mesh.peer_max_cost_usd_per_hour is <value>, which is out of range; use 0 (no ceiling) or a positive amount |
request_timeout_secs under 15 or over 31536000 |
mesh.request_timeout_secs is <value>, which is out of range; use 15 (the shortest built-in deadline, which this key raises but never lowers) to 31536000 (one year), or null to keep each path's built-in deadline |
link_timeout_secs under 10 or over 31536000 |
mesh.link_timeout_secs is <value>, which is out of range; use 10 (the shortest built-in deadline, which this key raises but never lowers) to 31536000 (one year), or null to keep each path's built-in deadline |
| a sync interval over one year | mesh.propagation_sync_interval_secs is <value>, which is out of range; use 0 (manual) or up to 31536000 (one year) |
about over 200 characters |
mesh.about is <n> characters, which is over the cap; use 200 or fewer |
fetch.inline_max_bytes out of range |
mesh.fetch.inline_max_bytes is <value>, which is out of range; use 1 to 98304 |
fetch.max_bytes out of range |
mesh.fetch.max_bytes is <value>, which is out of range; use 1 to 4194304 |
a relative fetch.inbox_dir
|
mesh.fetch.inbox_dir is '<value>', which is not absolute; use an absolute path |
envoy_memory.max_sessions: 0 |
mesh.envoy_memory.max_sessions is 0, which is out of range; use 1 or more |
envoy_memory.max_per_identity: 0 |
mesh.envoy_memory.max_per_identity is 0, which is out of range; use 1 or more |
envoy_memory.max_turns: 0 |
mesh.envoy_memory.max_turns is 0, which is out of range; use 1 or more |
envoy_memory.max_bytes: 0 |
mesh.envoy_memory.max_bytes is 0, which is out of range; use 1 or more |
envoy_memory.ttl_hours: 0 |
mesh.envoy_memory.ttl_hours is 0, which is out of range; use 1 or more |
display_name is checked when .mesh on encodes the announce:
mesh.display_name is N bytes, which is over the 64-byte limit announces carry; shorten it in config.yaml and
mesh.display_name contains a control or invisible formatting character (U+XXXX); remove it in config.yaml so peers can read the name.
enabled: true does three things: the whole block is validated when the config loads, the function-calling guard
below is armed, and the REPL joins the mesh whenever it has a session, in any context: before its first prompt when
started with --session, and when you enter one later with .session <name> or .agent <name> <session> (an
agent's default session included). The join is the .mesh on --yes path: it prints
mesh.enabled is true in config.yaml: joining the mesh for this session (.mesh off leaves it)., then the same
disclosure of what leaves the machine, and asks nothing. A precondition that .mesh on would refuse (no session, for
example) is printed as the same error and the mesh stays off; the REPL goes on. Only the REPL joins: coyote --macro
and a prompt given on the command line never start a node, so nothing outlives the command. The mesh needs a session:
started without --session the REPL prints Mesh needs a session: ... and joins as soon as .session <name> or
.agent <name> <session> has run. A macro run from the prompt whose steps enter a session joins once the macro is
done. A typed .mesh off holds for the rest of the run (its notice says so) and the sessions entered after it stay
off until .mesh on. .exit session leaves the mesh, and so does .exit agent while the agent holds a session,
since the node's instance id belongs to the session being exited, without taking that decision: the next session
joins again.
.mesh on sets enabled for the session only and never writes it back to config.yaml. With enabled: false,
.mesh on still works: it validates the block as if enabled were true and joins for the session.
How often the node fetches what a propagation node holds for it; see .mesh sync for
what each sync prints. Sideband and NomadNet, the reference clients for LXMF (the messaging layer the mesh runs over),
sync every 12 and 6 hours; a REPL that is being talked to waits 5 minutes. 0 turns the automatic fetch off, including
the one at join; .mesh sync still works. Each automatic fetch identifies this node to the nearest propagation node
heard (the node picks itself; you cannot pick one by hand yet), so a node with announce: false, which is meant to be
unseen, never fetches on its own: only .mesh sync, which is you contacting a node, does. The .mesh on preview says
which of the three applies.
What happens when an identity you trusted with .mesh trust --identity announces or claims an instance id whose
record is bound to another identity, or that the peer table holds under another identity you trusted the same way;
see Key changes. A colliding record is marked either way; when the collision is found
only in the peer table nothing is marked in either mode. With false, the default, the identity is served and you get a
warning: line. With true the collision is judged before the identity grant, so the identity is refused and you
get an error: line until you run .mesh trust <new destination>; only in this mode, and only for an identity
grant, does the verdict consult the peer table. A refusal found there is
remembered for as long as the node runs from the refusal itself, whether or not its error: line reached you, so
the old key's row ageing out of the table does not lift it. The memory names the key first heard holding the instance,
which it never refuses, whichever key presents first, and refuses the other key only while the key first heard stays
trusted for all destinations: .mesh trust <new destination> admits the new key and clears the memory,
.mesh block of either key clears it too, and .mesh untrust --identity <old key> admits the new key for as long
as the old one stays untrusted. The memory is in-process only (peers.json survives a restart, the memory does
not), so after a restart the refusal stands only while the old key's row is still in the table.
A destination you trusted explicitly is served in either mode, and an identity with no grant of its own is refused
in either mode. It decides what this node serves; what it sends, and the trust column of .mesh peers, is
unchanged. The setting is read at .mesh on and shown by .mesh info.
The three fetch.* keys bound file sharing; the share set, grants and the verbs are on
Mesh File Sharing.
max_bytes is what this node is willing to serve, but the limit a request is held to today is
min(max_bytes, 1048447), about 1 MiB: the current transport layer can only send a file that fits one segment. A
larger file is refused as too_large with the limit actually applied, and a pre-flight on .mesh grant says
it is <size> and this node serves at most <limit> bytes. The ceiling goes away once the transport layer can send
larger files; max_bytes then applies alone. Raising max_bytes above the ceiling changes nothing today.
inline_max_bytes is the other direction: the largest file a peer may attach inline to a message this node receives.
The per-message inline total is 98304 bytes, which is why the key cannot exceed it.
inbox_dir moves the staging inbox. Files peers send inline and files this node fetches land under
<inbox_dir>/<instance id>/<peer's full destination hash, lowercase>/<path>; nothing is ever served from the inbox,
but keep it outside any workspace you share all the same, and outside any tree you would rather not have peer-written
files appear in. Nothing sweeps it: staged files stay until .mesh inbox --purge-files or you remove them.
The six envoy_memory.* keys govern what the envoy remembers of a trusted peer's thread between messages; what is
stored, and what never is, is on Mesh.
enabled is off by default: the envoy starts every run with no memory of the peer, nothing is created under
<cache dir>/mesh/envoy-sessions/, and the inbox still files every message and reply. On, the envoy keeps the peer's
turns and what the node sent back per (proved sender identity, thread), and a follow-up in the same thread is answered
with them in context.
The five bounds are each 1 or more, enforced when a record is written and swept when the node starts and
periodically while it runs:
max_sessions caps the threads remembered across all peers and max_per_identity the threads of one identity, the
least recently used going first in each; max_turns and max_bytes cap one thread, the oldest exchange going first,
cut at the next turn the peer spoke so an answer is never kept without what it answered; ttl_hours counts from the
thread's last write. Size max_bytes to the envoy model's context window: remembered turns that do not fit it are
left out of the run, oldest first, and stay remembered, so a record larger than the window is never read in full.
The store is per instance: a fork (.mesh on --fresh, or a re-key) binds its own <instance id> directory and
remembers its own threads, and the envoy memory row of .mesh info and .mesh status counts the running
instance's store alone; .mesh memory forget all --dry-run shows the total across every instance store.
.mesh memory forget <identity|all> [thread] wipes conversations at will, and
.mesh untrust --identity and .mesh block sweep the identity's threads from every instance store under the cache
directory, whether or not the memory is on; see Mesh Commands.
The mesh sends and receives messages through tools, so it needs function calling on both sides. A config with
enabled: true and function_calling_support: false is refused at load:
mesh.enabled is true but function_calling_support is false: the mesh sends and receives messages through tools, so it needs function calling. Set function_calling_support: true in config.yaml, or run .set function_calling_support true in the REPL. Function calling can only be enabled when at least one tool is installed, so install tools first if you have none.
.set function_calling_support false is refused while the node is running and while mesh.enabled is true in memory;
the messages are on Mesh Commands.
The four peer_max_* keys bound what one sending identity can make this node accept and spend. Windows are fixed
one-hour periods per identity, kept in memory only, so a restart opens a fresh window; 256 identities are tracked.
Any one of the four at 0 is unlimited for that budget alone; the others still apply. The message, run and token
budgets default to bounded values; the cost ceiling defaults off (spend stays bounded through the token ceiling).
Only this node's own config lifts a bounded one: a peer cannot ask for more.
Enforcement happens at two points:
-
Message admission:
peer_max_messages_per_hour. A reply to a question this node asked bypasses it. -
Envoy run reservation:
peer_max_concurrentfirst, then the token ceiling, then the cost ceiling. Usage is charged after the run, so runs in flight can overshoot a ceiling by at mostpeer_max_concurrentruns before the next is refused.
What the two points do with a refused message differs:
-
Message admission (
rate_limited). On a live link the message is not filed anywhere and the sender's request is answered with thethrottled (0xf6)code, so it readsThe peer refused the message: throttled (0xf6)and can retry. A message that arrived by store-and-forward is filed in your inbox without an envoy run, and the sender gets one typed refusal reply per identity per reason per hour. -
Envoy run reservation (
peer_concurrency,token_ceiling,cost_ceiling, and the envoy's ownenvoy_busyandenvoy_stopping). The message is always filed in your inbox for you to read, and the sender always gets a reply for the reasons above.
Either way the REPL shows one folded line per identity per reason per hour. A typed refusal travels as
fields: {"refusal": "<reason>", "retry_after_secs": N}; capacity refusals say to retry in 120 s.
| Reason | What the peer reads |
|---|---|
rate_limited |
This node has taken as many messages from you as it accepts in an hour; try again when the hour is up. |
envoy_busy |
This node's envoy is busy; try again in a couple of minutes. |
envoy_stopping |
No answer: this node is shutting down. |
peer_concurrency |
This node is not taking another message from you until the current one finishes. |
token_ceiling |
This node has spent as many model tokens on you as it allows in an hour; try again when the hour is up. |
cost_ceiling |
This node has spent as much on you as it allows in an hour; try again when the hour is up. |
The cost ceiling is enforced only when the envoy model's prices are known.
Every peer request has a built-in deadline for the answer and another for the link to open and identify first:
15 s / 10 s on /message, /knock, /list and /access, 30 s / 10 s on /status, 120 s / 10 s on /fetch.
request_timeout_secs and link_timeout_secs raise those deadlines on the first five paths, for a slow radio
link or a peer whose envoy takes its time; neither ever lowers one, so a value under the shortest built-in
deadline (15 for the request, 10 for the link) is refused at load, as is one over one year (31536000), the same
cap as propagation_sync_interval_secs. /fetch and the status sweep behind mesh__peers with
with_status: true (5 s / 5 s) keep their own deadlines whatever the two keys say. .mesh info shows both keys,
null while unset. Before any of those deadlines runs, a request to a trusted peer the transport holds no route to
(one known only from the saved peer table, after a restart) waits up to 5 s for the network to answer a path
request; that wait is fixed, and the status sweep skips it, reporting such a peer unreachable at once instead of
waiting for each.
The gate is announce-only and all-or-nothing across interfaces: when any entry is public, the display name is
withheld from every announce, lan and private included, unless display_name_on_public is true. The status card
still carries the name to trusted peers either way, because it reaches trusted destinations and nobody else.
| Variable | Holds |
|---|---|
COYOTE_CONFIG_DIR |
mesh/identity.key, mesh/identity.key.lock, mesh/identity.predecessors.jsonl, mesh/trust.yaml, mesh/trust.yaml.lock, mesh/shares.yaml (the global share rules), mesh/shares.yaml.lock
|
COYOTE_CACHE_DIR |
mesh/peers.json, mesh/knocks.jsonl, the pending and inbound correlations, the instance locks, mesh/propagation.json, mesh/grants-<instance_id>.jsonl (one-off grants), mesh/inbox/<instance_id>/ (the staging inbox, unless fetch.inbox_dir moves it), mesh/envoy-sessions/<instance id>/ (the envoy memory, owner-only; absent while envoy_memory.enabled is false) |
The workspace share rules are in neither: <workspace root>/.coyote/mesh-shares.yaml, where
COYOTE_WORKSPACE_CONFIG_DIR renames the .coyote part.
There is no --config-dir flag; the directories are chosen by these environment variables (or XDG_CONFIG_HOME /
XDG_CACHE_HOME). Two sessions on one machine need nothing split to run: with one config dir they share the identity
and the cache (peers.json is last-writer-wins between them, which is harmless), each gets its own instance id and
destination, and all of them dial the local rnsd. mesh/trust.yaml is shared live: a trust, untrust or block made in
one REPL is seen by the other before its next decision, and two REPLs never overwrite each other's records; the write
is serialised through mesh/trust.yaml.lock. Two separate identities on one machine, to exercise trust from both
sides, need both directories split, so that each identity keeps its own trust, knock cache and propagation store; the
recipe is on Mesh Deployment. See also
Environment Variables.