Commit Graph

25 Commits

Author SHA1 Message Date
Dani 4e13f7881e
fix(observability): separate uncalibrated recall scores by name (#368)
Langfuse aggregates scores by name, so one name may only carry values on
one scale. recall_top_score was emitted for every method, mixing HYBRID's
LR-sigmoid probability and AGENTIC's cross-encoder score (both comparable
in [0, 1]) with KEYWORD's unbounded BM25 and single-route VECTOR's cosine.
A chart on that name averaged the two scales, and in practice a keyword
score can read numerically higher than a calibrated one while meaning less.

Uncalibrated methods now report recall_top_score_raw, leaving
recall_top_score comparable across methods and over time. Every recall
score also carries metadata = {method, calibrated}: a structured field
Langfuse persists and can split on, which the free-text comment could not
serve. The comment stays for reading individual scores.

Breaking for anyone charting recall_top_score for keyword search; 1.2.0 is
four days old, so this is the cheapest moment to correct the naming.


Claude-Session: https://claude.ai/code/session_01UyKinsWs1MgoARPoB9R4NW

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 10:30:49 +08:00
Kendrick-Song 42629dfd4d
fix: backfill missing 1.1.4 fixes into main (#366)
* fix(knowledge): contain original-file write path (CWE-22)

The multipart upload filename was joined into ``_original/`` verbatim.
An attacker-controlled filename such as ``/tmp/pwned`` or
``../../.bashrc`` would let ``POST /knowledge/documents`` write outside
the document directory (the ``/`` operator discards the left operand
for absolute paths; ``..`` walks upward). The read side had the
symmetric issue.

Fix, mirroring the sender_id containment shipped in 1.0.1
(GHSA-c795-2g9c-j48m):

- Add ``_safe_original_filename`` reducing the untrusted filename to a
  single POSIX/Windows-basename component; reject degenerate residuals
  (``""``, ``"."``, ``".."``) with PathTraversalError.
- ``_write_original_file`` asserts ``target.resolve()`` stays inside
  ``original_dir.resolve()`` before any filesystem touch (mkdir/write).
- ``_resolve_original_file_path`` sanitises symmetrically so a stored
  provenance label can never resolve to an out-of-directory file.

Four SEC regression tests cover: absolute filename, ``..`` traversal,
degenerate filename rejection, and read-side sanitisation.

Backport from GitLab release/v1.1.4 (commit 40f19de) — 1.1.4 shipped
this fix; the GitLab -> GitHub sync stopped at 1.1.3, so 1.2.0
regressed the containment. This commit alone restores the fix; the
1.2.1 release PR ships it to PyPI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cascade): retry classification, budget, and reconcile races

Backport the cascade reliability work that shipped in GitLab 1.1.4
(MR !49 / commit 95db2f5) — six interlocking changes the reviewer
should read in order:

1. Worker retry classification uses ExternalServiceError (embedding /
   LLM / rerank transient failures) as the "retry inline" signal.
   The legacy RecoverableError hierarchy under cascade/errors.py is
   removed; the retry contract now lives in the domain error tree
   (core/errors.py). Docstrings in handlers/base.py and
   sqlite/tables/md_change_state.py updated to match.

2. Cross-cycle retry budget: _MAX_TOTAL_RETRIES = 12. Once total
   attempts across scanner cycles exhaust the budget, the worker
   marks retryable=False in place instead of looping forever on a
   sustained upstream outage.

3. md_change_state upsert preserves retry_count on scanner
   re-enqueue when mtime is unchanged (previously reset to 0 every
   sweep, defeating the budget). mtime change (user edit) still
   resets the counter.

4. Reconciler no longer re-enqueues pending / processing rows on
   stable mtime — that was overwriting the worker's mark_done. It
   also skips failed rows with retryable=False on stable mtime so
   the entry-check demote path is stable.

5. mtime tolerance (10 ms, MTIME_TOLERANCE_SECONDS) absorbs the
   SQLite REAL float precision loss that previously flapped the
   reconcile decision when the same md was rewritten without a real
   content change. The constant is defined once in the sqlite repo
   and imported by the reconciler so both sides use the same tol.

6. Worker _run_rebuild_once carries an explicit
   `state.task is not asyncio.current_task()` guard before awaiting
   the optimize task — the previous contextlib.suppress was silently
   swallowing self-await RuntimeError.

Kept intact from the GitHub 1.2.0 baseline:
- The `except FileNotFoundError → handle_deleted` branch in the
  worker (delete/modify race — see
  test_modified_event_for_vanished_file_is_processed_as_delete).

