#########
OneChat is one AI agent in a single chat that never ends. A daemon owns
-the agent and its files, and a terminal client talks to it. It is in
-early development. Linux is supported.
+the agent and its memory, and you talk to it from a terminal client,
+Telegram, and through your WhatsApp chats. It is in early development.
+Linux is supported.
-****************
- Data directory
-****************
-
-OneChat keeps its data and its token file in one directory. It uses the
-first of these:
-
-#. ``ONECHAT_DIR``, when it is set.
-#. ``$XDG_DATA_HOME/onechat``, when ``XDG_DATA_HOME`` is an absolute
- path. A relative value is ignored.
-#. ``~/.local/share/onechat``. With no home directory, OneChat exits
- with an error.
+*******
+ Setup
+*******
-OneChat creates the directory with mode 0700.
+Data directory
+==============
-***************
- Configuration
-***************
+OneChat keeps all its data in one directory, with mode 0700. It uses
+``ONECHAT_DIR`` when it is set, else ``$XDG_DATA_HOME/onechat``, else
+``~/.local/share/onechat``. The development shell sets ``ONECHAT_DIR``
+to ``.devenv/onechat`` in the checkout.
-``config.json`` in the data directory holds the settings. OneChat does
-not create it. A missing file or field takes the default, and an unknown
-field is an error.
+Optional settings go in ``config.json`` in the data directory:
.. code:: json
"compactor": {"model": "gpt-6.1-sol", "effort": "high"}
}
-``endpoint``
- ``chatgpt`` (the default) sends model calls to the ChatGPT backend
- with the subscription login. ``api`` sends them to the OpenAI API
- with the key in ``OPENAI_API_KEY``, which must then be set. With
- ``chatgpt``, OneChat ignores ``OPENAI_API_KEY``.
+``endpoint`` is ``chatgpt`` (the ChatGPT subscription, the default) or
+``api`` (the OpenAI API, with ``OPENAI_API_KEY`` set). ``compactor`` is
+the model and effort of the memory summaries.
-``compactor``
- The model and the reasoning effort of the summaries. The defaults are
- ``gpt-6.1-sol`` and ``high``.
+Login
+=====
-*******
- Login
-*******
+.. code:: console
+
+ onechat login
+
+Open the link on any device and enter the code. Device code login must
+be allowed in the ChatGPT security settings. Do not share the login with
+the Codex CLI: each program logs the other out.
+
+Telegram
+========
+
+#. Make a bot with BotFather and keep its token.
+#. Find your numeric Telegram user ID.
+#. Write both to ``telegram.json`` in the data directory, with mode
+ 0600:
+
+.. code:: json
+
+ {"token": "123456:ABC-token-from-BotFather", "owner": 123456789}
-The ``chatgpt`` endpoint needs a login with a ChatGPT account:
+Start the daemon and write to the bot in a private chat. It answers only
+you.
+
+WhatsApp
+========
+
+Link your WhatsApp account as a linked device from the terminal client
+or Telegram:
+
+``/whatsapp pair PHONE``
+ Gives a pairing code. On the phone, open Linked devices, Link a
+ device, Link with phone number instead, and enter the code.
+
+``/whatsapp status``
+ Shows the link and the routes of chats to child agents.
+
+``/whatsapp unlink``
+ Ends the link.
+
+The session file gives full access to the account. If it leaks, remove
+the device in Linked devices on the phone.
+
+User service
+============
+
+Run the daemon as a systemd user service. Keep the ``result-service``
+link, so that Nix keeps the package:
.. code:: console
- onechat login
+ nix build --out-link result-service
+ systemctl --user link --force "$PWD/result-service/share/systemd/user/onechat.service"
+ systemctl --user edit onechat.service
-The command shows a link and a code. Open the link on any device, enter
-the code, and approve the login. Device code login must be allowed in
-the ChatGPT security settings. OneChat writes the tokens to
-``auth.json`` in the data directory, with mode 0600, and refreshes them
-before they expire. The file belongs to OneChat only. Do not share it
-with the Codex CLI, because each program rotates the refresh token and
-logs the other out.
+In the editor, set the data directory, and the tools that the agent may
+use in its shell:
-*******
- Agent
-*******
+.. code:: ini
-Start the daemon, and then the client in another terminal:
+ [Service]
+ Environment="ONECHAT_DIR=DATA_DIR"
+ Environment="PATH=TOOLS_PATH"
+
+Then start it, and let it run while you are logged out:
.. code:: console
- onechat daemon
- onechat chat
+ systemctl --user daemon-reload
+ systemctl --user enable --now onechat.service
+ loginctl enable-linger "$USER"
-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.
-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.
+After a ``git pull``, ``bin/service-update`` builds and restarts it.
+Never run two daemons on the same data.
-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.
+Backups
+=======
-``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
-prompt cache key comes from it.
+.. code:: console
-.. code:: json
+ onechat backup BACKUP_DIR/onechat.tar.gz
+ ONECHAT_DIR=EMPTY_DIR onechat restore BACKUP_DIR/onechat.tar.gz
- {
- "name": "OneChat",
- "id": "0123456789abcdef0123456789abcdef",
- "model": "gpt-6.1-sol",
- "effort": "medium",
- "workdir": "/home/user"
- }
+A backup runs while the daemon runs. Restore needs a stopped daemon and
+an empty directory. **An archive holds your logins, the bot token, and
+the WhatsApp link**, so keep it private.
-``workdir`` is an absolute path, and its default is the home directory.
-``agent/instructions.md`` holds the user's instructions: who they are,
-how their files are organized, and how they want work done. The prompt
-ends with them, and a change applies at the next call.
+For a daily backup with the last 14 kept, save this script, with
+``BACKUP_DIR`` replaced by a private directory outside the data
+directory:
-*******
- Probe
-*******
+.. code:: sh
-``onechat probe LOG COUNT [BUILDS]`` measures the compactor on a log. It
-appends the first COUNT messages of LOG, one JSON object ``{"kind",
-"text"}`` on each line, to a new chat in ``probe/`` in the data
-directory, and builds its tree with real model calls. The calls use the
-quota of the account. The run stops at the first failed call, after 660
-calls, or when the used percent of a quota window grows by more than 15
-points. At the end it reports the calls per message, the cached share of
-the input tokens, the retries, the line sizes, and the quota at the
-first and the last call. BUILDS, from 1 to 8, limits the model calls at
-once in the run.
+ #!/bin/sh
+ set -eu
+ umask 077
+ cd BACKUP_DIR
+ onechat backup "onechat-$(date +%F).tar.gz"
+ ls -1 onechat-????-??-??.tar.gz | sort -r | tail -n +15 | xargs -r rm --
-A test writes a generated log of readable made-up messages:
+and run it from a ``Type=oneshot`` user service with the same
+``ONECHAT_DIR`` and an ``OnCalendar=daily`` timer.
+
+*********
+ Talking
+*********
.. code:: console
- ONECHAT_PROBE_LOG=probe.jsonl go test -run TestProbeLogHasTheSyntheticMix
+ onechat
-*************
- Development
-*************
+``onechat`` connects to the running daemon. Each line is a message, and
+replies show with Markdown styles and tables. These lines are commands:
-Nix supplies the Go toolchain and all tools. Enter the development shell
-with direnv:
+``/stop``
+ Interrupts the running turn.
-.. code:: console
+``/stop NAME``
+ Stops the subagent NAME.
+
+``/image PATH [caption]``
+ Sends a JPEG, PNG, or WebP image. Quote a path with spaces.
+
+``/whatsapp ...``
+ The WhatsApp commands above.
+
+In Telegram, write as in the client, also with photos and image files.
+``onechat help`` lists all commands.
+
+*********************
+ What the agent does
+*********************
- direnv allow
+Memory
+ The agent remembers the whole chat as summaries that it can open
+ again, so it never starts over. ``agent/instructions.md`` holds your
+ lasting preferences. The agent can propose a change to it and makes
+ the change only after you agree.
-Or enter it directly:
+Tools
+ The agent runs shell commands as you, in its working directory, with
+ no sandbox.
+
+Subagents
+ For long tasks it starts subagents in the background, such as
+ ``GreenOtter``, and gets their reports as messages.
+
+Child agents
+ A child is a permanent agent for one area of your life, such as
+ ``House``, with its own memory. The root creates it, talks to it, and
+ hears from it only what needs you. Children never send WhatsApp
+ messages; they draft replies for you to approve.
+
+WhatsApp
+ The agent reads your chats in batches and wakes for a private message
+ or a mention of you. It finds chats and sends messages only when you
+ ask. It proposes to route a chat that is only about one area to the
+ child of that area, and sets the route after you agree.
+
+Images
+ Send a photo in Telegram or with ``/image``, and the agent sees it.
+ Its memory keeps what the image shows, and it can look at an older
+ image again.
+
+Schedules
+ Ask for reminders and timed tasks, such as "every weekday at 8, tell
+ me what my groups talked about". Each run uses the quota, so runs are
+ at least 5 minutes apart.
+
+Import
+ Old WhatsApp chats can join the memory in date order. Stop the
+ daemon, export the chat as a ZIP, and run the plan before the import.
+ The import rebuilds the target's memory with model calls:
.. code:: console
- nix develop
+ onechat import -n [--agent NAME] CHAT.zip
+ onechat import [--agent NAME] CHAT.zip
-The shell sets ``ONECHAT_DIR`` to ``.devenv/onechat`` in the checkout,
-unless it is already set. Development data goes there, and Git ignores
-``.devenv``.
+*****
+ Log
+*****
-Build and run:
+The daemon writes short events with ``key=value`` attributes to the user
+journal, at three levels: info, warning for a failure that is retried or
+loses nothing, and error for a failure that loses data or stops work.
.. code:: console
- nix build
- ./result/bin/onechat
+ journalctl --user -u onechat -f
+ journalctl --user -u onechat -p warning
-Format and check all files:
+*************
+ Development
+*************
+
+Nix supplies the Go toolchain and all tools. Enter the development shell
+with ``direnv allow`` or ``nix develop``. Then:
.. code:: console
+ nix build
+ ./result/bin/onechat version
bin/format
bin/check
nix flake check
+
+``onechat version`` prints ``yyyy-mm-dd-gitshort`` for a Nix build and
+``dev`` for a direct Go build. ``bin/check`` runs the linters and a
+short replay of a synthetic log, and ``nix flake check`` also runs the
+full replay of 100,000 messages. ``onechat probe LOG COUNT`` measures
+the memory summaries on a log with real model calls.