Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
16b9e1f
Hide disabled providers from model selection while retaining saved po…
Calmingstorm Sep 26, 2026
cef53ee
Preserve agent drafts and main selection across status refreshes
Calmingstorm Sep 26, 2026
4a11666
Preserve compatible model presentation and exclude Agent Auto from pr…
Calmingstorm Sep 26, 2026
285d048
Fix pre-dispatch computer refusal outcomes and audit success codes
Calmingstorm Sep 26, 2026
e985451
Fix email and validation probes for partial and error responses
Calmingstorm Sep 26, 2026
10923ec
Validate release tag before passing version to shell
Calmingstorm Sep 26, 2026
b91519d
Document actual logs and fresh-install bootstrap behavior
Calmingstorm Sep 26, 2026
d9dfaf3
Replace stale WebUI credentials when login persistence changes
Calmingstorm Sep 26, 2026
2d45a12
fix: count preserved GLM reasoning in context sizing
Calmingstorm Sep 26, 2026
e0022b6
fix: align compatible history reserve with request output
Calmingstorm Sep 26, 2026
853a47d
Fix retained audit segment integrity verification (#390)
Calmingstorm Sep 26, 2026
6bb0a0c
Fail closed when timed-out tool ledger settlement fails
Calmingstorm Sep 26, 2026
988ca62
Repair resumed tool calls by occurrence and generation
Calmingstorm Sep 26, 2026
bbe4556
Document schedule webhook delivery and retry semantics
Calmingstorm Sep 26, 2026
1d0a1aa
Quarantine interrupted one-time schedules
Sep 26, 2026
b1e197d
Fix scheduled digest identity and collection failures
Sep 26, 2026
1922c96
Fix Grafana named trigger batch matching
Calmingstorm Sep 26, 2026
8ecd772
Cover one-time schedule interruption and retry boundaries
Calmingstorm Sep 26, 2026
0fe8db4
Clarify browser tool session isolation
Calmingstorm Sep 26, 2026
ee583e8
Keep Discord code fences balanced across reply splits
Calmingstorm Sep 26, 2026
edf51c2
Parse complete reminder times across clock changes
Calmingstorm Sep 26, 2026
4fe7cee
Label failed tool results in compressed history
Calmingstorm Sep 26, 2026
e9af15e
Cover partial email delivery with refused To and accepted CC
Calmingstorm Sep 26, 2026
b633cfb
Fix cancellation races in persistence writes
Calmingstorm Sep 26, 2026
2bbbf96
Regenerate API reference after route and webhook changes
Calmingstorm Sep 26, 2026
6b88bf1
Publish knowledge documents atomically across stores and snapshots
Calmingstorm Sep 26, 2026
9ecd477
Cover vector retirement on knowledge replacement
Calmingstorm Sep 26, 2026
b05c770
Restore archived summary segment search and scope history by user
Calmingstorm Sep 26, 2026
b8ab740
Fix audit and time parser type findings
Calmingstorm Sep 27, 2026
e17584c
Fix startup and channel search compatibility
Calmingstorm Sep 27, 2026
bf6afff
Handle scheduled digest collection edge cases
Calmingstorm Sep 27, 2026
8acc584
Refuse duplicate operation identities during resume
odin Sep 27, 2026
8a50d4e
Keep compressed partial tool calls paired by result ID
Calmingstorm Sep 27, 2026
401b425
Pin compatible tool-chat output reserve and bare-chat override
Calmingstorm Sep 27, 2026
b1db788
Keep resume ledger lookup type-safe
Calmingstorm Sep 27, 2026
e809dc0
Account structured tool results in context estimates
Calmingstorm Sep 27, 2026
e33d004
Keep structured result regression lint-clean
Calmingstorm Sep 27, 2026
396493e
Wait for actual delivery in auto-resume regression
Calmingstorm Sep 27, 2026
7db9e23
Test knowledge and session archive failure boundaries
Calmingstorm Sep 27, 2026
1f500a5
test scheduled digest and compatible budget edge cases
Sep 27, 2026
f43bf56
Keep knowledge reconciliation regression lint-clean
Calmingstorm Sep 27, 2026
a82c094
test: cover audit and tool text edge paths
Sep 27, 2026
1c14c78
test channel logger user filter reset semantics
Calmingstorm Sep 27, 2026
b6e4e49
test: cover audit verification snapshot failure paths
Calmingstorm Sep 27, 2026
fba3bf2
test: cover knowledge durability preconditions
Sep 27, 2026
d8d6148
test: cover scheduled digest and legacy budget edges
Calmingstorm Sep 27, 2026
46e4db8
Cover reset filtering and unavailable knowledge FTS
Calmingstorm Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,21 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Extract version from tag
- name: Extract and validate version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Release tags must be vMAJOR.MINOR.PATCH; refusing ${GITHUB_REF_NAME@Q}" >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Set version in pyproject.toml
run: sed -i "s/^version = .*/version = \"${{ steps.version.outputs.version }}\"/" pyproject.toml
env:
VERSION: ${{ steps.version.outputs.version }}
run: sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml

- name: Install nfpm
run: |
Expand Down
111 changes: 111 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,117 @@ Each GitHub release body is the matching section of this file.

## [Unreleased]

### Changed

- Compatible and OpenRouter models budget chat, loop and agent history against
the output each request actually reserves (the profile's maximum output,
capped at 32,768 tokens), the same figure agent eligibility already used.
Long tool-using turns keep more history before summarizing (DeepSeek V4 at
the default 75%: 491,520 to 761,856 working tokens), so those turns send
more input tokens per request; models whose declared output nearly equals
their context window no longer run with no history budget and no overflow
rescue. An agent whose compatible provider rejects a payload already below
the provider-reported window now compacts instead of re-sending it. Codex is
unchanged. To retain the previous smaller compatible working set, lower
`openai_compatible.context_utilization` (about 48 for DeepSeek V4's previous
491,520 tokens).
- One-time check, workflow and webhook schedules interrupted by a restart are
now paused as inert rather than run again from the beginning. Their reason
is shown to operators; set a new `run_at` to re-arm after checking effects.
Reminders and digests still replay, and recurring schedules are unchanged.
No action is required on upgrade.
- Documented webhook success, partial-delivery and retry behaviour for
scheduled webhook actions and inbound webhooks.
- History search finds archived conversation summaries again; the first start
after upgrading indexes existing archives in the background. Expect the
session and full-text databases to grow during this one-time migration.

### Fixed

- Computer-use requests refused before any input (unknown source IDs, export
on an attached desktop, Hyprland target discovery failures, and stale or
unknown session references) now report `not_dispatched` with a safe next
step instead of unknown-outcome RELEASE-ALL guidance. Such refusals no longer
cancel a running session, including another session in the same channel.
Successful computer calls are audited with a success reason code rather
than `computer_rejected`.
- `email_send` reports recipients the mail server refused (To, CC or BCC)
instead of claiming the message went to everyone; a partial send is reported,
never retried.
- `email_read` no longer shows a text attachment as the message body, and its
attachment list follows the MIME disposition (any capitalisation) instead of
a text match.
- `http_probe` sends request bodies starting with `@` literally instead of
uploading a file, and rejects header names starting with `@`. Such bodies
require curl 7.43 or newer on the probing host.
- `validate_action` process checks no longer find their own command instead of
the target process; a matching ancestor still counts. Missing `pgrep` and
unusable patterns now report errors rather than false health.
- `validate_action` log checks report an error when the journal cannot be read
or is only partly readable instead of claiming it is clean. Invalid patterns
report errors and journalctl notices no longer count as log lines. Operators
whose service user lacks journal access must grant it (for example via the
`systemd-journal` group) before relying on these checks.
- `validate_action` HTTP checks accept explicitly expected 4xx/5xx statuses;
connection failures and timeouts never count as a received status.
- The release workflow rejects tags that are not `vMAJOR.MINOR.PATCH` before
touching package metadata, and passes a validated version to shell steps as
data rather than embedding tag text in a command.
- Documentation and `scripts/monitor.sh` now point to standard output and the
systemd journal rather than a log file Odin never writes. Fresh packages since
v4.0.0 start immediately in loopback-only bootstrap mode; the computer-use
handoff and install pages now say so.
- Logging in to the WebUI without **Stay logged in** now replaces an older
saved login, so reloading after a restart retains the new session instead of
returning to the login screen. A persistent login also clears the tab's old
session-only credential.
- Compatible endpoints configured for GLM preserved thinking now count replayed
reasoning when sizing context, so history is summarized before it overflows
and overflow recovery no longer claims a fit while that reasoning is still
sent; replayed reasoning is never shortened or edited.
- Verify integrity checks every retained rotated audit file, not only the active
one, and lists each file's result (verified, predates signing, break at line N,
unreadable, missing); files are streamed in a worker thread instead of read into
memory.
- A tool that hit its time limit no longer lets the turn continue when its
ledger record could not be saved; the turn stops with an error, as every
other ledger write failure already does.
- Resuming interrupted work no longer leaves a tool call unanswered, or answers
it with an earlier call's result, when a model provider reuses tool-call IDs
across replies. Agents also accept IDs reused across separate replies while
still refusing duplicates within one reply.
- Scheduled digests report disk and memory again. They run under the schedule's
identity (the creator, or the `scheduler` system identity) and list hosts they
cannot reach as collection failures.
- Grafana-triggered schedules with an alert name now match any alert in a
notification, not just the first; each schedule still runs once per
notification.
- Browser tool descriptions state that every call starts a fresh browser session:
`browser_click` no longer suggests reading the clicked page with a later
`browser_read_page`, `browser_fill` points to `submit=true`, and
`browser_evaluate` notes that navigation it starts is not awaited. Browser
behaviour is unchanged.
- Compressed tool history labels failed calls with a short reason (for example
`run_command→ERR (blocked)`, `→ERR (timed out)` or `→ERR (disallowed host)`)
instead of incorrectly marking these failures OK; Recent Actions marks
failed calls ERROR. Successful calls with explicit outcome metadata remain OK.

