10 Commits
Author SHA1 Message Date
Daniele 8361a238c7 fix(view-context): tell the chat agents when not to use it
Nightly Build / build (push) Successful in 40s
With the eye on, the snapshot of what the user is looking at was read as
part of the question: asked something unrelated to the open page, the
assistant went investigating the folder with a run of tool calls. The
transport was right; the prompt never said the block can be irrelevant.

New fragment agents/common/view-context.md, included only by the three
type: chat agents. It could have gone in harness.md — which all three
already include — but harness.md is also included by the two memory-lint
agents, which never receive a view. Hence the split, and hence moving the
snapshot bullet there too: both halves of the rule now live in one file.

The rule names the observed behaviour ("never open, list, search or
otherwise investigate ... just because it is there") and keeps the deictic
examples, so curing the over-use does not create the under-use.

docs/ gains the same rule where the in-app assistant reads it; the
CHANGELOG entry for the feature is extended, not duplicated.
2026-08-23 22:04:57 +01:00
Daniele 1b709a880f feat(agents): learn and reuse the user's writing style
Nightly Build / build (push) Successful in 9s
The chat agents now treat a stated preference about how something should be
written — or a correction to a draft they produced — as durable, and record it
in a `## Writing style` section of `user-memory/user.md`: preferred wording,
how emails open and close, what changes between the formal and the informal
register, and contacts written to differently from everyone else.

The section is capped at 10 lines, because it shares `user.md`'s own 40-line
budget and per-recipient detail is the half that grows without bound; past
that, the whole section moves into its own note and leaves a pointer behind.
A rule that turns out to be wrong is corrected in place rather than joined by
a second bullet contradicting it.

Shipped as the shared fragment `common/writing-style.md`, included by the
three `type: chat` agents. Only `assistant` injects `user.md` automatically,
so the fragment closes by telling the other two to read it before drafting.
2026-08-19 11:03:42 +01:00
Daniele 66d83358d9 feat(event-triage): user notification preferences via user-memory/notifications.md
Nightly Build / build (push) Successful in 9s
Release / verify-version (pull_request) Successful in 2s
Release / release (pull_request) Skipped
The single-user notifications.md mechanism (a file in data/) died in the
multi-user move: assistant's prompt still pointed at it, but nothing read
it. Replace it with a memory note:

- event-triage injects user-memory/notifications.md verbatim on every
  pass and treats it as authoritative over its default heuristics
- a shared common/notifications.md fragment, included by assistant, kid
  and project-coordinator, tells the chat agents to record preference
  requests there: one dated rule per bullet under a source heading or
  General, asking for the source when ambiguous
- docs/system-agents.md explains the steering to users
2026-08-14 13:21:06 +01:00
Daniele 5765941758 feat(prompt): tell the agent what its sandbox can run
Nightly Build / build (push) Successful in 8m6s
The agent had no way to know its container ships ffmpeg, ripgrep or
tesseract, so it either declined work it could do or spent a round finding
out. This adds a command list to the system prompt as a **discovery hint** —
explicitly not an inventory.

Every decision follows from it being a hint:

- The allowlist (~35 entries, `container/commands.rs`) is the curation; a
  full PATH dump is 800 entries of coreutils noise. The probe exists so the
  list cannot *lie*, not so it can discover: `command -v` at login means we
  never announce something a container recreate threw away.
- The rendered prose says the list is partial and names `command -v`, so a
  tool outside the allowlist costs one check rather than a wrong conclusion.
  An empty probe renders as an explicit "could not be read", never as
  silence under a heading promising a list.
- Order is the allowlist's own, grouped by kind of work — the grouping is
  the curation, and the reader is a model, not a grep.
- Staleness is cheap both ways, so there is no invalidation machinery: a
  login-time snapshot on `UserContext`, non-fatal, refreshed at next login.

The gate is the tool, not the sentinel. Every AGENT.md carries
`common/sandbox.md` — the four system agents included — and the section is
emitted iff the turn's model is shown `execute_cmd`, derived from
`allow_tools` plus the security group's visibility filter for a root turn
and from `child_defs` for a sub-agent: always the same definitions the model
will see. `has_execute_cmd` therefore joins the PrefixCache key, since the
group is switchable mid-conversation and that switch already rewrites the
tool payload in the same provider cache.

The fragment holds only the heading and one stable sentence; every
conditional claim lives in the renderer, because prose promising
`sudo apt-get install` is not the renderer's to retract when the tool is
absent. `execute_cmd`'s own description loses `(python + node available)`:
its job is steering away from the shell, and a capability advertisement
diluted it.
2026-08-09 09:49:50 +01:00
Daniele c27da4e6ab feat(skills): rebuild the skill system for the multi-user model
Nightly Build / build (push) Successful in 8m6s
Per blueprint/skill-project.md: the old single-namespace, hand-maintained
index is gone, replaced by a read-only, two-scope tree whose index is a
runtime function of its content.

- skills/ index generated at runtime (crates/skald-core/src/skills/:
  inventory, install, validate, watch), injected through the new
  <!-- SKILLS_LIST --> placeholder in AGENT.md (agents/common/skills.md);
  meta.json inject_skills flag removed. 11 chat/task agents carry the
  include, the 4 system agents do not.
- Two trees, both read-only in both directions: skills/shared/{id} (the
  group's) and skills/{username}/{id} (one member's own, on the stable
  userid). The root is closed too: UserFs::SkillMounts + RouteError (alias
  probe, plain-denied paths, no home fallback) and a per-user
  .skills-root/{userid} container mount with the two scope mounts nested
  inside, plus the fifth self-heal axis (skills_mounted).
- Agent verbs: skill_register/skill_delete (Config group, global scope
  behind the new skill.manage capability), fetch_repo for public repos,
  list_items(type="skills"); reads are plain read_file on the printed
  path. Seeded @fs_read skills/* allow.
- Freshness: a digest-gated watcher on the two trees emits
  SystemEvent::SkillsChanged, whose subscriber rebuilds the frozen prompt
  prefix via Skald::invalidate_prompt_prefix; in-process writers invalidate
  directly.
- The build ships no skills: the three bundled skills (ics2json,
  mcp-builder, skill-creator) and skills/index.md are removed, skills/ is
  instance data (gitignored, not packaged, no longer pruned by update.sh).
- Docs: skills.md, agents.md, shared-folders.md added; docs/index.md and
  agents/README.md updated.
2026-08-08 23:05:35 +01:00
dguiducci da0830aefa feat: an hour-precision clock that says so, and a cron tool that names the real timezone
Nightly Build / build (push) Successful in 7m30s
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.
2026-08-02 22:20:52 +01:00
dguiducci a3e1b0add0 memory: reshape the two stores into a maintained wiki
Nightly Build / build (push) Successful in 6m50s
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.
2026-07-26 19:03:49 +01:00
dguiducci 4d81295a3d messages: unify harness-injected data under <system-extra> tag
Nightly Build / build (push) Successful in 6m49s
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.
2026-07-26 17:54:14 +01:00
dguiducci 9224245f6f docs overhaul: agent-facing doc bundle, read-only docs mount in containers
Nightly Build / build (push) Successful in 6m38s
- 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
2026-07-22 10:20:21 +01:00
dguiducci 8ff64cbddc rename agents/main→assistant, role-based default entry agent
Nightly Build / build (push) Successful in 6m31s
2026-07-21 21:40:06 +01:00