| Name | Modified | Size | Downloads / 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_modeon/add: accept now, extract in the background. A request withasync_mode: trueis written to a durable SQLite queue (memorize_inbox) and answered with202 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;/flushhas noasync_modeof 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 markedfailedand 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 andGET /api/v2/taskslists them (filter bysession_id/status, paged). The field name, the202body, 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, andGET /healthreports the queue (memorize_queueblock: loop alive, backlog, failed, restarts). Configured under[memorize.async_queue];enabled = falsestops accepting new async work while rows accepted earlier are still delivered. Synchronous calls without a backlog are unchanged. -
[embedding] dimsets 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.dimnow drives the schema, and startup checks the configured model against it. (#434) -
cascade statusand/healthtell an emptied index apart from a healthy one. Deleting.index/lancedbused to leave a store that reported perfect health: the ledger lives in.index/sqliteand survives the delete, so every file stayeddone, the scanner enqueued nothing,cascade statusshoweddone: 106withlag: 0, and/healthreported zero failures — while search returned nothing. Startup now counts rows per business table and compares them against the ledger.cascade statusprints a live count per table and warns when every one of them is empty;/healthcarries the startup snapshot incascade.index_rowsandcascade.index_emptied. Reported, not enforced:healthydoes 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 iseveros 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 ateveros cascade rebuild; the CLI exits 3 without a traceback. Deployments with no embedding configured (keyword-only, an emptyapi_key) are unaffected — the check is skipped whole. -
An index records which embedding model built it. LanceDB keeps it in the
episodetable'svectorcolumn 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 rebuildrecords the model it rebuilt with. -
/healthcapabilities.embedreports the startup probe, not whether a provider can be constructed. It used to be true whenevermodel,api_keyandbase_urlwere 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
everoscommand 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
/addor/flushmerged 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 batchesfailedone after another). The third consecutive call that ends this way now moves the session's buffer and the call's own messages to thequarantinetrack ofunprocessed_buffer, answers422 INVALID_INPUTnaming 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 asEXTERNAL_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 insession_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_clusteringortrigger_skill_clusteringran 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 butcountrose and the centroid was re-weighted, andextract_user_profilegates on that count. An entry already among the owner's members is now left where it is; the downstream event is still emitted. - OME
inspectignored the Counter gate's cooldown. The dry run compared the bucket's counter with the threshold only, so during a cooldown window it reportedwill_run: truefor an eventdispatchwould hold back. The store now answers the dry run with the same ruledispatchapplies (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_countcould 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'sentry_countalone, 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 aTypeError. The next id is now allocated from the higher ofentry_countand the highest sequence number already in the file, and a frontmatter that disagrees is logged. - OME
wait_for_eventreturned while a retry was still pending. The runner records a failed attempt asFAILEDbefore its backoff sleep and gives the retry a fresh run id, so during the backoff the event's only row isFAILED;wait_for_eventcounted 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.FAILEDnow means "retry pending"; a failure with no retry left isDEAD_LETTER, as before. to_timestamp_msis now the exact inverse offrom_timestamp_ms. It computedint(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_projectare 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 todefault. The API now answers422for 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.timeoutaroundawait 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/quiescedrains 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_typeandparent_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, withradiusapplied, and logsvector_recall_fact_pool_emptyat 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(orEVEROS_EMBEDDING__DIM) to what the model returns, then runeveros 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-4Breturns 2560,dimis 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. Setdimensions = 1024to 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