Documentation v1.0.9

Sessions & Agents

Session

A session is the core unit in Agenvoy. Each session has its own conversation context, memory, agent persona, and tool configuration.

Storage path: ~/.config/agenvoy/sessions/<sid>/

File Purpose
history.json Recent conversation, delta-appended
summary.json / .summary_cursor Rolling summary and its incremental cursor
action.log Tool call audit log. One line per event, [timestamp][window_hash][kind][task_hash] body (window_hash is the writing window's hash, the same value events carry as window_hash); the trailing task_hash bracket was added in v1.0.2 so every line can be traced back to the run that wrote it, and the replay parser treats it as optional so pre-v1.0.2 lines still load
.cmd_history TUI input history for this session
pending/ Pending ask_user / confirm metadata, one JSON per task hash

Everything else moved into ~/.config/agenvoy/.store/history.db:

SQLite table Content
session Display name, self_id, persona, model, reasoning level, rule
note + note_fts5 Operator notes and their trigram full-text index (v0.35.3)
messages + messages_fts5 Full message archive and its full-text index
action_history Finished runs and their tool results
file_history Recorded versions of every file the tools changed
usage Per-model token spend, 28-day retention

Vectors, tool cache, error memory, and running-task presence live in ToriiDB (.store/db_0 ... db_3). Legacy bot.json / bot.md / config.json / status.json / usage.log are migrated into SQLite at daemon startup and no longer written. The state table was dropped in v0.35.3: a running task is now an action:<session_id>:<task_hash> key in ToriiDB, refreshed every 55 s with a 60 s TTL, so a killed process stops being online on its own rather than needing a PID liveness check.

Session prefixes and lifetime

Prefix Lifetime
cli-* Permanent — local CLI / TUI work (TUI /new, or POST /v1/session)
chat-* Permanent — web / API work (POST /v1/send with persist=true)
dc-* Permanent (Discord channels)
tg-* Permanent (Telegram chats — per-chat, shared across users in that chat)
temp-* Reaped after 30 min idle (default for POST /v1/send and subagent sessions)

Cleanup runs every 30 minutes via cron (and once on startup) against the temp-* prefix only — cli-*, chat-*, dc-*, and tg-* are never auto-reaped.

The prefix also decides where a confirmation goes back to, and it is what the TUI session picker groups by: with at least two groups present the picker shows an all tab plus one tab per prefix, current session first. A daemon-side fsnotify watcher logs the ID and configured name of every newly created session directory.

Agent persona

Each session carries its own persona, stored in the session row rather than a file:

Field Notes
name Display name
self_id Normalized to lowercase, up to 32 ASCII letters, digits, _ or -; non-empty values must be unique across sessions (a duplicate returns 409); the lookup key for the subagents self_id parameter
rule Persona body, rendered into the system prompt's ## Bot Persona block on each turn
model / reasoning Session model and reasoning level; model: "" resets to auto

Edit any of them from the TUI with /bot, or through GET / POST /v1/session/:id — one endpoint for the whole session object, with POST as a partial update.

Agent routing

Two ways decide which agent handles a task:

1. Automatic — A dispatcher LLM analyzes the input and picks the best-fit provider via ResolveAgent().

Startup order: exec.Prepare (TUI-only exclusions, /skill-name match) → exec.Start (named skill resolution, agent resolution, session load) → Execute. A named skill that is excluded or not found fails the run.

The :self-id one-shot override prefix was removed; delegate to a named session with subagents(self_id=...), or switch sessions with /sessions.

2. subagents tool — An agent calls another agent in-process (no HTTP) during execution, inheriting AllowAll and WorkDir from the parent ctx.

Subagents run under a collection-only charter — they gather and report, they don't nest or write. The exclusion set is built from three parts:

Source Excluded
Charter base subagents, edit_file, generate* (wildcard)
TUI-only tools / skills Whatever configs/jsons/tui_tools.json lists, e.g. extension-upload, extension-install
Caller's exclude_tools Anything the parent passes in

ask_user is not excluded — subagents can ask the user through the shared pending registry. A subagent's pending question is also published to the owning (parent) session, and the leg waits for that resume and returns the answered result. At most three subagent legs execute concurrently (maxConcurrentSubagents); a fourth waits for a slot while its own MaxSubagentTimeoutMin (30 min) timeout is already running, so wide fan-outs should be dispatched in batches of three. Each result carries a usage prefix that rolls up into the parent session's totals.

Permission mode

Mode Behavior
single-confirm Each non-ReadOnly tool call requires user confirmation (the TUI default)
always-allow Tools auto-execute; the LLM is instructed to invoke ask_user first for seven categories of truly irreversible operations

The seven irreversible categories that still require explicit ask_user under always-allow:

  1. Filesystem — rm -rf / rm -r, deleting directories or existing files not produced by this task
  2. Database — DROP DATABASE / DROP TABLE / TRUNCATE, DELETE / UPDATE without WHERE, any production DSN
  3. Git — reset --hard, push --force to main/master, deleting shared branches, clean -fdx
  4. System — chmod 777 / chown -R, edits under /etc / /usr / /System, launchctl / systemd changes, sudo escalation
  5. Overwrite — an unread non-empty existing file, .env / credentials / lock files / .git/index
  6. Cloud & infra — gcloud / aws / kubectl delete, terraform destroy
  7. Process — shutdown / reboot, kill -9 on system service PIDs

Ordinary writes (edit_file, build and test commands, git status / add / commit, read-only shell) proceed directly. The gate is enforced by the system prompt (configs/prompts/system_prompt/permission/always_allow.md), not by hardcoded Go-side filters.

Per-session concurrency

MaxSessionTasks (NumCPU × 4) limits how many concurrent Execute() calls a single session can run. A task publishes its ToriiDB online marker and registers its cancel function before competing for a slot, so a queued task stays visible and cancellable instead of blocking invisibly. The marker carries a 60 s TTL refreshed every 55 s, so a killed or crashed process stops counting as online by expiry rather than by a PID check. The value is a package constant — it is not read from config.json.

Cancelling work

Cancellation is per task, not per session: POST /v1/session/:id/cancel/:task_hash stops one running task ({"cancelled": true}); when that task hash is not running in this process it records a canceled event and returns {"cancelled": false, "stale": true}. The former once_id identifier was replaced by the task hash. Tool confirmations are answered separately with POST /v1/session/:id/confirm/:confirm_hash, where confirm_hash comes from the confirm event. There is deliberately no cancel-everything variant, so one long-running job can be dropped without disturbing the session's other tasks. In the TUI, Esc during a run opens the cancel-confirmation popup for the active task.

Resuming interrupted work

A run that stopped on an ask_user or a confirmation leaves a pending task behind. GET /v1/session/:id/task lists the ones that are actually resumable: a live run refreshes action:<session_id>:<task_hash> in ToriiDB every 55 s with a 60 s TTL, so a task abandoned by a closed window or a killed process reappears in that list within a minute, while a task still being worked on does not. Answer it with POST .../resume, or drop it with DELETE .../:task_hash. In the TUI this is /pending.

Completed runs stay queryable too: GET /v1/session/:id/task/history lists them as task_hash, end_at, objective, model, and reasoning (optional keyword filter), and GET .../:task_hash/history returns one run's full action record as content.

中文