Download Latest Version EverOS 1.5.1 source code.zip (3.3 MB) Google Add to Preferred Sources
Home / v1.5.0
Name Modified Size InfoDownloads / Week
Parent folder
EverOS 1.5.0 source code.tar.gz 2026-10-21 2.7 MB
EverOS 1.5.0 source code.zip 2026-10-21 3.3 MB
README.md 2026-10-21 16.5 kB
Totals: 3 Items   6.0 MB 0

/add no longer has to wait for extraction, and an index can no longer be wrong without saying so. A client can hand a batch to /add with async_mode and get a 202 back in milliseconds, while a durable in-process queue runs extraction in session order; the field, the response body and the task-status paths match EverOS Cloud, so one client works against both. Vector columns now follow [embedding] dim instead of a hardcoded 1024, startup refuses an index the configured model cannot have produced instead of accepting writes that are truncated or searches that quietly return nothing, and an emptied index is reported instead of looking healthy. The rest is reliability: OME writes no longer starve one another on Windows, a session lock timeout no longer strands a memcell without its episode, a message the model can never segment no longer blocks its session, resolving a cluster's members no longer reads two embedding vectors per member, and a dozen smaller faults found by property-based tests are fixed.

Added

  • async_mode on /add: accept now, extract in the background. A request with async_mode: true is written to a durable SQLite queue (memorize_inbox) and answered with 202 Accepted; an in-process worker then runs the unchanged memorize pipeline for each batch, per session in arrival order and several sessions at a time (max_concurrency, default 4), so a session's batches and its flush never overtake each other and one slow session does not hold up the others. A synchronous call on a session that still has queued batches is queued too, for the same reason; /flush has no async_mode of its own and queues only behind such a backlog. The queue is bounded (413 PAYLOAD_TOO_LARGE, 429 QUEUE_SATURATED), a batch whose delivery fails is marked failed and skipped (no automatic retry by default: the pipeline is not atomic, so a re-run is not a clean retry), and every row is deleted seven days after it was accepted. GET /api/v2/tasks/{task_id} reports one request's outcome and GET /api/v2/tasks lists them (filter by session_id / status, paged). The field name, the 202 body, the task paths and the status vocabulary match the EverOS Cloud gateway. Replays after a crash are bounded (max_replays, default 3), the delivery loop relaunches itself if it ever dies, and GET /health reports the queue (memorize_queue block: loop alive, backlog, failed, restarts). Configured under [memorize.async_queue]; enabled = false stops accepting new async work while rows accepted earlier are still delivered. Synchronous calls without a backlog are unchanged.

  • [embedding] dim sets the width of every vector column. It used to be hardcoded at 1024, so a model of any other width could not be used: a narrower one failed every write, a wider one was truncated without saying so. dim now drives the schema, and startup checks the configured model against it. (#434)

  • cascade status and /health tell an emptied index apart from a healthy one. Deleting .index/lancedb used to leave a store that reported perfect health: the ledger lives in .index/sqlite and survives the delete, so every file stayed done, the scanner enqueued nothing, cascade status showed done: 106 with lag: 0, and /health reported zero failures — while search returned nothing. Startup now counts rows per business table and compares them against the ledger. cascade status prints a live count per table and warns when every one of them is empty; /health carries the startup snapshot in cascade.index_rows and cascade.index_emptied. Reported, not enforced: healthy does not flip, because these are startup counts and a signal that cannot clear until the next restart is worse than a number an operator can read. A fresh root, a queue still draining, and a root whose markdown was all deleted are each quiet. Recovery is everos cascade rebuild; markdown is the source of truth and is never touched. (#501, [#522])

Changed

  • Startup refuses an index the configured embedding cannot have produced. Two cases stop a start that used to proceed and fail later: the model returns fewer dimensions than dim (nothing could ever be stored), and the index records a different model than the one configured (distances between vectors from two models are meaningless, and searches just return nothing useful). Both name the values involved and point at everos cascade rebuild; the CLI exits 3 without a traceback. Deployments with no embedding configured (keyword-only, an empty api_key) are unaffected — the check is skipped whole.

  • An index records which embedding model built it. LanceDB keeps it in the episode table's vector column metadata, Milvus as a collection property. An index that predates this release has no such record: the first start after upgrading writes one in place, without touching a row or a column type, and logs that it did. everos cascade rebuild records the model it rebuilt with.

  • /health capabilities.embed reports the startup probe, not whether a provider can be constructed. It used to be true whenever model, api_key and base_url were set, including against a dead endpoint. It is now the result of the one embedding call made at startup, and does not flip back without a restart.

  • Every everos command makes one embedding call at startup to measure the model's real output width, with a 5-second budget and no retries. If it fails, the start continues with a warning and the width check is skipped — the model-change check does not depend on it.

Fixed

  • OME: concurrent run-record writes could starve one another until the busy timeout. Every store write opened its own connection and left the waiting to SQLite's busy handler, which sleeps and retries, so under several writers a late arrival could keep taking the write lock ahead of an early one. On Windows, where a commit costs 30-80 ms, a writer starved that way hit the 5 s timeout and failed with database is locked (measured on CI: 16 writers, p99 3.8 s, one failure in 30 s), which silently drops the run when it lands on the RUNNING insert and leaves the row RUNNING until a restart when it lands on a status update. Writers now queue on an in-process lock that nobody overtakes before touching the file. The engine also keeps one connection open for its lifetime: with a connection per statement, every close used to be the last close, which checkpoints and deletes the WAL files under an exclusive lock and on Windows held a fresh connection's first statement past the same timeout. Found by the OME run state-machine test, which failed on Windows CI.
  • A message the model can never segment no longer blocks its session. Boundary detection retries an unusable reply three times per call; a message that always produced one stayed in the session's buffer, every later /add or /flush merged it back in and failed the same way, and the session was stuck until someone cleared the buffer by hand (the async queue marked that session's batches failed one after another). The third consecutive call that ends this way now moves the session's buffer and the call's own messages to the quarantine track of unprocessed_buffer, answers 422 INVALID_INPUT naming the session and the count, and the next call starts from an empty buffer. Infrastructure failures (no quota, network) are not counted and still propagate as EXTERNAL_SERVICE_UNAVAILABLE, so a buffer waits out an outage.
  • A session-lock timeout could cancel extraction after the memcell was written. memorize() wrapped the whole call in session_lock_timeout_seconds; a timeout that landed after the boundary had written the memcell row and consumed the buffer cancelled the extraction pipelines, and nothing retried them: the cell stayed without an episode for good. The timeout now bounds the two places a call can hang while another waits on the lock, the lock wait and the boundary LLM call, both of which run before any write; once a cell is committed its extraction runs to completion, after the lock is released and shielded from a cancelled request. Found by a property test that lands random concurrent calls under random timeouts.
  • A replayed clustering run inflated the cluster's count. OME delivers a strategy run at least once; when trigger_profile_clustering or trigger_skill_clustering ran again for an episode or case it had already folded in (a crash after the upsert, a retry after the emit failed), it folded it in a second time: the member row was deduplicated but count rose and the centroid was re-weighted, and extract_user_profile gates on that count. An entry already among the owner's members is now left where it is; the downstream event is still emitted.
  • OME inspect ignored the Counter gate's cooldown. The dry run compared the bucket's counter with the threshold only, so during a cooldown window it reported will_run: true for an event dispatch would hold back. The store now answers the dry run with the same rule dispatch applies (threshold and cooldown, one clock).
  • Knowledge: a failed PUT /knowledge/{doc_id} could leave two directories for one document, or bury the old one inside the new. The replace path backs the document's directory up, writes the new one and restores the backup on failure; when the failure came after the writer had created the new directory (the original-file step, for one), the restore moved the backup inside it if the name was unchanged, or left it next to the restored one if the title or category had changed. The half-written directory is now removed before the backup comes back.
  • A hand-edited entry_count could make a daily-log writer reuse an entry id. The episode, atomic-fact, foresight and agent-case writers allocated the next id from the frontmatter's entry_count alone, so lowering it by hand made the next append produce an id the file already held (the index keeps one of the two), and a non-numeric value raised a TypeError. The next id is now allocated from the higher of entry_count and the highest sequence number already in the file, and a frontmatter that disagrees is logged.
  • OME wait_for_event returned while a retry was still pending. The runner records a failed attempt as FAILED before its backoff sleep and gives the retry a fresh run id, so during the backoff the event's only row is FAILED; wait_for_event counted that as terminal and returned. Reflection waits on it before deprecating what the extraction replaced, so it could act on an outcome that was not final. FAILED now means "retry pending"; a failure with no retry left is DEAD_LETTER, as before.
  • to_timestamp_ms is now the exact inverse of from_timestamp_ms. It computed int(timestamp() * 1000), and the float product sat just below the integer for about 0.6 % of instants (1095954883735 came back as 1095954883734); integer arithmetic on the timedelta since the epoch replaces it.
  • The watcher classified md files the scanner never visits. Its kind matcher was anchored on the right only, so a path with extra leading components (x/app/project/users/u/episodes/episode-2026-01-01.md) was accepted and queued while the root-anchored scanner glob ignored it; the match now requires the same number of path components.
  • default_app / default_project are rejected as ids. They are the on-disk directory names of the default scope, so an app with that literal id shared the default app's directory and its index rows were attributed to default. The API now answers 422 for them.
  • Search: a duplicate id inside the dense pool kept its last score, not its highest, and the multi-round decider's JSON parser gave up on a reply with a stray { in the prose before the object (its docstring promised otherwise); it now also decodes from every brace.
  • Cascade worker: a bounded wait between optimize and rebuild cancelled the task it waited on. The optimize runner waits up to 180 s for an in-flight index rebuild of the same kind, and the rebuild loop waits for an in-flight optimize, so the two never commit on one LanceDB manifest at once. Both waits used asyncio.timeout around await task, which cancels the awaited task when the deadline fires: a rebuild longer than the wait, with a write landing on that table meanwhile, was killed mid-flight, and the rebuild loop ended with it, silently, so no periodic rebuild ran again until restart and that table's index UUID / FTS segment count grew unbounded. The mirror wait cancelled the optimize beat it was meant to stay clear of. Both waits now only stop waiting. Present since 1.2.2.
  • Cascade: the handler limit held per drain pass, not per worker. A worker runs at most four handlers of a batch at once, but the limit was created inside each drain pass, so two passes running together each got their own: POST /cascade/quiesce drains while the background loop is still draining, and that overlap peaked at eight concurrent handlers. The limit is now shared by the worker. (#492)
  • Resolving a cluster's members read each member's full row, vectors included. The profile path maps a fresh cluster's episodes back to their parents and needed two fields per member, yet fetched every member's whole episode row with both embedding vectors; a cluster can hold thousands of members, so that read dominated the path's memory. LanceDB now reads only parent_type and parent_id (Milvus still loads full rows), and the reflection path's bulk read skips the vector columns. (#490)
  • method="vector" finds episodes when no atomic fact is in scope. Vector search over episodes recalls through atomic facts, and it returned an empty list (HTTP 200, no log line) whenever none were in scope — fact extraction disabled, not caught up yet, or failing — while keyword search kept answering. It now falls back to each episode's own vector, with radius applied, and logs vector_recall_fact_pool_empty at info. (#502, [#509])

Upgrade

:::bash
pip install --upgrade everos   # or: uv sync

pip install --upgrade everos is enough for most roots, and nothing in this release rewrites or re-embeds anything on its own. The first start records which model built the index and logs one line saying so.

The first start also creates the memorize_inbox table in system.db for the async queue. async_mode is opt-in: clients that never send it get the same synchronous responses as before.

Two deployments need attention:

  • A model narrower than dim — the server now refuses to start instead of accepting writes that could never be stored (this is [#434]). The error names both numbers and the two steps: set [embedding] dim (or EVEROS_EMBEDDING__DIM) to what the model returns, then run everos cascade rebuild — the tables were already created at the old width, so fixing the configuration alone is not enough. Your markdown is untouched throughout.
  • A model wider than dim — unchanged behaviour, but the start now warns that the extra dimensions are truncated. The shipped default is this case (Qwen/Qwen3-Embedding-4B returns 2560, dim is 1024) and nothing is wrong: that model is Matryoshka-trained, 1024 is one of its published truncation points, and the index compares with cosine distance. Set dimensions = 1024 to silence it, if your endpoint accepts that parameter.

Changing dim or model later stops the next start and requires everos cascade rebuild, which re-embeds every entry from markdown — budget for it.

Downgrading to an earlier EverOS is safe as long as dim is still 1024: the older schema check compares column types only and ignores the new record. If you changed dim, set it back and rebuild before downgrading.

Full changelog: v1.4.2...v1.5.0

Source: README.md, updated 2026-10-21