Files
Skald-Circle/docs/index.md
T
dguiducci daaceff6ba
Nightly Build / build (push) Successful in 7m34s
feat: show a conversation its own background tasks, and give it back every outcome
An `execute_task mode="async"` was invisible from the chat that started it.
The only trace was the receipt in the transcript and a row on the Tasks page
— which does not say *which* of those rows the assistant just spawned — so
"is it still going?" had no answer where the question is asked.

Worse, a task that did not simply succeed never came back at all. `run_job`
branched on `Ok`/`Err` first and routed by `job.kind` only inside the `Ok`
arm, so a failure or a kill left through the `Err` arm's unconditional
`hub.notify` — the home source (`/sethome`), worded "Cron job … failed" —
while the parent conversation sat waiting for a `task_completed` that would
never arrive. The wrong chat, and a wedged one.

The fix is a shape, not a branch: one `JobOutcome` classification, then one
`match job.kind` delivery site for every ending. An async task now ends in
its parent conversation whatever happened to it. The sink has a single
channel deliberately — to the model reading it, "it broke" is a result like
any other and must not be overlookable — so a failure is delivered as prose,
carrying whatever partial output the run produced, which is usually the only
clue about why. A cron job keeps the home notification: it belongs to nobody's
conversation. Cancellation becomes a third outcome rather than a flavour of
failure (`job_runs.status` has always had `'cancelled'` in its CHECK and
nothing ever wrote it), classified off the new typed `TurnCancelled` error so
nothing keys on a message string.

The strip above the composer is the visible half. `ServerEvent::TaskUpdate`
announces state to the source of the parent conversation only; the list is
`renderTaskStrip` (shared by the desktop copilot and the mobile chat), fed by
state on `ChatSession`. Each row links to `#session/{id}` — the page that
already shows, live, what a background agent is doing, and without which
"a task is running" is a fact you can do nothing with. Stopping is the
existing kill endpoint. A finished row clears itself after 20 s (its result
is in the conversation by then); a failed one stays until dismissed, and the
dismissal is remembered across reloads.

`GET /api/{source}/tasks` is what makes the strip survive a browser refresh:
the event is a broadcast with no replay, so without a load-time read a reload
would empty a chat that still has work running under it. It answers with the
running tasks plus failures from the last 30 minutes — the two states a person
can still act on. Successes are absent on purpose. Its window compares through
`datetime()` on both sides: `completed_at` is RFC 3339 and the cutoff is
SQLite-shaped, and `'T' > ' '` would let every same-day row through a window
meant to exclude it.

Not addressed, and worth doing next: a cron job's result should go where its
creator says, not always to the home chat.
2026-08-04 19:13:30 +01:00

4.8 KiB

Documentation index

This folder is written for you, the assistant, not for the human directly. It is mounted read-only at ~/docs/ in your workspace. Read it when a user asks how the software itself works, wants help configuring something, or asks what's possible — then explain it in your own words, adapted to that person (their technical level, their language, their actual goal). Don't just paste these files back at them.

Keep answers grounded in what's actually enabled and configured for this instance — check with the relevant tool (e.g. list installed/enabled plugins) rather than assuming everything described here is turned on. A feature documented here may not be enabled on this particular instance.

This index will grow over time. Right now it covers memory, projects, background tasks, system agents, access grants, voice input and plugins; more sections (agents, connectors, security groups, shared folders…) will be added later.

Features

Document What it covers
memory.md Private and shared memory: what goes where, the indexes and history log, why some shared facts can't be changed on request
projects.md Projects: shared folders with their own assistant chat, a live file explorer, and member sharing
system-agents.md Background agents that run on a schedule (event triage, the two memory lints, the nightly conversation review of a supervised account): what they watch, why they only ever report, why a run can be skipped, and their settings
tasks.md Background tasks: the strip above the message box, following one live, stopping one, and how every outcome comes back to the conversation
settings.md The admin's Config page: interface language, the compaction model picker, debug mode
access.md Who can use which plugin or connector: the open default, removing access per person, and the role switch that keeps children out of it
voice.md Voice input: configuring a transcription model, and why the microphone button does nothing unless the page is served over HTTPS or localhost

Plugins

Plugins are optional add-ons an admin can enable and configure — extra voices, extra ways to reach the assistant (Telegram, a phone app), image generation, long-term memory, remote access, and so on. Each has its own document in plugins/:

Document What it adds
plugins/comfyui.md Local image generation via a self-hosted ComfyUI server
plugins/elevenlabs.md Cloud text-to-speech and transcription (ElevenLabs)
plugins/whisper_local.md Local, private speech-to-text (no cloud, no API key)
plugins/kokoro_tts.md Local, lightweight text-to-speech (CPU-only, no API key)
plugins/orpheus_tts_3b.md Local, expressive text-to-speech with emotion tags (needs a GPU)
plugins/honcho.md Long-term cross-session memory via an external Honcho server (opt-in per user)
plugins/telegram.md Chat with the assistant from Telegram
plugins/mobile-connector.md Companion mobile app: Inbox notifications + remote access, end-to-end encrypted
plugins/remote_connectivity.md Reach the web app remotely over a Tailscale mesh network

General plugin mechanics that apply to all of them:

  • An admin enables/disables and configures each plugin from the Plugins page (sidebar → Plugins, admin-only): one card per plugin, an enable toggle, and a Configure button opening its settings form.
  • Enabling a plugin hands it to everyone straight away — except to roles that opt out of that (the Children role does). The admin then removes it from whoever should not have it, rather than granting it person by person. Full details in access.md. (Mobile Connector is the one exception to the whole grant model: access there is the device-pairing itself, not a grant list.)
  • Access is changed per person, from that person's own page: sidebar → Users → click the user → the Plugins section, right below their Connectors. So "what may this person use?" is answered in one place, for plugins and connectors together. (The plugin's own page shows the reverse view — who currently holds it — but read-only.) Admins can use every enabled plugin without being granted anything.
  • A plugin with per-user settings (e.g. Telegram's pairing code, Honcho's memory opt-in) gives each granted user its own dedicated sidebar page to manage them — separate from the admin's instance-wide config.
  • A plugin can add tools the assistant calls directly (e.g. set_secret, telegram_pairing), a dedicated sidebar page, or both.