Test coverage added:
- test_retry_budget_exhausted_marks_unrecoverable
- test_external_service_error_at_budget_edge_demotes_in_place
- test_upsert_preserves_retry_count_for_failed_stable_mtime
- test_upsert_resets_retry_count_on_mtime_change
- test_optimize_fallback_rebuild_on_sustained_failure
- reconciler mtime-tolerance / stable-mtime skip suite

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(embedding): raise on empty API data; forward MRL dimensions

Backport from GitLab 1.1.4 (MR !49 / commit 95db2f5).

Two behavioural changes on ``OpenAIEmbeddingProvider._embed_chunk``:

1. ``response.data == []`` now raises ``EmbeddingServiceError`` instead
   of returning an empty list. Some upstream providers (observed on
   DeepInfra under load) return HTTP 200 with an empty ``data`` array;
   the silent zero-vector path was corrupting search indexes without
   any signal.

2. When ``[embedding] dimensions = N`` is set in ``everos.toml``, the
   parameter is forwarded to the API so MRL-capable models
   (OpenAI text-embedding-3-*, Qwen3-Embedding, ...) do server-side
   truncation with proper re-normalization. Client-side truncation to
   ``dim`` remains as a fallback for backends that ignore the param.
   ``openai.NOT_GIVEN`` is used as the sentinel so the request omits
   the field when the setting is left at the default ``None``.

Config plumbing:
- ``EmbeddingSettings.dimensions: int | None = None``
- factory forwards ``dimensions=settings.dimensions`` to the provider

The provider stays inside the existing ``memory_span`` OTel wrapper
and continues to report input-only tokens via ``set_generation_usage``
- both are GitHub 1.2.0 native tracing behaviours preserved intact.

Test coverage:
- test_empty_response_data_raises_embedding_error (new)
- test_usage_span._FakeEmbeddings.create signature updated to accept
  ``dimensions`` kwarg so the OTel token-recording tests still exercise
  the same call path

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(extract): retry episode extraction on malformed LLM output

Backport from GitLab 1.1.4 (MR !49 / commit 95db2f5).

The ``/flush`` synchronous path called ``EpisodeExtractor.aextract``
exactly once. everalgo raises ``ValueError`` when the LLM returns
malformed JSON (observed with OpenRouter partial responses where
finish_reason=stop but the body is truncated) — the caller was
surfaced a 500 for a transient upstream hiccup.

``_extract_with_retry`` wraps the call with two extra attempts at 1s
and 2s backoff (final attempt propagates untouched), typed as
``AlgoEpisode`` so the caller path stays annotated. Retry stays inside
the existing GitHub 1.2.0 ``memory_span("everos.extract", ...)`` OTel
wrapper — the OTel token capture and the retry loop are orthogonal.

TODO in the code notes we should catch a typed everalgo
``ExtractionError`` once that type is introduced (currently ValueError
is broad).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: log non-stop finish_reason; bump everalgo-user-memory 0.3.2

Two loosely coupled changes from GitLab 1.1.4 (MR !49 / commit 95db2f5)
that arrive together as a housekeeping commit.

1. ``_LoggingLLMClient`` diagnostic wrapper (new, file-private).

   Wraps the raw everalgo LLM client and, on every ``chat()``, warns
   when ``resp.finish_reason != "stop"`` — logging the reason,
   ``content_len``, the last 200 chars of ``content``, and ``model``.
   Aims at OpenRouter/DeepSeek truncation triage where the provider
   silently caps output length and returns finish_reason=length /
   filter / etc. Non-invasive: one branch per call, no config gate.

   Wrapper stack in ``get_llm_client``:

       LoggingLLMClient(UsageRecordingClient(build_client(...)))
       LoggingLLMClient(build_client(...))    # observability off

   ``UsageRecordingClient`` (GitHub 1.2.0 native OTel token capture)
   stays gated by ``settings.observability.enabled`` — this commit
   preserves that. LoggingLLMClient is always outermost so the reason
   it observes is exactly the reason the underlying provider reported.

2. ``everalgo-user-memory`` 0.3.1 -> 0.3.2 (pyproject + uv.lock).

   Same bump the GitLab 1.1.4 release lane took; unblocks the
   episode-extract retry work in commit 4 seeing the upstream
   improvements. Verified via ``uv sync``.

No functional API changes.

Test coverage:
- test_returns_singleton_when_configured now asserts the outer
  LoggingLLMClient wrapper.
- test_wraps_client_when_observability_enabled asserts the two-layer
  Logging(UsageRecording(...)) stack.
