propose a change to the file, and it makes the change only after the
user agrees.
+A permanent child agent lives in ``agent/children/NAME/`` with the same
+files as the root. The root's ``agent_create(name, responsibility)``
+tool creates and starts it with the root's model, effort, and working
+directory, and a separate random ID. A name describes the area, such as
+``House`` or ``Calendar``. It is one word of 2 to 32 ASCII letters,
+starts with a capital letter, and cannot name the root, an existing
+child, or a subagent. The daemon opens all children at startup. A
+missing or invalid child config stops startup. Only one level of
+children is supported. Remove a child by hand while the daemon is
+stopped. The root's turn state lists each child and its responsibility.
+The root passes area messages from mixed chats with sender, date, and
+words, and asks the user before it sends a child's draft to WhatsApp.
+
+Each child has its own memory, model client, usage file, subagents,
+schedules, and turn loop. Its prompt limits work to its area, asks for
+only reports that need the parent or the user, and permits an empty
+reply. Children have no WhatsApp tools and draft replies instead of
+sending them. Clients address only the root. A child's saved schedules
+and subagents can resume at startup. The ready log line lists the child
+names.
+
+A message from a parent to its own child enters the child's log as
+``user`` and wakes it. A child's final reply reaches the parent as
+``work`` with the prefix ``[NAME] `` and wakes it. An empty reply sends
+nothing. A busy recipient takes the message between tool calls or in its
+next turn. ``agent/messages.json``, with mode 0600, keeps messages until
+their recipient finishes its turn. A daemon stop preserves queued
+messages and reports for the next start without a second log entry. An
+interrupted turn can repeat its model calls and tools after restart.
+Backups lock this queue with the agent files, including children made
+during the daemon run. The root uses ``tell`` for both children and
+subagents. ``zoom("Name")`` gives a child's view or a subagent's whole
+log. ``spawn`` starts short-lived subagents, not permanent children.
+
**************
User service
**************
systemctl --user stop onechat.service
systemctl --user disable onechat.service
+********************
+ Backup and restore
+********************
+
+Back up the running daemon to one mode 0600 ``.tar.gz`` archive. The
+command prints its path and size in bytes. With no daemon, it fails.
+
+.. code:: console
+
+ onechat backup BACKUP_FILE.tar.gz
+ ONECHAT_DIR=EMPTY_DATA_DIR onechat restore BACKUP_FILE.tar.gz
+
+The daemon flushes memory and holds its write locks during the agent
+copy. It also flushes and locks the child agents and all subagent logs.
+It copies the WhatsApp session with SQLite ``VACUUM INTO``, not as raw
+database files. It then compresses the copy and installs the archive
+through a temporary file and a rename. Use a backup path outside the
+data directory.
+
+The archive includes configuration, instructions, the log, tree,
+indexes, views, layout, schedules, subagents, children, Telegram offset,
+tokens, and WhatsApp state and session. It excludes sockets, lock files,
+``probe/``, and the ``agent-backup-*`` import copies. Links and special
+files fail, rather than enter the archive. **The archive holds
+secrets**, including login tokens, the Telegram bot token, and the
+WhatsApp link. Keep it private. There is no encryption or remote copy.
+
+Restore needs a stopped daemon and an empty destination. It checks the
+token lock and validates the archive before it installs files, with
+their modes. It rejects unsafe paths, links, and special files. A failed
+restore can leave ``auth.lock`` in the destination. Use another empty
+directory for a retry. Do not run the source and restored WhatsApp links
+at the same time. Check the restored log and view before you use it in
+place of the source.
+
+For a daily local archive with the last 14 kept, save this script as
+``BACKUP_SCRIPT``. Replace ``BACKUP_DIR`` with a private archive
+directory outside the data directory. Replace ``ONECHAT_BINARY`` with
+the absolute path through the stable ``result-service/bin/onechat``
+package link. Make the archive directory with mode 0700 before you
+enable the timer.
+
+.. code:: sh
+
+ #!/bin/sh
+ set -eu
+ umask 077
+ cd BACKUP_DIR
+ ONECHAT_BINARY backup "onechat-$(date +%F).tar.gz"
+ find . -maxdepth 1 -type f -name 'onechat-????-??-??.tar.gz' -printf '%f\n' |
+ sort -r | tail -n +15 |
+ while IFS= read -r file; do
+ rm -- "$file"
+ done
+
+Save these units as ``onechat-backup.service`` and
+``onechat-backup.timer`` in the systemd user unit directory. Replace
+``DATA_DIR`` and ``BACKUP_SCRIPT`` with absolute paths. The service uses
+the daemon unit from User service above.
+
+.. code:: ini
+
+ [Unit]
+ Description=Back up OneChat data
+ Requires=onechat.service
+ After=onechat.service
+
+ [Service]
+ Type=oneshot
+ Environment="ONECHAT_DIR=DATA_DIR"
+ UMask=0077
+ ExecStart=/bin/sh BACKUP_SCRIPT
+
+.. code:: ini
+
+ [Unit]
+ Description=Back up OneChat each day
+
+ [Timer]
+ OnCalendar=daily
+ Persistent=true
+
+ [Install]
+ WantedBy=timers.target
+
+Enable the timer with the user manager. A failed backup leaves the
+existing archives in place and skips removal of old archives.
+
+.. code:: console
+
+ systemctl --user daemon-reload
+ systemctl --user enable --now onechat-backup.timer
+ systemctl --user list-timers onechat-backup.timer
+
************
Daemon log
************
``/whatsapp status``
Shows the link: not linked, linked and connected, or linked and
- disconnected, with the times of the connection.
+ disconnected, with the times of the connection and the WhatsApp
+ routes.
``/whatsapp unlink``
Ends the link and deletes the session. When WhatsApp is disconnected,
They keep them only for a limited time, so a long stop can lose
messages. These messages form batches by their original times, as live
messages do. The daemon waits for the end of this catch-up, or one
-minute at the most, and then gives all its batches to the agent in one
-turn, not one turn for each chat. The batches of ``whatsapp/state.json``
-at the start wait for the first catch-up in the same way.
+minute at the most, and then gives all its unrouted batches to the root
+in one turn, not one turn for each chat. Routed batches wake their
+child. The batches of ``whatsapp/state.json`` at the start wait for the
+first catch-up in the same way.
The agent finds a chat with the tool ``whatsapp_find``: it gives the
contacts and the groups whose name holds a text, with no regard to case
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.
+The root manages source routes with ``route(source, agent)``,
+``unroute(source)``, and ``routes()``, only after the user agrees. A
+source is ``whatsapp:CHAT-ID``, with the ID from ``whatsapp_find`` or a
+``[WhatsApp]`` message, and the agent must be its child. The root keeps
+the map in ``agent/routes.json`` with mode 0600. Children and subagents
+have no route tools. There is no command to change routes. Each routed
+batch enters only its child's log and wakes it, including a group batch
+with no mention. The durable agent queue prevents a second log entry
+after a restart. An unrouted source or a missing child uses the root. A
+queued batch whose child is removed while stopped goes to the root.
+
When the session holds a linked device, the daemon connects at its
start. If the first connection fails, it tries again after 2 seconds,
then doubles the wait up to 5 minutes. The inbox and commands stay
my WhatsApp groups talked about". A schedule has a text, the task for
the agent, and either one time or a cron expression. The agent also
lists the schedules with their next times, and removes one when the user
-asks. Only the agent has the schedule tools, not its subagents.
+asks. The root and its children have the schedule tools, not their
+subagents. Each schedule records its owner and wakes only that agent.
A cron expression has five fields: minute, hour, day of month, month,
and day of week, where 0 and 7 are Sunday. A field is a comma list of