Files
Skald-Circle/default.config.yaml
dguiducci da0830aefa
Nightly Build / build (push) Successful in 7m30s
feat: an hour-precision clock that says so, and a cron tool that names the real timezone
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

172 lines
11 KiB
YAML

server:
host: 127.0.0.1
port: 9000
# ── Global timezone ─────────────────────────────────────────────────────────────
# IANA timezone name applied globally to:
# - cron expression evaluation (next_run_at computation)
# - the date/time string injected into the LLM context each turn
# When omitted, the server's local system timezone is used.
# Examples: Europe/Rome, Europe/London, America/New_York, Asia/Tokyo
#
# timezone: Europe/Rome
# ────────────────────────────────────────────────────────────────────────────────
web:
static_dir: ./web
# ── Connector marketplace ──────────────────────────────────────────────────────
# The feed of vetted connectors the admin browses under Connectors → Marketplace.
# It is *consultative*: the feed proposes, the admin installs into the local
# catalog, and only then can a connector be enabled globally or activated by a
# user. The trust anchor stays on this box.
#
# Point it at a self-hosted mirror or a local copy to run fully offline — the feed
# is plain static files (`connectors.json` + `<folder>/connector.json`).
marketplace:
url: https://connectors.skaldagent.net
# The database lives at ./database/system.db — fixed, not configurable.
# ── LLM clients ────────────────────────────────────────────────────────────────
# LLM clients (providers, models, API keys, strength) are configured
# via the web app and stored in the database — not in this file.
# ───────────────────────────────────────────────────────────────────────────────
llm:
# ── History window (DISABLED by default) ────────────────────────────────────
# Hard cap on the number of history messages sent to the LLM, applied as a
# sliding tail window: past the cap, the oldest messages are dropped.
#
# Off by default, for two reasons:
# 1. Cache. Once history exceeds the cap, every turn shifts the window's
# start, so the prompt prefix changes on every single request and the
# provider's prompt cache (Anthropic breakpoints, OpenAI automatic prefix
# caching) misses every time. Append-only history keeps the prefix stable
# for the whole conversation.
# 2. Memory. The window drops messages with no summary standing in for them,
# so the assistant silently forgets. `/compact` replaces them with a
# summary instead.
#
# With this off and automatic compaction off (the shipped default), the context
# grows until the model's own limit — use `/compact` to summarise it.
# Ignored when automatic compaction is enabled (see `compaction` below).
#
# max_history_messages: 30
# ───────────────────────────────────────────────────────────────────────────
max_tool_rounds: 100
# Max synchronous sub-agents dispatched concurrently when the LLM emits a
# homogeneous batch (≥2) of sub-agent calls (execute_task mode=sync /
# execute_subtask) in a single response. Bounds fan-out to avoid provider
# rate-limit storms. Omit for the default (4); set to 1 to force sequential.
max_parallel_subagents: 4
# Injects "Current date and time: Sunday 2026-08-02 17:00 +02:00 (Europe/Rome)"
# as the last system message of each request. The time is always truncated to
# the hour and the block tells the model so, pointing it at `date` when it
# needs the exact minute — there is no rounding setting.
datetime:
enabled: true
# ── Tool result size limit ──────────────────────────────────────────────────
# When set, tool results from *previous* turns that exceed this character
# count are replaced (at context-build time) with a short placeholder:
# "[Tool response hidden: N chars. Call the tool again if you need it.]"
# The original result is always preserved in the database and shown in the
# frontend; only what the LLM receives in subsequent turns is affected.
# The current turn always sees full results regardless of this limit.
# Omit or comment out to disable (no limit).
max_tool_result_chars: 10000
# ───────────────────────────────────────────────────────────────────────────
# ── Context compaction (AUTOMATIC pass disabled by default) ─────────────────
# Compaction summarises old history into a single block, persisted to the DB
# and injected at the start of subsequent turns in place of the messages it
# covers, keeping the last `keep_recent` raw messages for immediate context.
#
# The `/compact` command works ALWAYS and needs nothing here — this whole
# section is optional and only tunes it.
#
# `threshold_tokens` is what arms the AUTOMATIC pass: set it, and history is
# compacted on its own once the previous turn exceeded that many input tokens.
# It is COMMENTED OUT by default: every compaction rewrites the prompt prefix
# and so costs a prompt-cache miss, and doing it unprompted trades away context
# the user may still need. Compact manually with `/compact` for now.
# (Future: an automatic pass triggered by the model's own context window rather
# than by a hand-tuned token count.)
#
# `strength` controls which LLM is picked for summary generation via the AUTO
# selector (same strength levels used for agent assignment). Compaction is a
# simple writing task — `low` or `average` is usually sufficient.
# Omit `strength` to use whatever AUTO picks.
# NOTE: the Settings page has an instance-wide "Compaction model" picker
# (registry config key `compaction_model`) — when set, it wins over this
# `strength` fallback and needs no restart.
#
# When the LLM provider does not report token usage (e.g. some LM Studio
# setups), a rough estimate (total chars / 4) is used as a fallback.
#
# compaction:
# threshold_tokens: 30000 # arms the automatic pass, above this many input tokens
# keep_recent: 6 # raw messages kept outside the summary
# strength: low # LLM strength for summary generation
# ───────────────────────────────────────────────────────────────────────────
# ── Date/time injection ─────────────────────────────────────────────────────
# Configured above as `datetime`. The only setting is `enabled`.
# The injected time is always truncated to the hour: not for the prompt cache
# (the block is the LAST system message, so the cached prefix never changes
# whatever the timestamp says) but because a second-precision stamp reads as
# exact to the model long after it stopped being true. The block states the
# granularity and tells the agent to run `date` when it needs the minute.
# enabled: true # set to false to disable injection entirely
# ───────────────────────────────────────────────────────────────────────────
# ── LLM request/response log ────────────────────────────────────────────────
# Logs every chat_with_tools call to the `llm_requests` table.
# Captures the full HTTP request body, response body, headers (api-key redacted),
# token counts, and round-trip duration.
#
# WARNING: disabling `enabled` also disables the home-page LLM statistics.
#
# What to save (all default to true):
# request_payload_save — full request JSON (can be hundreds of KB per call)
# response_payload_save — full response JSON
# request_header_save — HTTP request headers (api-key always redacted)
# response_header_save — HTTP response headers
#
# Cleanup policy (all optional — omit to keep data forever):
# cleanup_request_payload_after — set request_json = '' after N days
# cleanup_response_payload_after — set response_json = NULL after N days
# cleanup_headers_after — null out both header columns after N days
# cleanup_rows_after — physically delete rows after N days
#
# The cleaner runs 1 minute after startup, then every 12 hours.
#
requests_log:
enabled: true
request_payload_save: false
response_payload_save: true
request_header_save: true
response_header_save: true
cleanup_request_payload_after: 7
cleanup_response_payload_after: 14
cleanup_headers_after: 30
cleanup_rows_after: 90
# ───────────────────────────────────────────────────────────────────────────
# ── Event triage (background event processor) ──────────────────────────────────
# Runs periodically to process pending MCP events (email, calendar, WhatsApp) and
# decide whether to surface a notification to the user.
#
# NOTE: top-level, NOT under `llm:` — nesting it there parses fine and is then
# silently ignored.
#
# interval_secs — how often it runs (default: 900 = 15 minutes)
# batch_size — max events processed per pass (default: 50)
#
# event_triage:
# interval_secs: 900
# batch_size: 50
# ───────────────────────────────────────────────────────────────────────────────