The daemon owns the root agent in ``agent/`` in the data directory and
listens on the Unix socket ``onechat.sock`` there. In the client, each
line is a message to the agent, the line ``/stop`` interrupts the
-running turn, and Ctrl-D ends the client. The client shows the replies,
-the tool calls, and the first lines of each tool result as they happen.
-It also shows each message that the user sends from another client, with
-the client as a mark, such as ``[telegram] text``. The daemon is the
-only process that uses the token file while it runs, so ``onechat
-login`` and ``onechat probe`` refuse to start until it stops.
+running turn, the line ``/stop NAME`` stops the subagent NAME, and
+Ctrl-D ends the client. The client shows the replies, the tool calls,
+and the first lines of each tool result as they happen. It also shows
+each message that the user sends from another client, with the client as
+a mark, such as ``[telegram] text``. When a subagent starts, the client
+shows ``[NAME] started``, and it shows the whole report of the subagent,
+which starts with ``[NAME]``, but not the tool calls of the subagent.
+The daemon is the only process that uses the token file while it runs,
+so ``onechat login`` and ``onechat probe`` refuse to start until it
+stops.
The agent runs commands with bash as the user in its working directory,
with no sandbox. It has the rights of the user, also on the token file.
``agent/subagents/NAME/work``. A task in a repository gives the path of
the repository. The steps of a subagent go to its own log in
``agent/subagents/NAME``, not to the chat, and its final reply comes
-back to the agent as one message that starts with ``[NAME]``. Each model
-call of a subagent has the name in the ``agent`` field of
-``agent/usage.jsonl``.
+back to the agent as one message that starts with ``[NAME]``. The agent
+can send a subagent more messages: a running subagent reads them between
+its tool calls, and a finished one goes on from its log, and its reply
+comes back in the same way. When the user stops a subagent, the chat
+gets a note, and the report of the subagent is logged, but it starts no
+turn. Each model call of a subagent has the name in the ``agent`` field
+of ``agent/usage.jsonl``. ``agent/work.json`` holds the names of the
+running subagents. A subagent that runs when the daemon stops goes on at
+the next start: the chat gets the note that the daemon restarted, and
+the subagent gets the same note and goes on from its log.
``agent/config.json`` holds the settings of the agent. The first start
writes it with the defaults and a random ID. Do not change the ID: the
{"token": "123456:ABC-token-from-BotFather", "owner": 123456789}
The bot reads only the private chat of the owner and ignores all other
-updates. Each text message goes to the agent as a user message, and the
-line ``/stop`` interrupts the running turn. The bot sends the replies in
-parts of at most 4096 characters, with their Markdown as Telegram
-formats: bold, italic, strikethrough, code, code blocks, links, and
-headings as bold text. When Telegram refuses the formats of a part, the
-bot sends the part again as plain text. Each error goes as a short
-message in plain text. It shows the typing action while a turn runs. It
-does not send the tool calls. It sends each message that the user types
-in the terminal client, with the mark ``[terminal]``.
+updates. Each text message goes to the agent as a user message, the line
+``/stop`` interrupts the running turn, and the line ``/stop NAME`` stops
+the subagent NAME. The bot sends the replies in parts of at most 4096
+characters, with their Markdown as Telegram formats: bold, italic,
+strikethrough, code, code blocks, links, and headings as bold text. When
+Telegram refuses the formats of a part, the bot sends the part again as
+plain text. Each error goes as a short message in plain text. It shows
+the typing action while a turn runs. It does not send the tool calls.
+For a subagent it sends only ``[NAME] started`` and ``[NAME] finished``,
+``[NAME] stopped``, or ``[NAME] failed``, because the reply of the agent
+carries the content of the report. It sends each message that the user
+types in the terminal client, with the mark ``[terminal]``.
``agent/telegram-offset.json`` holds the ID of the next update. The bot
moves it past an update only after the message is durable in the log, so
or a rate limit makes the bot wait and try again, and does not stop the
daemon.
+**********
+ WhatsApp
+**********
+
+The daemon connects to the WhatsApp account of the user as a linked
+device. The terminal client and Telegram take three commands:
+
+``/whatsapp pair PHONE``
+ Links the account of PHONE, the number with its country code, such as
+ ``+15550001111``. The daemon answers with the steps and then with a
+ pairing code in its own message: in WhatsApp on the phone, open
+ Linked devices, tap Link a device, then tap Link with phone number
+ instead, and enter the code. The code expires in about 2 minutes, and
+ the same command gives a new code. When the link succeeds, the daemon
+ answers with the linked number.
+
+``/whatsapp status``
+ Shows the link: not linked, linked and connected, or linked and
+ disconnected, with the times of the connection.
+
+``/whatsapp unlink``
+ Ends the link and deletes the session. When WhatsApp is disconnected,
+ the phone does not get the logout, so remove the device in Linked
+ devices on the phone.
+
+The answers go only to the client that asked, and the commands do not go
+to the agent, so the agent never gets the phone number or the code. The
+chat gets only the notes that WhatsApp is linked or unlinked.
+
+The session is ``whatsapp/session.db`` in the data directory, with mode
+0600 in a directory with mode 0700. It gives full access to the WhatsApp
+account, so the daemon refuses the file when its mode is not 0600. The
+shell of the agent runs as the user, so it can read the session. If the
+session leaks, remove the linked device in WhatsApp on the phone, and
+the session stops working. Without the file, WhatsApp is not linked.
+
+The daemon reads the text messages of the private chats and the groups,
+from other people and from the user. It does not read media, reactions,
+edits, deletions, view-once and disappearing messages, or the history
+from before the link. The messages of one chat form one batch until the
+chat is quiet for 20 seconds, for 2 minutes or 50 messages at the most.
+Each batch goes to the chat of the agent as one work message that starts
+with ``[WhatsApp]``, with the name and the ID of the chat, and the time,
+the sender, and the text of each message. The messages of the user show
+as ``You``. ``whatsapp/state.json``, with mode 0600, keeps the batches
+that are not in the chat yet, so they survive a restart. A crash just
+after a batch goes to the chat can make the batch repeat once.
+
+A batch of a private chat with a message of another person starts a
+turn, and so does a batch of a group with a message that mentions the
+user or replies to a message of the user. The other batches go to the
+chat with no turn, and the agent sees them at its next turn. A batch
+that starts a turn ends with a line that tells the agent to end its turn
+with an empty reply when nothing in the batch needs the user. No client
+shows an empty reply, so such a batch gives no message in Telegram. The
+terminal client shows each batch, and Telegram shows none.
+
+The phone number of the user never goes to the chat: a mention of the
+user shows as ``@you``, the number shows as ``OWN``, and the chat of the
+user with itself is not read. But the IDs of the other chats go to the
+chat and to the model, because the agent needs them to send. The ID of a
+private chat holds the phone number of the contact, such as
+``15550002222@s.whatsapp.net``, and the ID of a group can hold the
+number of the person who made it.
+
+Each batch of a group goes to the chat, also with no turn, and the
+memory tree summarizes it with model calls, which use the quota.
+
+When the session holds a linked device, the daemon connects at its
+start, and after a network failure it connects again by itself. When a
+disconnection lasts 10 minutes, all clients get one notice, and one more
+when the connection comes back. When WhatsApp ends the connection for
+good, for example because another program uses the same session, all
+clients get a notice at once. When the user removes the device in the
+phone, the daemon deletes the session and tells all clients one time.
+
*******
Probe
*******