- Long replies split around code blocks keep their formatting: text after a
block no longer shows as code, no message ends with an empty code block, and
a split with a long language tag no longer creates an over-limit message.
Truncated workflow and loop posts close open code blocks before the marker.
- `parse_time` uses every part of an expression, including compound durations
(`in 1 hour 30 minutes`), a day after a time (`5pm tomorrow`), a time after a
weekday (`friday 3pm`), and `in 2 days at 9am`. Unused date or time words now
return an error instead of silently scheduling the wrong time. Hours and
minutes measure elapsed time across daylight-saving changes; days retain the
local clock time.
- A cancelled knowledge, memory, list or learned write can no longer overwrite (or corrupt) a newer save that already succeeded.
- A failed knowledge ingest no longer leaves search hits for a document that was not stored or blocks that name; leftovers from earlier failures are removed at startup.
- Knowledge ingest reports failure when its version record cannot be saved, and re-ingesting the same content repairs a missing version snapshot.
- The Sessions page User ID filter now applies to every result source; summaries
and index results that cannot be attributed are left out.

## [4.7.0] - 2026-09-24

### Added
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ sudo -u odin /opt/odin/.venv/bin/python /opt/odin/scripts/codex_login.py \
--credentials-path /var/lib/odin/codex_auth.json --device
sudoedit /etc/odin/config.yml # set web.api_token; bind web.host to 127.0.0.1 unless it sits behind TLS;
# review permissions.default_tier (template: admin) and tools.hosts
sudo systemctl start odin # WebUI on the configured web.port (default 3000)
sudo systemctl restart odin # WebUI on the configured web.port (default 3000)
```

**Upcoming branch packages:** unlike the published v3.98.0 procedure above, a fresh install automatically enables and starts a restricted loopback bootstrap service. Open `http://127.0.0.1:3000/ui/` locally. For a remote install, run `ssh -L 3000:127.0.0.1:3000 user@odin-host` on your workstation, then open that same local URL. Do not publish pending setup through a reverse proxy.
**Packages since v4.0.0:** unlike the v3.98.0 procedure above, a fresh install automatically enables and starts a restricted loopback bootstrap service. Open `http://127.0.0.1:3000/ui/` locally. For a remote install, run `ssh -L 3000:127.0.0.1:3000 user@odin-host` on your workstation, then open that same local URL. Do not publish pending setup through a reverse proxy.

