Skip to content

Prometheus v0.9.5

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Oct 19:23
· 232 commits to main since this release
3c29eb4

Tokens are redacted before anything is kept, skills are advertised and measured, GEPA only proposes changes, and the first daemon start repairs conversation history once. Read "Upgrading from 0.9.4" before you restart the daemon.

Security

  • Tokens are redacted before anything is kept (#596, #599). Token shapes become <redacted> before a row is written:

    • lcm.db: messages, summaries, divergence checkpoints, session titles and their full-text indexes;
    • memory.db: facts, including updates;
    • telemetry.db: run summaries, signal payloads, circuit-breaker samples and parsed tool calls. Tool-call errors and outputs were already covered.

    The redactor now also knows PEM private keys, AWS keys, passwords in URLs to non-loopback hosts, Slack and Discord webhook paths, access_token/api_key/client_secret query values, and about 30 more vendor key prefixes. Every prompt a background subsystem sends to a model is redacted too: skill creation and refinement, the curator, memory extraction, GEPA, LCM summaries and video digests.

  • The live conversation is not redacted. A token you paste for the agent to use keeps working for the rest of that session. What reads history back shows <redacted>: the recall tools, a history reload in Beacon, and a session restored after a restart (sessions.rehydrate).

  • Data kept before 0.9.5 is not rewritten for you (#622). oara scrub reports what it would change, as counts only, with every database opened read-only. With the daemon stopped, oara scrub --apply rewrites in place. It covers telemetry, training pairs, trajectory exports, both LCM files and memory.db. Before rewriting a database, it copies it to <db>.pre-scrub-<UTC time>. It never prints a matched value. Delete the *.pre-scrub-* copies once you are satisfied: they, and any other backup or snapshot, still hold the old values. Not covered: the MEMORY.md and USER.md files the memory tool keeps, and wiki pages compiled from facts before the scrub.

Skills

  • The model is offered the skill tool and told when to use it (#593, #623). skill joins the default advertised tool set, including in configs that setup wrote with the old list. Its description, and the prompt's skills section, now say what a skill is: saved step-by-step instructions for one kind of task. They also say when to load one: before starting a task it covers. If your config lists tools.deferred_loading.always_loaded, see the upgrade notes.
  • Skill use is measured (#591, #598).
    • Every skill load is recorded in telemetry.db.
    • /skills list on Telegram, Slack and Discord shows when each skill was last used and how many loads it has, not its file date.
    • GET /api/skills/list rows gain loads and last_loaded_at, and GET /api/status gains skill_loads.
    • The Curator ages skills by their last load. A skill with no recorded load is never marked stale or pruned by its file date.
    • A loaded skill's text is never microcompacted away.
  • Fewer near-duplicate auto skills (#591, #625).
    • On the automatic path, a new skill within cosine 0.80 of a skill already served is not written. Set the threshold with learning.skill_dedupe_threshold. This uses an optional local encoder; see the upgrade notes.
    • Every writer refuses a description that is literally name: ….
    • tool_search lists a skill only when the query matches it (a score of 0.5 or more), not by name distance.
  • Skill creation and refinement see what each tool call was given (#597). Each call's input appears in their prompts, redacted and cut to 240 characters. Before, every call read tool({}).
  • Machine-written skills are scanned before they are written (#611). A skill with dangerous Python in it is not written. This applies to the automatic path, teacher escalation, Record a Skill and accepting a draft. Record a Skill answers 502 with skill persistence rejected the generated content, and accepting a draft answers 422 and keeps the draft.
  • Nothing machine-written replaces a live skill (#615, #617). Skill files are created exclusively and never overwritten.
    • Name clashes go to a person. A teacher escalation or a recording whose skill name is already served goes to skills/drafts/ for review. A name counts as served whether it belongs to an auto, a builtin or a user skill. Record a Skill answers "status": "draft". The automatic path skips the name.
    • Accepting a clashing draft (POST /api/learning/skill-drafts/{id}/accept) answers 409 with the clash. Send {"replace": true} to archive the auto skill it replaces into skills/auto/archive/ and accept.
    • Recorded values stay values. Values in a recording are written as JSON inside inline code, so they cannot open markdown in the skill.

Learning: GEPA

  • GEPA works from real skill loads and only proposes (#606). It is still off by default and experimental.
    • Candidates: auto skills the model loaded at least gepa_min_loads times (default 3). The runs around those loads are the evidence.
    • Drafting: the local model drafts variants. A hosted primary is used only with learning.gepa_allow_hosted: true.
    • Judging: the judge scores the live skill and each variant on the same runs. A variant becomes a proposal in ~/.prometheus/skills/proposals/ only if all three hold:
      • it beats the live skill by gepa_min_margin (0.15);
      • it reaches gepa_judge_threshold;
      • it is worse on no run.
    • Promotion: nothing changes until a person runs oara gepa promote <id>. It checks the live skill is the version that was scored, runs the code scanner, and archives the old version.
    • New commands: oara gepa proposals | show <id> | promote <id> | reject <id>, and oara gepa dry-run [--json] (counts only: no model calls, no writes).

Evals

  • A judge reply with no verdict is no longer a score (#603).
    • Before: an empty or unparseable reply scored 0.0, a fallback read any number it found, and out-of-range scores were clamped.
    • Now: the metric is listed as unavailable (unavailable_metrics, plus unparseable_metrics) and is never a pass or a fail. Summaries report judge_unavailable, scored_tasks, and a pass_rate over scored tasks only.
    • Parsing: JSON verdicts are read only as JSON, and G-Eval only from a final SCORE: line. The evals, the model ladder and GEPA share this one parser.

Memory and sessions

  • Every message keeps one position in its conversation (#587).
    • The bug: restarts, /reset and retried turns re-used positions that older messages held. History read in order could then interleave two conversations. That fed the LCM summarizer and the golden-trace export; the live prompt was not affected.
    • The fix: new messages are numbered above the session's highest stored position, and the CLI chat numbers each message separately.
    • Existing data: the first daemon start repairs what is already stored and adds a guard (see the upgrade notes).
    • API: the ordinal field in history and WebSocket messages is now unique within a session. Keep ordering by message_id.
  • Restoring a session after a restart finds the conversation (#590, #600). This applies with sessions.rehydrate: true, which is off by default.
    • Deeper search: when the newest 40 rows hold no plain human message, as after a long agentic turn, the restore now finds the newest human turn up to 2,000 rows back. Before, it restored nothing.
    • Fitting it: tool payloads over 1,000 characters are shortened in the restored copy. If that is still too big, it keeps the request plus the newest rounds, with a note saying what was left out.
    • More surfaces restore: Telegram task results, Slack, Discord and POST /api/chat now restore before replying.
    • Sizing: rows are sized with the compactor's own estimate.
  • A failed turn in Beacon keeps a message you sent while it ran (#590).
  • Reasoning no request sends back no longer counts toward the context estimate (#595). This is the thinking of Ollama thinking models and OpenAI-compatible reasoning providers. Counting it made compaction fire early, made /context overstate use, and could refuse a local prompt as too long.
  • memory.db migration snapshots are SQLite backups, so they include rows still in the WAL. They are owner-only, and a second snapshot in the same second gets a -1 suffix instead of overwriting the first (#594, #601). A 0.9.4 store needs neither migration.
  • Golden-trace exports pair each tool call with the conversation just before it (#586). In a session longer than 500 messages, calls after the 500th were paired with the conversation around message 500. Files exported earlier are not rewritten.

Models and providers

  • Qwen's XML tool calls run (#582). A tool call written as text in Qwen's <function=…><parameter=…> format used to be stripped out and lost. It now runs. Calls written in XML and JSON in one reply run in the order written, and an unwrapped XML call no longer shows in the final reply.
  • A local model the registry does not know gets its tier from the served chat template (#583).
    • llama.cpp's /props and Ollama's /api/show are asked whether the model calls tools natively: yes gives light, no gives full. Before, every such model ran at full.
    • One boot line per adapter says which tier and why. A model that falls back to full gets a WARNING saying how to name it.
    • The tier is decided at boot. Restart the daemon after swapping the served model.
  • adapter.model_tiers sets the tier per model by hand (#602).
    • Keys are case-insensitive substrings of the model name, like the registry's; the longest match wins, and it beats every other source.
    • Values are auto, off, light and full. Write "off" in quotes: bare off is YAML for false, and the entry is refused.
    • A bad value is refused with an ERROR and the entry ignored.
    • off on a local backend is allowed with a WARNING. So is lifting a cloud model off off.
    • It does not reach router fallback or rule providers.
  • Coding runs use the model the server actually serves (#585). oara code, and the coding tasks the daemon starts, used to build their adapter from the config's model name. That is often blank, so every coding run ran at full with no native tools. They now get the same tier as chat, and requests and telemetry carry the served model's name.
  • Ollama thinking models think only when asked, and the thought is kept (#592).
    • For a model whose /api/show lists thinking, chat turns send reasoning_effort: "none"; coding runs still think. There is no setting in this release to turn thinking back on for chat.
    • When a model does think, the thought is stored with the message instead of being thrown away. It is never shown in the reply or sent back to the model.
    • A reply that is all thought and no answer is logged, and the call is retried once with thinking off.
  • Cloud and OpenAI-compatible calls record the model that served them (#614), beside the one requested.

Router

  • A fallback never hands back the provider that just failed (#584). On a llama.cpp → Ollama → Anthropic chain, an Ollama failure used to route back to Ollama, and Anthropic was never reached. When the chain runs out, one WARNING names what failed and why each entry was passed over.
  • Rule and fallback settings are honoured or refused (#584). A rule used to drop everything but provider, model and base URL, so its api_key_env was ignored and the provider's default key used. Rules now pass their settings on. An entry with a key its provider does not read is skipped with a WARNING and shown in oara doctor. See the upgrade notes.
  • router.auxiliary is removed; nothing ever used it (#584).

Telemetry

  • Background model calls are counted (#619). The compactor, memory extractor, skill creator and refiner, curator, knowledge synthesis, teacher, live recorder, LCM summaries, session titles, vision and prompt hooks now record tokens. Totals in /api/usage and /status rise to match.
  • Cache counts are recorded, and unknown ones say so (#610, #619). llama.cpp and Anthropic cache counts are recorded. In /api/usage, cached_input_tokens is null where no row recorded a count, not 0, and the new cache_reported_runs says how many did. Clients should handle null.
  • Anthropic input counts now include cache reads and writes (#619). The cost estimate prices every input token at the full rate, so for cached Anthropic prompts it now reads higher than before.
  • Local rounds are billed as local whatever the model is called (#619). A model whose history spans the upgrade can show billing: "mixed".
  • Rounds record the model that served them (#609, #614, #619):
    • Ollama, Qwen, xAI and Anthropic record it beside the one requested.
    • A round served by the provider fallback is recorded under the fallback's model.
    • A local fallback's calls are never marked golden.
  • Runs with no session of their own are filed under one (#608). POST /api/chat runs are filed under web:<session_id> and subagents under subagent:<parent>:<id>. Failure rows, loop transitions and microcompact rows carry their session (#607, #619).
  • A tool the model calls without it being advertised writes a marker row, not a second "successful" call (#605).
  • Rows written before 0.9.5 are not rewritten. There is no schema change.

Also

  • The model ladder, a benchmark suite and harness for local models, runs from a checkout (#588). Parity-harness additions: a rebaseline mode, a stricter undo rule and a twelfth golden (#612, #613, #616). Audits of skill usage and telemetry gaps are in docs/audits/ (#589, #604).
  • The pre-commit scan checks every staged file, including renames and type changes, and never prints the matched line (#620).

Upgrading from 0.9.4

  • The first oara daemon start repairs lcm.db once (#587). Only the daemon runs this; the CLI chat, --once and oara code do not.

    • No repeated positions (a fresh install, or a store that never had a daemon restart mid-session): it adds the guard and logs, at INFO, LCM turn_index migration: no duplicates; a trigger now refuses a repeated (session_id, turn_index). No backup file is written.
    • Repeated positions:
      • It first copies lcm.db to lcm.db.pre-turn-index-<UTC time>.bak beside it (~/.prometheus/data/ by default). The copy is owner-only (0600) and the same size as lcm.db. It needs that much free space plus 64 MB, or the migration does not run.
      • It then renumbers in one transaction, checks the result and logs a WARNING: LCM turn_index migration: renumbered N rows in S sessions (R runs, largest shift K); … Backup of the previous state: <path> (<bytes> bytes). <seconds>s.
      • On the reference store (18,879 messages, 185 MB) the work took about half a second, plus writing the backup.
      • The backup is never deleted for you. Delete it once you are satisfied: it holds your history as it was, including anything kept before this release's redaction.
    • If it fails, nothing is committed and it logs an ERROR (LCM TURN_INDEX MIGRATION FAILED — nothing was committed; the daemon runs on WITHOUT the turn-index guard, as before.). The daemon keeps running and the next start tries again.
    • Going back to 0.9.4 after this ran: 0.9.4 cannot store a message whose position is already taken: its daemon logs the failed write and stops saving that conversation, and its CLI chat crashes on its first reply. Before downgrading, stop the daemon and run sqlite3 ~/.prometheus/data/lcm.db "DROP TRIGGER lcm_messages_turn_index_guard; PRAGMA user_version = 0;". Starting 0.9.5 again migrates again.
  • skill is in the default advertised tool set (#593), and it reaches the configs setup wrote (#623).

    • Setup no longer writes tools.deferred_loading.always_loaded, so new configs follow the shipped default across upgrades.
    • A list that is exactly one Prometheus shipped as the default follows the current default instead, and each daemon start logs one INFO line saying so. That covers two kinds of config:
      • those that oara setup --fast, --noninteractive or --provider, prometheus-init or Beacon's first-run page wrote from 2026-08-12 until this release;
      • those copied from the template.
    • Any other list is yours and is used exactly as written. It still gets config OVERRIDES a shipped default … THE CONFIG WINS at boot, and oara doctor warns when it leaves out a shipped tool such as skill.
    • To keep an old default deliberately, write the list in a different order, or add or remove a tool. It is then treated as your own.
    • oara doctor and /doctor say which case applies. The list matters only while deferred loading is on, which is the default for local models; cloud providers are shown every tool.
  • learning.gepa_min_traces_required is renamed to learning.gepa_min_loads (#606). The old key counted exported traces and is now ignored. A config that still sets it gets one WARNING at every daemon start, whether GEPA is on or off, and from oara gepa dry-run:
    learning.gepa_min_traces_required: config key is deprecated — it was renamed to learning.gepa_min_loads (WP-X.33) and is IGNORED. … Delete the key; set gepa_min_loads to change the minimum.
    The shipped template set this key (to 10) from April until this release, so configs copied from it have it. The new key counts skill loads, not traces, and defaults to 3.

  • router.auxiliary is removed (#584). Nothing ever routed a task through it, so no provider set there was ever used.

    • A config that still has the block, including the template's empty placeholders, boots normally.
    • Each start logs router.auxiliary: config key is deprecated — nothing ever routed a task through it, so no provider configured there has been used. Delete the block.
    • oara doctor (and /doctor) shows a Router warning naming it. Delete the block.
  • Router rules and fallback entries are checked at load (#584). The daemon still boots, and every other entry loads. An entry is skipped with one WARNING, and listed in the doctor's Router row, if it:

    • carries a key its provider never reads (for example max_tokens or api_key_env on a llama_cpp or ollama entry);
    • names an unknown provider;
    • has no provider;
    • is a rule without task_type or model, or with an unknown task_type.
      The WARNING looks like router.fallback[0] ollama/qwen3 refused: max_tokens is not read for provider ollama (it reads base_url, model, provider, timeout), and it names keys, never values.
      Also:
    • Rules now pass the rest of their provider settings (api_key_env, timeout, …). Before, they were dropped, and the provider's default key was used.
    • A fallback no longer hands back the provider that just failed.
  • Scrub what was kept before 0.9.5.

    1. Stop the daemon.
    2. Run oara scrub (a dry run, counts only).
    3. Run oara scrub --apply.
    4. Start the daemon.
    5. Once you are satisfied, delete the *.pre-scrub-* copies, and the lcm.db.pre-turn-index-*.bak if the migration made one. Both hold the old values.

    It finds the stores where the daemon keeps them, including under PROMETHEUS_CONFIG_DIR and PROMETHEUS_DATA_DIR. If you moved training pairs or trajectory exports in the config, pass --training or --trajectories.

  • The near-duplicate check for auto skills is optional and off until you set it up (#591). Skill creation works without it; the first auto skill in each daemon run logs SkillCreator: near-duplicate check skipped — <reason> and is kept. To turn it on:

    1. Install the runtime into the same environment as oara: pip install -U 'oara-prometheus[skills]'. That is numpy, onnxruntime and tokenizers. [voice] includes them; [full] does not, because it lacks tokenizers.
    2. Fetch the model with the Python of the environment oara is installed in (in a venv, <venv>/bin/python): python -m prometheus.skills.similarity fetch.
      • It downloads BAAI/bge-small-en-v1.5, about 134 MB, from huggingface.co at a pinned revision into ~/.prometheus/models/.
      • It keeps each file only if its size and SHA-256 match the pin, and the daemon checks them again on first use.
    3. Check it: python -m prometheus.skills.similarity verify prints available (exit 0), or unavailable: <reason> (exit 1).
    4. Restart the daemon. It decides once per run.

    On Homebrew the formula carries none of the three packages, so the check stays off.

  • An unregistered local model may move from full to light (#583). If the served chat template says the model calls tools natively, it now gets native tool calling instead of the grammar-constrained text format. To keep the old behaviour, set adapter.model_tiers: {"<name>": full}. Models listed in the registry keep their tier.

  • Ollama thinking models stop thinking on chat turns (#592). See Models and providers above.

  • The Curator stops aging skills by file date (#591). It is on by default and runs weekly. Until loads are recorded, skills it had marked stale or archived by date become active at its next pass, and nothing is pruned.

  • /api/usage can return null. cached_input_tokens is null where nothing was recorded, and the response gains cache_reported_runs (#610). Anthropic input counts now include cache reads and writes (#619).

  • Golden-trace files exported before 0.9.5 are not rewritten (#586). For sessions longer than 500 messages, their context may be wrong. Re-export them if you train on them.

Known issues

  • On Intel Macs, pip builds cryptography from source, because its releases since 49.0 ship no Intel-Mac wheel. That needs Rust and OpenSSL; with Homebrew's OpenSSL, set OPENSSL_DIR=$(brew --prefix openssl@3). On Python 3.14, watchdog also builds from source, which needs Xcode's command-line tools.
  • The new skills extra needs onnxruntime, which ships no Intel-Mac wheel after 1.23.2, and 1.23.2 stops at Python 3.13.

Install

pip install -U oara-prometheus           # the daemon, its web API, the CLI and Telegram
pip install -U 'oara-prometheus[full]'   # adds Slack, Discord, MCP, the browser tool, Anthropic and voice output
brew upgrade oara                        # Homebrew on Apple Silicon; first install: brew install oaralabs/tap/oara

Python 3.11 or later. On PyPI: oara-prometheus 0.9.5.