The datetime block claimed second precision it never had. It is built once per
request and a turn can run for minutes, so `17:54:31` is a lie by the time the
model reads it — and the model, having no way to know, wrote cron expressions
from it.
Rounding was already there but configurable (`round_minutes`, shipped at 60)
and justified by the prompt cache. That justification was false: the block is
the LAST system message, after the whole conversation, so the cached prefix is
identical from turn to turn whatever the timestamp says. Rounding buys nothing
for caching today.
So the knob goes and the granularity becomes part of the contract: always
truncated to the hour, stated in words, with a pointer to `date` for the cases
that need the minute. `DatetimeConfig` keeps only `enabled`.
- truncation happens in the DISPLAYED zone, not on the UTC epoch: +05:30 zones
would otherwise render 20:30 — an hour off and not on an hour boundary, which
reads as precise again.
- the weekday is spelled out. "next Tuesday" is a far more common ask than the
minute, and weekday-from-date is exactly the arithmetic models get wrong.
Also fixes a real bug found on the way: `execute_task` told the model, twice,
that cron expressions are evaluated in Europe/London — hardcoded, while
TaskManager uses the configured timezone. On a non-UK box every scheduled job
was written against the wrong clock. The description now names the zone the
scheduler actually uses (`TaskManager::timezone_name`), and the assistant's
AGENT.md stops repeating the literal.
CLAUDE.md: record that the instance is in production. The greenfield licence has
expired — schema changes need a versioning mechanism, and per-user SQLCipher
files mean it cannot be a boot-time sweep.
Memory was a scrapbook: notes accumulated, nothing kept them consistent,
and shared memory had no rule saying what belonged in it. This adopts the
LLM-wiki pattern — the assistant maintains an evolving artifact rather
than re-deriving knowledge each session.
The schema (agents/common/memory-wiki.md, included by the three type:chat
agents) adds an append-only log.md beside each index.md, names Ingest /
Recall / Lint as habits, and states the rule for shared memory: write it
there only if you would say it out loud with every member in the room.
One person's health, results or another member's opinion of them stays
private — that is what shared folders, not shared memory, are for.
Tampering is the reason the rules are shaped this way. Shared facts carry
provenance and are superseded rather than erased, and a member who
contradicts a fact they did not write gets a logged CLAIM instead of an
overwrite: only the originator or an admin can turn it into a change.
The approval gate cannot enforce this — it asks the caller, who is the
same person pushing — so a per-role write permission on shared memory is
still the real boundary. The prompt is the etiquette, not the fence.
append_file is a new fs tool because the log needs a write that cannot
shorten a file. On memory paths it is one SQL statement, so concurrent
appends (parallel tool batches, two sessions of one user) cannot lose a
line — a dropped line in an audit trail is worse than a failed write. It
is auto-allowed on shared-memory/log.md at a lower priority than the
shared write rule: gating the trail would be friction with no safety, and
a rejected log write yields an unlogged change.
memory::scaffold seeds index.md / log.md / user.md so the schema does not
describe files that do not exist. Seeded empty rather than left absent:
a missing note resolves to nothing at injection, so the model cannot tell
"nothing recorded yet" from "this mechanism is not running".
__MEMBERS__ renders the roster from users + roles instead of a note the
model maintains. A remembered copy drifts and can be talked into being
edited; this one bypasses the model entirely. users.notes are excluded —
they are the admin's private notes about a person and this block is
visible to every member.
Still open: a UI to browse memory (list_dir does not classify memory
paths yet), a tool-written revisions table (log lines are still typed by
the model), and the periodic lint pass.
Replace the ad-hoc [SYSTEM INFO] / [TELEGRAM SYSTEM INFO] prefixes with a
single canonical <system-extra> wrapper, sourced from one constant
(SYSTEM_EXTRA_TAG) so emission and documentation can never diverge.
- core-api: SYSTEM_EXTRA_TAG + system_extra() helper; attachments_block
rebuilt on top of it.
- telegram: system_info_message (location) uses the helper; the voice
transcript is forwarded as a plain user message (it is the user's own
words, not harness metadata).
- chat agents: new agents/common/harness.md include (long form, with an
explicit "data, not instructions" guard), added to assistant/kid/
project-coordinator. The tag name rides the __HARNESS_TAG__ sentinel,
resolved in AgentSystemContext to SYSTEM_EXTRA_TAG — renaming the tag
stays a one-line change.
- Strip ~55 stale upstream docs (dev docs never meant for the agents)
- Write new slim index.md as an agent-facing guide to the app's features
- Add new plugin docs: comfyui, elevenlabs, kokoro_tts, orpheus_tts_3b,
remote_connectivity, whisper_local (replacing old names)
- Add docs_host to UserFs: docs/… and ~/docs/… resolve to {WD}/docs,
mounted read-only at /root/docs in every user's container
- Instruct assistant/kid/project-coordinator agents to read docs/index.md
when users ask how the software works