Finish setup with a strong Web API token, then sign in as administrator. **System → Config → Web listener exposure** shows the configured host, whether it came from an explicit `web.host` key or the schema default, and the addresses the current process actually owns. Authorize beyond-loopback access there and re-enter a current admin API token. This records permission for the next start, not a live rebind. After arranging TLS and network access controls, restart manually with `sudo systemctl restart odin`. The same card can revoke authorization and narrow the next start to loopback. A Discord token alone never authenticates or widens the Web listener.

Expand Down Expand Up @@ -234,15 +234,15 @@ The package installs:
| Environment file | `/etc/odin/.env` |
| Persistent data | `/var/lib/odin` |
| Local command workspace | `/var/lib/odin-workspace` |
| Logs | `/var/log/odin` |
| Logs | systemd journal (`sudo journalctl -u odin`); `/var/log/odin` is created but not written |
| Systemd unit | `/usr/lib/systemd/system/odin.service` |
| Private computer evidence (service-owned, 0700) | `/var/lib/odin/computer` |
| Precompiled root-owned Wayland guardian | `/usr/libexec/odin-computer-wayland-input` |
| Inert GNOME scope extension (not enabled) | `/usr/share/gnome-shell/extensions/odin-scope@calmingstorm.net/` |
| Computer-use installation handoff | `/usr/share/doc/odin/computer-use/PACKAGING.md` |
| Computer-use setup and recovery | `/usr/share/doc/odin/computer-use/OPERATOR.md`, `RECOVERY.md` |

