Expand description
Client shell of the messenger: the store key (random, in the client vault), the one web-bot wake,
and the HTTP a released OutgoingRequest actually performs.
(Historical: the store key was SHA-256 of the session id string; it is now random and lives in the vault, see store_key.) store_seal_key is that old derivation. The client derives
it when it opens the store. It is not a passphrase, it is not stored
beside the records, and nothing here is a KDF or a vault.
The private Olm account stays in this store. MessengerCore::open_sealed
calls OlmAccountState::load_or_create and pickles it under the seal.
Clients exchange only public keys with the server, through the existing
keys upload and keys query paths.
Outbound mail to other agents is MessengerCommand::SendMessage and
/sync, not POST /mail/send and not POST /admin/listener.
Inbound wake is not a second mailbox. After the engine has a readable
room text from someone else, this shell posts a small JSON object to
one routine URL through post_decrypted, or pushes the same text on
the leader socket. The plaintext stays in body. from is the sender
mxid, event_id is the Matrix event id, and nick is the sender’s
display name only when this shell already has one. Which of those two
triggers is armed depends on the open path, not on a file. Missing the
one this path uses skips that trigger. It does not drop the message
and it is not an error. Routine replay skips texts already loaded.
A leader prompt skips keys already listed in leader-prompted. When
that file records nothing, joined rooms with an empty timeline are
paged once from the stored sync token, and only the newest inbound
text in each room is prompted.
The session bearer stays in memory on OpenedStore. It comes from the client’s own
key-signature login (the identity lives in the vault; see m4a-agent), is sent as
Authorization: Bearer and is never written next to the sealed records. No password, token or
device bearer is ever read from the environment or handed in by a host.
Paths the engine builds under /_matrix are sent without that prefix:
mail4agent-server-bin mounts the Client-Server router at /client/v3.
Two open paths, one shell. This is not a second product.
The web machine client is MachineClient::from_env. One process
prefers the live agents directory (AGENTS_DIR_ENV /
DEFAULT_AGENTS_DIR): each folder is an agent id (also the mail
session id) and profile.json has the display name. When that
directory is absent it reads SESSIONS_DIR_ENV. The nick is
nick_from_display_name of that display name. Mail between sessions
this client holds is in process
(MachineClient::set_local_delivery). A peer that is not in the list
uses the homeserver. Wake is the bot’s own webhook routine, named by
its nick, which only the bot can create (UpdateRoutine). This process
keeps a disabled local mirror in the same folder so the gateway hands
out that routine’s URL and key; a null key is retried, never replaced
by a second routine. ensure_agent_webhook_routines does that without
opening stores. MachineClient::poll_agent_directory retries and
rescans for new bots. The URL and key live in memory and in the
session’s keychain file under the store root, and are not logged. This
path does not read LEADER_SOCK_ENV.
A node is OpenedStore::connect_node_from_env. One CLI session, woken
by ACP on LEADER_SOCK_ENV. A routine URL or bearer in the environment
is refused before register. This path does not gain a webhook config.
OpenedStore::connect still registers one session a caller already
built; it is not either of those open paths and it does not read a wake
from the environment.
Re-exports§
pub use provider::plan_chain;pub use provider::HostEnv;pub use provider::HookFlavor;pub use provider::ResumeSpawnAdapter;pub use provider::SessionRecord;pub use provider::WakeChain;pub use provider::WebVendor;pub use provider::adapter_for;pub use provider::wake_prompt;pub use provider::AdapterConfig;pub use provider::ClaudeChannelAdapter;pub use provider::ClaudeRoutineFireAdapter;pub use provider::CodexAppServerAdapter;pub use provider::CodexCloudAdapter;pub use provider::CodexEndpoint;pub use provider::CursorAgentAdapter;pub use provider::GrokLeaderAdapter;pub use provider::KimiServerAdapter;pub use provider::NoInboundAdapter;pub use provider::ProviderKind;pub use provider::ProviderSession;pub use provider::RoutineWebhookAdapter;pub use provider::SessionKind;pub use provider::Surface;pub use provider::WakeAdapter;pub use provider::WakeError;pub use provider::WakeLetter;pub use provider::WakeOutcome;pub use provider::INBOX_DIR_ENV;pub use provider::PROVIDER_ENV;
Modules§
- cli
- The client’s commands. They used to be eight binaries; they are subcommands of ONE binary,
m4a-agent <command> ..., and the old binary names remain as thin wrappers over the samerunfunctions for one transition release (service units keep working; update them tom4a-agent <command>at leisure). - provider
- Provider wake adapters for the one-per-machine mail4agent clients.
- resolve
- The five places a session id used to come from, as sources of ONE resolver chain
(
m4a_agent::ResolverChain). Each source only names the session (and, when it knows it, the tier-1 local session id); identity, nick and credentials are the client’s identity store’s. The chain refuses two sources that name different sessions for one process. - store_
key - The store key. It used to be SHA-256 of the session id: anyone who could guess the id and read
the directory could open the store. Now it is 32 random bytes in the client’s vault
(
<store_root>/.m4a-agent/), and stores sealed under the old derivation are moved over, one way:
Structs§
- CmdReply
- The answer to one
CmdRequest. - CmdRequest
- One command request.
- Decrypted
Wake - One decrypted room text the routine should learn about.
- Device
Id - A device id: an opaque, server-assigned string with no sigil, one per
authenticated session (plan §3.1’s
MessengerCore::device_id). - EventId
- A Matrix event id:
$opaque(room version 3+ shape — no server-name suffix; the opaque part is itself a content hash in most room versions, but this crate does not interpret it further). - Found
Session - A session found by nick. No routine URL and no bearer.
- Grok
Listener - Push listener for every live local grok session.
- Heard
- One live grok CLI session the index named.
- Host
Session - One bot session the host says lives on this machine.
- Listen
Report - What one
GrokListener::tickdid. Nicks only. No bearer, no session id. - Machine
Client - The sessions on one machine, and the in-process bus they use when the peer is one of them.
- Node
Client - One local ACP node: a single
OpenedStore, its push link, and an optional send socket form4a-send. - Node
Tick Report - What one
NodeClient::tickdid. Event ids, room ids, and error texts only — never URLs, keys, or bearers. - Opened
Store - A sealed messenger core opened for one session, plus the homeserver it performs released requests against. The Olm account was loaded or created into it. Private key material stays here. The device bearer stays in memory and is zeroed when this value is dropped.
- Outgoing
Message - One message a caller wants sent via
MessengerCommand::SendMessage— M13b’s binding content shape: text/notice/emote, an optional reply pointer, and an optional edit target (which must name one of the caller’s own already-sent events —MessengerCore::dispatchrejects an edit of someone else’s event rather than silently sending a replacement the room’s other members will refuse to accept, perm.replace’s own “must come from the original sender” rule,crate::room::relations’s own module doc).reply_toandedit_ofare mutually exclusive at the wire level — if both are set,edit_ofwins (an edit’s ownm.relates_toism.replace, not a reply). - Pushed
Room Event - One room event the homeserver pushed for a single session.
- RoomId
- A Matrix room id:
!opaque:server_name(client-server API’s ownroom_idgrammar — opaque per spec, never derived from the room’s own alias or name). - Room
View - One room this store currently holds, from this account’s own view.
- Routine
Report - One bot’s wake routine, by nick and folder id.
- Routine
Wake - One URL per bot, already created. Neighbors are addressed by nick or mxid on the server.
- Send
Reply - The answer to one
SendRequest. - Send
Request - One request on the send socket.
- Session
Config - One session of the client, ready to log in. Built from the host environment
(
SessionConfig::from_env) orSessionConfig::new_identity. The nick is the one the operator assigned with the invite; it is known after the first login. - Session
Wake - Wake targets for one shell. Empty fields mean that trigger is off.
routine_beareris kept only in memory and only attached whenroutine_urlis set. The caller fills it from the environment, never from a literal in source. - Text
View - One text-like timeline row. Ciphertext is not included.
- Tick
Report - What one
MachineClient::tickdid. Nicks, event ids, room ids, and error texts only. - UserId
- A Matrix user id:
@localpart:server_name(client-server API’s ownuser_idgrammar). - Wake
Attempt - One routine POST: the Matrix event id it carried and the HTTP status
(
Nonewhen no response arrived).200means the routine woke. - Wake
Options - Knobs for
ensure_agent_webhook_routines. All machine-specific, so they come from the environment (WakeOptions::from_lookup).
Enums§
- Create
Room Kind - Which shape
MessengerCommand::CreateRoombuilds, mapped onto the server’s ownPOST /createRoomfield set (visibility/is_direct/invite/name/topic— the server deriveskinditself fromvisibility+is_direct, per the server plan’s §5; a client never sends apreset/kindfield directly). - Message
Kind - Which
m.room.messageshapeOutgoingMessagerenders as (M13b send pipeline). - Messenger
Command - One command a shell/adapter dispatches into the core — both the M13a
receive-side variants (
MarkRead,SetTyping,RetryDecryption,LoadOlder) and the M13b send/room-membership ones. OnlyPartialEq(notEq):MessengerCommand::SetTag’sorder: Option<f64>cannot implementEq. - Room
Kind - What kind of room this is, derived from its current state – never a
separately stored flag (see
RoomState::derive_room_kind’s own doc for the precedence between the two possible signals). - Shell
Error - Why opening the client store, releasing a send, or posting a routine failed. The seal key and the device bearer are never included.
- Wake
Status - Where one bot’s wake stands after
ensure_agent_webhook_routines. Carries no URL and no key.
Constants§
- AGENTS_
DIR_ ENV - Live Grok Bot agents on this machine. Each child folder name is an
agent id;
profile.jsonhas the displayname.MachineClient::from_envprefers this overSESSIONS_DIR_ENVwhen the directory exists. - AGENT_
RESCAN_ SECS_ ENV - Optional rescan period in seconds for
MachineClient::poll_agent_directory. Unset or0means the caller decides when to poll; open still scans once. - BOT_
NAME_ ENV - Display name of this Grok Bot web session, the name the host already shows. The nick is derived from it. This is not a nick slug.
- CONFIG_
ENV - Optional path of a toml file whose
homeserver_urlis used whenHOMESERVER_URL_ENVis unset. Unset looks at./mail4agent.tomland ignores it when that file is absent. - DEFAULT_
AGENTS_ DIR - Default agents directory, relative to the user’s home (
$HOMEor%USERPROFILE%). Used whenAGENTS_DIR_ENVis unset and the resolved path is a directory. - DEFAULT_
SOCK_ NAME - Socket file name under the store root when
SEND_SOCK_ENVis unset. - ENV_
FILE_ ENV - Env file with the machine’s client settings (
KEY=VALUElines), read bym4a-web-clientandm4a-sendfor every variable the process environment leaves unset. Default$HOME/.config/mail4agent/web-client.env. Settings only: homeserver URL, store root, keychain dir, skip list, session-id aliases. Never secrets. - HOMESERVER_
URL_ ENV - Homeserver origin for a Grok Bot web session, for example
http://127.0.0.1:8741. Wins overCONFIG_ENV/mail4agent.toml. There is no built-in host. - LEADER_
CWD_ ENV session/loadworking directory whenLEADER_SOCK_ENVis set. Unset uses the process current directory.- LEADER_
SOCK_ ENV - Leader socket for a local session (
leader.sock). Unset: no ACP push. - MAX_
SEND_ BYTES - Longest text one send carries.
- NODE_
DEFAULT_ SOCK_ NAME - Default send-socket file name under the store root for the node client.
- PRODUCT_
INVITE_ ENV - Identity mode: the operator’s one-time invite code (read by the CLIENT process, once; the agent is never given it). Not needed after the identity is enrolled.
- PRODUCT_
URL_ ENV - Product-session mode: base URL of the product server. Every Client-Server call goes through its proxy with the product session token as bearer.
- PROFILE_
NOTE_ ENV - Set to
1to write the bootstrap note into the profile description of each bot that has no routine yet, through the gateway’supdateAgent(the host pushes profile edits to the server copy of the bot). The note asks the bot to create its own routine namedcrate::routine_folder_idof its nick on its next turn. Off by default: it edits a description the owner wrote. - ROUTINE_
BEARER_ ENV - Optional
Authorization: BearerforROUTINE_URL_ENV. Sent only when that URL is also set. A host keychain injects this into the process environment from outside. It is never written to disk and never read from source. - ROUTINE_
URL_ ENV - Routine POST target. Unset or empty: no POST. There is no default URL.
- SEND_
COMMAND - Command a woken bot runs to answer, as shown in the wake’s
reply. - SEND_
SOCK_ ENV - Path of the web client’s local send socket.
- SESSIONS_
DIR_ ENV - Directory of session records. Each
*.jsonfile isbot_name,session_id, and an optionalagent_id. Used when no agents directory is available. Unset means the host passed the list toMachineClient::openinstead. - SESSION_
IDS_ ENV - Comma-separated
agent_id=session_idpairs. An agent listed here uses that mail session id instead of its agent id, for a bot whose session was registered before agent-id sessions. Machine-specific, so it lives in the environment. - SESSION_
ID_ ENV - Session id the web host already assigned. The store directory is
session_store_dirof this id. The same id reopens the same store. - SKIP_
NICKS_ ENV - Comma-separated nicks this client leaves alone: no session, no mirror, no credential call. Machine-specific, so it lives in the environment and not in source.
- STORE_
LOCK_ FILE - Lock file in each sealed session directory.
MachineClient::openholds an exclusive lock on it for as long as the client lives, so a second process (another client, orm4a-sendopening the store itself) cannot write the same Olm state at the same time. - STORE_
ROOT_ ENV - Shared store root on this machine. The caller supplies it. Unset is not a shared fallback outside tests.
- TIER_
ENV - Identity mode:
server(our product API, default) ormatrix(the Matrix client API). - WAKE_
KEYCHAIN_ FILE - Per-session keychain file under the session’s sealed directory
(
crate::session_store_dir). Mode 0600. Holds the folder id, the webhook URL, and its key. Outside any repository; never logged.
Functions§
- ensure_
agent_ webhook_ routines - Ensures each agent’s local mirror (webhook trigger, disabled, folder =
crate::routine_folder_idof the nick) and asks the gateway for its credential. Bots whose nick is inskip_nicksare not touched. Does not open sealed stores, does not register on the homeserver, and does not log or return URLs or keys. Whenstore_rootis set, a ready URL and key are written to that session’s keychain file. Withprofile_note, a bot still waiting gets the bootstrap note. - ensure_
agent_ webhook_ routines_ from_ env ensure_agent_webhook_routinesusing the host gateway file, the agents directory from the environment (orDEFAULT_AGENTS_DIR),SKIP_NICKS_ENV, andSTORE_ROOT_ENVwhen set.- hear
- Reads an
active_sessions.jsondocument and names each row from that session’s own topic. Empty text is an empty list. A document that is not that index is an error. - load_
agents_ dir - Reads each agent folder under
dir. Folder name is the agent id and the mail session id.profile.jsonsupplies the display name. Folders without a usable profile are skipped. This does not hardcode agent ids. - load_
env_ file - Sets each
KEY=VALUEfrom the env file (ENV_FILE_ENVor the default web-client path) that the environment does not already have. Blank lines and#comments are skipped; surrounding quotes are dropped. Call at the start ofmain, before any thread starts. Returns the file read. - load_
env_ file_ named - Like
load_env_file, but the default under<home>/.config/mail4agent/isdefault_name(node usesnode-client.env). Home isHOMEwhen non-empty, otherwiseUSERPROFILE.ENV_FILE_ENVstill wins when set. - load_
session_ records - Reads
*.jsonsession records fromdir. A file that carries anything besidesbot_name,session_id, andagent_idis refused, so a webhook URL or a bearer cannot ride along in a world-readable record. - mxid_
localpart - Localpart of a Matrix user id (
@alice:server->alice). - nick_
from_ display_ name - Cyrillic (and a few adjacent letters) to Latin, then the host’s routine slug rule, so a bot’s nick, its wake routine name, and that routine’s folder id are one string.
- post_
decrypted - POSTs
wakeonce, as a JSON object, tourl. No mailbox path and noAuthorizationheader.urlis the bot’s already-configured routine.httpandhttpsare both followed; anything else is refused before a socket is opened. - post_
decrypted_ with_ bearer post_decryptedplus the routine key. WhenbearerisSomeand non-empty, that same value is sent asAuthorization: Bearerand asX-Automation-Key.Content-Typeisapplication/json. One JSON body. One attempt, 8 seconds. A 200 means the routine woke. Anything else is a failure and is not retried here. The key and the URL are not included in the error. The value must come from memory, not from source.- post_
routine_ json post_decrypted_with_bearerwith any JSON object as the body: the machine client’s own events (kind=peer_joined) go to a bot’s routine this way. Same headers, one attempt, 8 seconds, 200 or error. The key and the URL are not included in the error.- reply_
hint - The
replyhint for a wake fromfrom_nicktoto. - routine_
folder_ id - Folder id the Grok Bot routine store gives a routine named
name. - send_
cmd_ via_ socket - Sends one rich command over the client’s socket and waits for the answer.
- send_
sock_ path - Socket path:
SEND_SOCK_ENVwhen set, elseDEFAULT_SOCK_NAMEunderstore_root. - send_
sock_ path_ named - Like
send_sock_path, but usesdefault_nameunderstore_rootwhenSEND_SOCK_ENVis unset (node client usesnode-client.sock). - send_
via_ socket - Sends
requestover the client’s socket and waits up towaitfor the answer.Errmeans no client answered on that socket (missing, refused, or closed early), so the caller may fall back to opening the session itself. - session_
store_ dir - Directory for
session_idunderroot. - store_
root STORE_ROOT_ENVwhen set and non-empty. Tests with it unset get one temp directory for this process. Other builds returnShellError::StoreRootand do not invent a path two agents would share.- store_
seal_ key - SHA-256 of
session_id’s UTF-8 bytes. That digest is the check that this session may open the store. The bytes are not written to disk.