- test_does_not_wrap_client_when_observability_disabled asserts
  Logging still wraps when tracing is off.
- test_logging_wrapper_warns_on_non_stop_finish_reason (new).
- test_logging_wrapper_silent_on_stop_finish_reason (new).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(changelog): restore [1.1.4] section to match published sdist

The 1.1.4 changelog entry on this branch previously listed three
items (Langfuse example, delete/modify race, live-server telemetry).
The 1.1.4 sdist on PyPI, however, was built from the internal
release lane and includes the CWE-22 containment, the cascade
retry-budget / mtime-tolerance / reconcile-guard work, the embedding
empty-data raise, the episode-extract retry, MRL dimensions, and the
LLM finish_reason diagnostic — none of which were represented here
when the tag was cut.

Rewrite the [1.1.4] section so it matches the wheel a user actually
installs from PyPI:

- Add a header note explaining the retroactive restoration.
- Fixed: CWE-22, cascade reliability bundle, delete/modify race
  (unchanged wording), embedding empty-data, episode extract retry,
  Langfuse live-server telemetry (unchanged wording).
- Added: MRL dimensions, LLM finish_reason diagnostic, Langfuse
  example (unchanged wording).
- Changed: everalgo-user-memory 0.3.1 -> 0.3.2.

The GitLab-side `.gitlab-ci.yml` in-house-runner entry is dropped —
open-source CI runs on GitHub Actions and the internal runner switch
is not visible to public users.

Date stays 2026-07-20 (the GitHub v1.1.4 tag date / PyPI upload
timestamp) rather than the internal 2026-07-23 code-freeze date, so
the timeline of what shipped where remains internally consistent.

The corresponding code fixes are all backported by earlier commits
in this PR; this commit only aligns the changelog surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-28 14:57:53 +08:00
zhanghui 25964ccbb3 fix(observability): don't spawn a root trace per cascade embedding
The embedding-observation change wrapped every _embed_chunk call in a
span. Cascade-time indexing embeds run outside any request trace, so each
chunk started its OWN root trace — a per-chunk trace explosion (13 orphan
everos.embedding traces per add/flush), detached from session/user and
contrary to the "cascade is not instrumented" decision.

memory_span gains nested_only: open a span only when one is already
active. Embedding uses it, so search/flush embeds still nest under their
recall/extract span, while cascade embeds no-op (no trace) — restoring the
cascade-untraced boundary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:29:54 +08:00
zhanghui f20a123805 fix(observability): correct span typing and telemetry shape
Follow-up to the live-trace audit — all on the enabled path:

- boundary detection LLM (everalgo detect_boundaries) ran with the
  SPAN-typed request root as the current span, so its ~1.2k tokens were
  dropped from cost. Wrap it in an everos.memcell.boundary GENERATION
  span so Langfuse prices it.
- embedding calls stamped usage on the enclosing retriever span. Wrap
  each /embeddings call in an everos.embedding EMBEDDING span so the type
  is correct and pricing can apply.
- agentic recall emitted a duplicate, same-name everos.search.recall
  (cluster_scoped wrapping hybrid_full, which owns the real recall span).
  Drop the redundant outer span; hybrid_full keeps the one recall span
  (also used standalone in round 2).
- search now captures the returned hit ids (episodes/cases/skills) as
  observation output when capture_content is on — previously only the
  query input was captured.
- add/flush spans carry request_id in metadata.
- persist captures the memory-root-relative .md path, not the host
  absolute path (no host layout leak to the telemetry backend).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:29:54 +08:00