The package installs the application files and systemd unit. Its post-install script creates the `odin` service account, virtual environment, SSH key, data directories, configuration links, and local command workspace. Upcoming branch packages automatically enable and start a new installation in loopback-only bootstrap mode; published v3.98.0 packages are enabled but require manual configuration and start. See the [Quick start](#quick-start) for local access and SSH forwarding. Upgrades preserve configuration and data and restart the service only if it was already running.
The package installs the application files and systemd unit. Its post-install script creates the `odin` service account, virtual environment, SSH key, data directories, configuration links, and local command workspace. Since v4.0.0, a fresh installation is enabled and started in loopback-only bootstrap mode; v3.98.0 and earlier packages were enabled but required manual configuration and start. See the [Quick start](#quick-start) for local access and SSH forwarding. Upgrades preserve configuration and data and restart the service only if it was already running.

Fresh installs and upgrades install the `pdf` and `computer` Python extras and
provision private computer state with symlink rejection. Computer enablement is
Expand Down Expand Up @@ -304,7 +304,7 @@ sudoedit /etc/odin/config.yml
5. Start the service and inspect startup:

```bash
sudo systemctl start odin
sudo systemctl restart odin
sudo systemctl status odin
sudo journalctl -u odin -f
```
Expand Down
9 changes: 7 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,9 @@ attempts; a healthy in-flight generation has its own transport limits.
[`TurnResumeManager`][resume] rechecks the original request and current
authorization before resuming suspended chat work. In-process auto-resume also
requires the session not to have advanced. After restart, resume is explicit.
Ledger repair supplies stored results or explicit uncertainty for unmatched tool
calls; it does not automatically execute them again. Unresolved external effects
Ledger repair pairs calls by transcript position and generation, supplying stored
results or explicit uncertainty for unmatched tool calls; it does not automatically
execute them again. Unresolved external effects
block automatic continuation.

## Managed hosts: desired state, runtime identity, access
Expand Down Expand Up @@ -223,6 +224,10 @@ result summaries, elapsed time, errors, and optional risk/diff metadata.
reflection/lifecycle hooks. These explain what happened; the durable turn ledger
governs replay safety. Neither replaces the other.

Each retained audit file carries its own HMAC chain from genesis; Verify integrity
checks and reports every retained file independently. It cannot detect files
already removed by rotation or deletion from the oldest end.

Large outputs should be retrieved, not regenerated merely because a preview was
short. The retention contracts are intentionally different:

Expand Down
7 changes: 6 additions & 1 deletion docs/computer-use/PACKAGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ storage safely, including for source installs; they never repair existing unsafe
objects or move receipts. System Python dependencies remain distinct from the venv.

Fresh installation leaves computer use disabled by default. The base Odin service
is enabled but not started until the operator completes setup. Ordinary upgrades
is enabled and started immediately in a restricted bootstrap mode: it listens on
loopback only, whatever `web.host` says, and serves guided setup at
`http://127.0.0.1:3000` (use an SSH tunnel for a remote host, never a reverse
proxy) until the operator completes it; widening beyond loopback later is an
explicit, authenticated operator choice. Packages up to v3.98.0 enabled the
service without starting it. Ordinary upgrades
preserve configuration, computer enablement, data and prior service state: an
already-running service is restarted; an inactive one remains inactive. No computer
task, desktop capture, input action, extension activation, session-bus connection,
Expand Down
7 changes: 7 additions & 0 deletions docs/computer-use/RECOVERY.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ Those measures apply only when release is unverified or outcome is unknown.
inspect fresh evidence before planning a different action. No category permits
replaying an action whose effect may already have occurred.

Observe, source selection, export, status, target inventory and start preflight
refusals that occur before input, focus recovery or cleanup carry
`not_dispatched` and leave an existing session running. A rejected request for
an unknown source ID or an unavailable attached-desktop export therefore does
not require RELEASE-ALL. Successful calls carry the audit reason
`computer_succeeded`, or `verified`/`executed` for successful action receipts.

For the separate alternate-input rule, see [OPERATOR.md](OPERATOR.md#alternate-input-paths).

For persisted quarantine, **System > Computer** remains available even when input
Expand Down
18 changes: 16 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ and OpenRouter. URLs are used verbatim: Odin never appends `/v1`. Local examples
are vLLM `http://127.0.0.1:8000/v1`, llama.cpp `:8080/v1`, and LM Studio
`:1234/v1`. Profiles declare reasoning dialect plus reasoning-content feedback;
the safe default is not to echo provider reasoning into history.
A compatible model's usable prompt budget is its total window minus the output
each request asks for (the profile's max output, capped at 32,768 tokens);
`openai_compatible.context_utilization` sets how much of that history may use.

`iteration_timeout_seconds` bounds each agent LLM call. It is a backstop
against a hung call, not a working limit — set it well above a legitimate
Expand Down Expand Up @@ -248,6 +251,8 @@ browser:

Leave `cdp_url` empty to launch a local headless Chromium. Set to `ws://host:port?token=secret` for remote Browserless.

Each browser tool call runs in a new, empty browser context that is closed when the call ends; cookies, storage, form input and page state never carry over between calls.

Run `playwright install chromium` after installation.

## Image Generation
Expand Down Expand Up @@ -312,6 +317,10 @@ Runtime overrides persist in `data/permissions.json` and take precedence.

## Webhooks

For scheduled HTTP actions and inbound webhook delivery, see
[Schedules & webhooks](scheduling.md). This includes retry and partial-delivery
behaviour; outbound notifications use a separate path.

```yaml
webhook:
enabled: false
Expand All @@ -333,17 +342,22 @@ context:
```yaml
logging:
level: INFO # DEBUG, INFO, WARNING, ERROR
directory: ./data/logs
directory: ./data/logs # reserved path kept out of the command workspace; Odin writes no log files here
```

Odin writes application logs to standard output/error: for the service use
`journalctl -u odin -f`, for Docker use `docker logs odin-bot`, and for a source
run use its terminal. `logging.level` sets verbosity. Tool executions and events
go to the audit log (`data/audit.jsonl`), which the WebUI Audit and Logs pages read.

## File Paths (DEB install)

| Purpose | Path |
|---------|------|
| Config | `/etc/odin/config.yml` |
| Secrets | `/etc/odin/.env` |
| Data | `/var/lib/odin/` |
| Logs | `/var/log/odin/` |
| Logs | systemd journal (`sudo journalctl -u odin`); `/var/log/odin` is created but not written |
| Application | `/opt/odin/` |
| Systemd | `/usr/lib/systemd/system/odin.service` |

Expand Down
6 changes: 3 additions & 3 deletions docs/install.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Install

The WebUI-first bootstrap flow below describes this branch's upcoming release.
Published v3.98.0 packages still require the earlier manual configuration and start procedure.
The WebUI-first bootstrap flow below applies to packages since v4.0.0.
v3.98.0 and earlier packages used the manual configuration and start procedure.

Odin ships as an amd64 Debian package and as a source checkout. The package path is for a long-running service; the source path is for development.

Expand All @@ -27,7 +27,7 @@ The package installs a dedicated `odin` system user, a Python virtual environmen
| Environment file | `/etc/odin/.env` |
| Persistent data | `/var/lib/odin` |
| Local command workspace | `/var/lib/odin-workspace` |
| Logs | `/var/log/odin` |
| Logs | systemd journal (`sudo journalctl -u odin`); `/var/log/odin` is created but not written |
| Systemd unit | `/usr/lib/systemd/system/odin.service` |

## First-time setup
Expand Down
Loading
Loading