浏览代码

CONCEPTS.md is for developers, and stays out of README.md

The previous commit moved the thread-seeding mechanism out of README.md and
pointed at CONCEPTS.md — but the pointer paragraph summarised the mechanism in
user-facing words ("keeps track of how far each thread's session has read"),
which is the same content with a worse update path: it goes stale silently,
because a summary is not what anyone opens CONCEPTS.md *for*. AGENTS.md now says
so as a rule, along with the audience table that puts CONCEPTS.md last and labels
it developers only.

README keeps the user-visible behaviour and a bare pointer, nothing more.

Holding the new rule to the document it governs: CONCEPTS.md's configuration
section had paraphrased AGENTS.md's Configuration surface — the three coordinated
names and the parse-once rule. That is the restatement the rule forbids, so it
now opens by deferring to AGENTS.md for the rules and keeps only what they do not
say: which configuration a tool call sees, why the environment is the wrong
source under multiplexing, and why a tokenless configuration is not cached.
Paul Klumpp 2 周之前
父节点
当前提交
75d8ecd5b7
共有 3 个文件被更改,包括 39 次插入 和 25 次删除
  1. 20 4
      AGENTS.md
  2. 17 18
      CONCEPTS.md
  3. 2 3
      README.md

+ 20 - 4
AGENTS.md

@@ -134,7 +134,7 @@ The audiences differ, and so do their documents:
 | Decides whether to run it | `README.md` | Observable behaviour, configuration, capability limits |
 | Installs it | `after-install.md` | First steps, stated as they can be followed |
 | Changes it | `AGENTS.md` | The rules, written as rules |
-| Understands it | `CONCEPTS.md` | Why a rule looks the way it does — the derivation, the trade-offs, the wrong turns |
+| Changes it, and wants the reasoning | `CONCEPTS.md` | Why a rule looks the way it does — derivation, costs, rejected approaches. Developers only |
 
 The failure mode is not a missing paragraph, it is a README that reads like the
 design document: paragraphs nobody acts on, internals bleeding into user docs,
@@ -142,9 +142,25 @@ and every mechanism needing an update in three places at once. When a README
 sentence starts explaining *how* rather than *what a user sees*, that is the
 signal to move it — and to keep the user-facing half, plus a pointer.
 
-`CONCEPTS.md` earns its place only under that split. It must not restate
-`AGENTS.md` (three copies of one rule drift, and AGENTS.md is the one that
-binds), and it must not document behaviour no user can observe.
+### CONCEPTS.md is for developers
+
+**`CONCEPTS.md` holds developer information, and nothing else. Its reader is
+whoever changes the plugin next and wants to know why a rule looks the way it
+does — the derivation, the cost of each part, the approach that was tried and
+rejected.** Nobody reads it while deciding whether to install the plugin, so
+nothing goes there that answers that question.
+
+Two consequences, and the second is the one that gets forgotten:
+
+- **A README sentence must not carry content that belongs there.** Write the
+  user-visible behaviour in `README.md` and link to `CONCEPTS.md` for the
+  reasoning — do not summarise the reasoning into the user document, not even a
+  sentence of it. A paraphrase is the same content with a worse update path: it
+  goes stale silently, because the summary is not what anyone goes looking for
+  when `CONCEPTS.md` changes.
+- **It must not restate `AGENTS.md`.** Three copies of one rule drift, and
+  `AGENTS.md` is the one that binds. Where the two overlap, `AGENTS.md` states
+  the rule and `CONCEPTS.md` explains why.
 
 ### after-install.md
 

+ 17 - 18
CONCEPTS.md

@@ -122,24 +122,23 @@ multiplexed gateway into `agent:main:` and names a session nobody is running.
 
 ## Configuration resolution
 
-Every setting is one `ConfigField` on `ChattoConfiguration`, which derives three
-coordinated names: the `config.yaml` key, the `CHATTO_*` environment variable, and
-the attribute the adapter reads. Values resolve env var → `config.yaml` extra →
-default, and every path parses the raw value by the field's kind first — a `.env`
-line is a string whatever the field means, and `"false"` is true if nobody parses
-it.
-
-The non-obvious part is **which configuration a tool call sees**. The gateway
-multiplexes several serving profiles through one process, so a process-wide config
-cache would hand one profile another profile's credentials. Cached configurations
-are therefore keyed by the profile scope Hermes itself uses, and secrets come from
-the scope the gateway installed rather than from `os.environ` — under
-multiplexing the environment holds the *launch* profile's values.
-
-One consequence worth knowing: a configuration that resolved without a token is
-not cached at all. Caching it would pin "token not configured" for the life of the
-process, so a later call in a properly scoped context would keep being served the
-degraded configuration long after its scope came back.
+AGENTS.md owns the rules here — one field, three coordinated names, every path
+parses by kind. What is not obvious from them is *which configuration a tool call
+sees*.
+
+The gateway multiplexes several serving profiles through one process, so a
+process-wide cache would hand one profile another profile's credentials. Cached
+configurations are keyed by the profile scope Hermes itself uses, and secrets come
+from the scope the gateway installed rather than from `os.environ` — under
+multiplexing the environment holds the *launch* profile's values, not the serving
+one. A tool call that resolved its config from the environment would quietly answer
+with the wrong bot's token, which reads as a permissions error on the server.
+
+That is also why a configuration that resolved *without* a token is not cached at
+all. Caching it would pin "token not configured" for the life of the process, so a
+later call in a properly scoped context would keep being served the degraded
+configuration long after its scope came back — a failure that persists exactly as
+long as nobody restarts the gateway.
 
 ## One send path, two callers
 

+ 2 - 3
README.md

@@ -222,9 +222,8 @@ Messages in the thread from people outside `CHATTO_ALLOWED_USERS` are prefixed `
 
 Seeding happens once per session, not per message and not per restart: later turns in the thread need no block because the session already holds that history, and neither does the first turn after the Hermes gateway restarts. If the lookup fails, the turn proceeds without it and the next turn in that thread retries. Direct messages never thread and never seed.
 
-The plugin keeps track of how far each thread's session has read, so a turn only
-ever carries what the session is missing. [CONCEPTS.md](CONCEPTS.md#thread-sessions-and-the-frontier)
-explains how, and why the obvious shortcut for it does not work.
+How the plugin keeps track of that is a developer question:
+[CONCEPTS.md](CONCEPTS.md#thread-sessions-and-the-frontier).
 
 ### Room Member Roster