zhanghui 0cbfb9f854 fix(observability): address OTel review findings
Adversarial review of the merged OTel instrumentation (#352) surfaced
four issues, all on the enabled path (default-off, so no impact until
tracing is turned on):

- search LLM client was unwrapped, so hybrid/agentic token usage — the
  heaviest LLM spend — never reached Langfuse. Wrap it with
  UsageRecordingClient when observability is enabled, mirroring
  get_llm_client(); graceful keyword-only degradation is preserved.
- set_generation_usage overwrote token counts, undercounting any span
  that wraps more than one chat call (the now-wrapped agentic path).
  Accumulate instead of replacing.
- recall_hit was emitted for uncalibrated methods (unbounded BM25 /
  single-route vector), a near-constant always-hit signal that inflates
  dashboards. Gate hit on calibrated methods (HYBRID/AGENTIC); keyword
  and vector emit only the raw top_score.
- init_tracing / init_score_sink were not idempotent — a re-init without
  an intervening shutdown orphaned the export thread + OTLP socket +
  worker task. Tear down the previous instance first (init_score_sink is
  now async).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:29:54 +08:00
zhanghui 21b845bf88 feat(api): serve endpoints under /api/v2, retain /api/v1 as alias
Every business endpoint (memory/*, ome/*, knowledge/*) is now served
under /api/v2, aligning the open-source API with the EverOS Cloud
contract. /api/v1 is retained as a permanent, backward-compatible alias:
the same router objects are mounted under both prefixes, so both resolve
to identical handlers and request/response contracts. Existing /api/v1
integrations keep working unchanged. Infra endpoints (/health, /metrics)
stay unversioned.

Fix the Prometheus request-metric label to build the path from the full
request URL (with path params folded) rather than the route's
router-relative path, so the version prefix is preserved and v1/v2
traffic stays distinguishable.

Docs (docs/api.md, docs/openapi.json), CHANGELOG, and route docstrings
updated to lead with /api/v2. Add test_api_versioning as the parity
guard: every v2 route has an identical v1 twin and vice versa.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 13:44:58 +08:00
zhanghui 357f619c64 feat(observability): instrument memory ops + OME trace linking
Open spans at the memory hot paths (all no-op when tracing is off):

- add / flush (service.memorize), extract + persist.markdown (user pipeline).
- search: everos.memory.search retriever + a uniform recall / rank
  decomposition across keyword / vector / hybrid / agentic (manager, agentic
  modules, cross-encoder callbacks); query-embedding tokens land on recall.
- recall quality: top_score / hit on the search span, plus recall_top_score /
  recall_hit pushed to Langfuse scores via the bounded-queue sink (method
  tagged; off the request path).
- OME: everos.ome.<strategy> agent span + everos.reflect.consolidate
  generation; a W3C traceparent captured at enqueue is threaded through the
  APScheduler job and re-attached in the Runner, so strategies fanned out
  from a request nest under that request's trace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:47:39 +08:00
zhanghui bd26f5a80f feat(observability): capture LLM + embedding token usage
Surface gen_ai.* model + token attributes onto the active span so Langfuse
can compute cost — without touching everalgo:

- UsageRecordingClient wraps the LLM client and records response.usage after
  each chat(); get_llm_client composes it over the existing _LoggingLLMClient
  only when observability is enabled (disabled default stays overhead-free).
- OpenAIEmbeddingProvider records its response.usage (input tokens) onto the
  active span too.

Tokens land on the everos.extract / everos.reflect.consolidate generation
spans and the search embedding recall; no-op when tracing is off.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:47:39 +08:00
zhanghui ab5cf0447e feat(observability): OTel tracing chassis + request-id propagation
Optional, off-by-default native OpenTelemetry export (the [otel] extra),
wired as chassis so call sites never branch on config:

- ObservabilitySettings ([observability]) — enabled / endpoint / headers /
  sample_rate / capture_content / langfuse_* / recall_hit_threshold; langfuse
  creds derive the OTLP endpoint + Basic-auth header.
- core.observability.tracing: TracerProvider lifecycle (module-local, no-op
  when off/absent), memory_span helper stamping the langfuse.* contract,
  set_generation_usage, privacy-gated capture_input/output (redaction +
  truncation), non-blocking recall-score sink, W3C traceparent in/out helpers.
- TracingLifespanProvider builds provider + score sink at startup.
- RequestIdMiddleware: per-request id (state + contextvar + structlog +
  X-Request-Id) and continues an upstream traceparent when present; managers
  read the propagated id via resolve_request_id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:47:39 +08:00
Elliot Chen 02dae05cbc
chore(release): update EverOS to 1.1.4 (#348) 2026-07-23 13:18:19 +08:00
zhanghui 45656d331e
fix(lancedb): fix FTS with_position optimize crash + disk bloat (#336)
FTS indexes built with with_position=True crash lance's optimize/compaction
on lancedb >= 0.32 when merging an unindexed tail (Max offset exceeds length
of values; upstream lance-format/lance#7653). The crash aborts optimize()
including version cleanup, so the index dir grows unbounded until the disk
fills. everos recall is OR-mode BM25 and never does phrase queries, so
positions are never read -- disabling is lossless.

- base: default with_position=False
- infra: migrate_fts_indexes() rebuilds pre-fix indexes once at startup + reclaims orphans
- cascade worker: count consecutive optimize failures, escalate warning->error

Fixes #335.

Co-authored-by: zhanghui <zhanghui@shanda.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 16:54:58 +08:00
Kendrick-Song 56ee9c8e5c
fix(search): scope deprecated_by filter to user tables only (#330)
compile_filters() unconditionally appended 'deprecated_by IS NULL' to
every query, but the deprecated_by column only exists on user-scoped
tables (episode, atomic_fact — Reflection V1). Agent tables
(agent_case, agent_skill) lack this column, causing a SQL error on
agent search/get queries.

Gate the clause behind owner_type == 'user' so agent queries no longer
reference a non-existent column.

Bump version to 1.1.2.

Co-authored-by: Jiayao Song <jiayao.song@shanda.com>
2026-07-08 11:17:01 +08:00
Elliot Chen 15efd1198c
chore(release): update EverOS to 1.1.1 (#327) 2026-07-07 18:30:03 +08:00
Elliot Chen 0341f1230f
docs: align config and github workflow (#314)
* docs: align config and positioning copy

* docs: remove internal branch workflow residue

* docs: record github sync guard

* chore: retrigger commit lint

* docs: leave readme files unchanged

* docs: remove public positioning comparison
2026-06-29 07:31:31 +08:00
Elliot Chen 0df88f5603
chore(release): update EverOS to 1.1.0 (#307) 2026-06-24 23:17:23 +08:00
Elliot Chen 1ea44ca548
feat(cli): add live demo mode (#302) 2026-06-23 20:11:23 +08:00
Elliot Chen 32cb22343d
feat(cli): add EverOS demo TUI (#298)
* feat: add EverOS demo TUI

* docs: add EverOS demo quick start

* style(cli): elevate EverOS demo TUI

* style(cli): align demo palette with poster

* fix(cli): round demo sphere animation loop

* fix(cli): refine EverOS demo TUI layout

* fix(cli): use uniform demo sphere dots

* fix(cli): refine EverOS demo sphere media

* fix(docs): smooth EverOS demo animation

* fix(docs): raise demo animation frame rate

* docs(readme): note external demo animation hosting

* fix(docs): optimize demo animation size

* feat(cli): add demo confetti success state

* feat(cli): make demo playable

* test(cli): normalize demo help ansi output

* docs(readme): streamline demo and roadmap sections

* docs(readme): restore star history section

* refactor(tui): move demo implementation out of cli
2026-06-23 18:33:44 +08:00
Yangtze-Seventh dc02b2fbab
feat(rerank): add Bailian DashScope provider support (#295) 2026-06-17 20:19:42 +08:00
Elliot Chen 9c7c9d7316
docs: make quick start prove first memory (#293)
* docs: make quick start prove first memory

* docs: add env example for quick start

* docs: highlight quick start success moment

* docs: move use cases after quick start

* docs: align quickstart response contracts
2026-06-17 13:15:59 +08:00
Elliot Chen a10cdcd197
chore(release): prepare EverOS 1.0.1 (#290) 2026-06-16 21:46:17 +08:00
Elliot Chen 79b3df4de2
docs(readme): polish launch highlights and banner (#261)
* docs: simplify README launch highlights

* docs(readme): use six launch highlights

* docs(readme): use optimized banner asset

* ci: lint pull request titles
2026-06-06 19:49:59 +08:00
Elliot Chen 00f1dfaec5
chore: finalize repo audit hygiene (#257) 2026-06-06 13:59:12 +08:00
Elliot Chen ab23e40b28
ci: block repository media assets (#256)
* ci: block repository media assets

* test: stabilize cascade scanner loop test
2026-06-06 11:44:45 +08:00
Elliot Chen 873e7535fb
ci: harden contributor checks (#254)
* ci: harden contributor checks

* ci: pin setup-uv action release

* ci: split workflow checks

* docs: clarify required checks
2026-06-06 10:47:16 +08:00
Elliot Chen 518b8eca85 chore: initialize EverOS 1.0.0
md-first memory extraction framework for AI agents.

Markdown is the single source of truth; SQLite holds state and LanceDB
provides the rebuildable vector + BM25 + scalar index. The codebase follows
a single-direction DDD layering (entrypoints -> service -> memory -> infra,
with component / core / config cross-cutting) enforced by import-linter.

Engineering surface:
- Coding conventions in .claude/rules/ (path-scoped) and workflows in
  .claude/skills/ (/commit, /new-branch, /pr).
- GitHub Actions CI runs make lint + test + integration; pre-commit mirrors
  the gates locally (ruff, hygiene hooks, gitlint commit-msg).
- Commit messages follow Conventional Commits, enforced by gitlint.
- make lint also enforces datetime two-zone discipline and OpenAPI drift.
2026-06-06 07:33:17 +08:00