OneChat
#########
-OneChat is a Go program. It prints its name. It is in early development.
-Linux is supported.
+OneChat is one AI agent in a single chat that never ends. A daemon owns
+the agent and its files, and a terminal client and a Telegram bot talk
+to it. 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.
+
+OneChat creates the directory with mode 0700.
+
+***************
+ Configuration
+***************
+
+``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.
+
+.. code:: json
+
+ {
+ "endpoint": "chatgpt",
+ "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``.
+
+``compactor``
+ The model and the reasoning effort of the summaries. The defaults are
+ ``gpt-6.1-sol`` and ``high``.
+
+*******
+ Login
+*******
+
+The ``chatgpt`` endpoint needs a login with a ChatGPT account:
+
+.. code:: console
+
+ onechat login
+
+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.
+
+*******
+ Agent
+*******
+
+Start the daemon, and then the client in another terminal:
+
+.. code:: console
+
+ onechat daemon
+ onechat chat
+
+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, 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.
+
+When the user asks for it, the agent starts subagents for long tasks.
+Each subagent has a name, such as ``GreenOtter``, and runs in the
+background while the agent answers other messages. It gets the view of
+the agent and its task, and it runs its tools as the agent does, in
+``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]``. 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
+prompt cache key comes from it.
+
+.. code:: json
+
+ {
+ "name": "OneChat",
+ "id": "0123456789abcdef0123456789abcdef",
+ "model": "gpt-6.1-sol",
+ "effort": "medium",
+ "workdir": "/home/user"
+ }
+
+``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.
+
+**********
+ Telegram
+**********
+
+The daemon also connects the agent to a Telegram bot when
+``telegram.json`` exists in the data directory. It polls the Bot API for
+new messages, so it needs no open port.
+
+#. Make a bot with BotFather and keep its token.
+
+#. Find your numeric Telegram user ID.
+
+#. Write the token and the ID to ``telegram.json``, and give the file
+ mode 0600, because the token is a secret. The daemon refuses the file
+ with any other mode.
+
+#. Start the daemon, open a private chat with the bot, and write.
+
+.. code:: json
+
+ {"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, 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
+a restart of the daemon does not lose a message. A crash just after the
+write of a message can make the message repeat once. A network failure
+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. In the terminal client or in Telegram, the line ``/whatsapp
+status`` shows the link: not linked, linked and connected, or linked and
+disconnected, with the times of the connection. The answer goes only to
+the client that asked, and the line does not go to the agent.
+
+The session is ``whatsapp/session.db`` in the data directory. 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. When the session holds a linked device, the
+daemon connects at its start, and after a network failure it connects
+again by itself.
+
+*******
+ Probe
+*******
+
+``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.
+
+A test writes a generated log of readable made-up messages:
+
+.. code:: console
+
+ ONECHAT_PROBE_LOG=probe.jsonl go test -run TestProbeLogHasTheSyntheticMix
*************
Development
bin/format
bin/check
nix flake check
+
+``bin/check`` and ``nix build`` run a short replay of the synthetic log.
+``nix flake check`` also runs the full replay of 100,000 messages.