chore: remove useless docs, mv some code to better places (#5244)

This commit is contained in:
MaojiaSheng
2026-09-21 14:02:00 +08:00
committed by GitHub
parent 336f2173b4
commit aa77061c14
78 changed files with 48 additions and 1134 deletions
+2 -2
View File
@@ -231,7 +231,7 @@ jobs:
shell: bash
run: |
OPENVIKING_VERSION=$(
uv run --frozen python -c "from build_support.versioning import resolve_openviking_version; print(resolve_openviking_version())"
uv run --frozen python -c "from scripts.build_support.versioning import resolve_openviking_version; print(resolve_openviking_version())"
)
echo "OPENVIKING_VERSION=$OPENVIKING_VERSION" >> "$GITHUB_ENV"
echo "Resolved OpenViking version: $OPENVIKING_VERSION"
@@ -465,7 +465,7 @@ jobs:
shell: bash
run: |
OPENVIKING_VERSION=$(
uv run --frozen python -c "from build_support.versioning import resolve_openviking_version; print(resolve_openviking_version())"
uv run --frozen python -c "from scripts.build_support.versioning import resolve_openviking_version; print(resolve_openviking_version())"
)
echo "OPENVIKING_VERSION=$OPENVIKING_VERSION" >> "$GITHUB_ENV"
echo "Resolved OpenViking version: $OPENVIKING_VERSION"
+1 -1
View File
@@ -59,7 +59,7 @@ jobs:
else
python -m pip install "setuptools-scm>=8.0"
version="$(
python -c "from build_support.versioning import resolve_openviking_version; print(resolve_openviking_version())"
python -c "from scripts.build_support.versioning import resolve_openviking_version; print(resolve_openviking_version())"
)"
fi
if [ -z "${version}" ] || [ "${version}" = "0.0.0" ]; then
+13 -13
View File
@@ -180,7 +180,7 @@ jobs:
echo "cuvs_changed=false" >> $GITHUB_OUTPUT
fi
LANGCHAIN_PATTERN="integrations/langchain/|openviking/integrations/langchain/|tests/(unit/test_langchain|integration/langchain_langgraph/)|examples/langchain-langgraph/|pyproject\.toml|uv\.lock|\.github/workflows/(pr|python-langchain-release)\.yml"
LANGCHAIN_PATTERN="examples/langchain/|openviking/integrations/langchain/|tests/(unit/test_langchain|integration/langchain_langgraph/)|examples/langchain-langgraph/|pyproject\.toml|uv\.lock|\.github/workflows/(pr|python-langchain-release)\.yml"
LANGCHAIN_CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }} HEAD | grep -E "$LANGCHAIN_PATTERN" || true)
if [ -n "$LANGCHAIN_CHANGED_FILES" ]; then
@@ -291,19 +291,19 @@ jobs:
- name: Install standalone integration
run: |
uv pip install --system -e sdk/python
uv pip install --system -e "integrations/langchain[langgraph,test,dev]"
uv pip install --system -e "examples/langchain[langgraph,test,dev]"
- name: Check package formatting and lint
run: |
ruff format --check \
integrations/langchain/src \
integrations/langchain/tests \
examples/langchain/src \
examples/langchain/tests \
openviking/integrations/langchain \
tests/integration/langchain_langgraph \
tests/unit/test_langchain*.py
ruff check \
integrations/langchain/src \
integrations/langchain/tests \
examples/langchain/src \
examples/langchain/tests \
openviking/integrations/langchain \
tests/integration/langchain_langgraph \
tests/unit/test_langchain*.py
@@ -311,8 +311,8 @@ jobs:
- name: Type-check package
run: >-
mypy
--config-file integrations/langchain/pyproject.toml
integrations/langchain/src/langchain_openviking
--config-file examples/langchain/pyproject.toml
examples/langchain/src/langchain_openviking
- name: Run LangChain and LangGraph tests
run: |
@@ -322,13 +322,13 @@ jobs:
- name: Build and inspect distributions
run: |
python -m build integrations/langchain
python -m twine check integrations/langchain/dist/*
python -m build examples/langchain
python -m twine check examples/langchain/dist/*
- name: Verify the installed wheel
if: matrix.python-version == '3.12'
run: |
WHEEL_PATH="${GITHUB_WORKSPACE}/$(find integrations/langchain/dist -name '*.whl' -print -quit)"
WHEEL_PATH="${GITHUB_WORKSPACE}/$(find examples/langchain/dist -name '*.whl' -print -quit)"
uv venv .langchain-wheel-venv --python "${{ matrix.python-version }}"
uv pip install \
--python .langchain-wheel-venv/bin/python \
@@ -336,10 +336,10 @@ jobs:
"${WHEEL_PATH}"
cd /tmp
"${GITHUB_WORKSPACE}/.langchain-wheel-venv/bin/python" \
"${GITHUB_WORKSPACE}/integrations/langchain/tests/installed_base_smoke.py"
"${GITHUB_WORKSPACE}/examples/langchain/tests/installed_base_smoke.py"
uv pip install \
--python "${GITHUB_WORKSPACE}/.langchain-wheel-venv/bin/python" \
"${WHEEL_PATH}[langgraph]"
"${GITHUB_WORKSPACE}/.langchain-wheel-venv/bin/python" \
"${GITHUB_WORKSPACE}/integrations/langchain/tests/installed_smoke.py"
"${GITHUB_WORKSPACE}/examples/langchain/tests/installed_smoke.py"
uv pip check --python "${GITHUB_WORKSPACE}/.langchain-wheel-venv/bin/python"
@@ -50,12 +50,12 @@ jobs:
python-version: "3.12"
- name: Install build dependencies
working-directory: integrations/langchain
working-directory: examples/langchain
run: python -m pip install build setuptools setuptools-scm twine wheel
- name: Resolve package version
id: version
working-directory: integrations/langchain
working-directory: examples/langchain
run: |
PACKAGE_VERSION=$(python -m setuptools_scm)
echo "package_version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT"
@@ -74,18 +74,18 @@ jobs:
fi
- name: Build distributions
working-directory: integrations/langchain
working-directory: examples/langchain
run: python -m build
- name: Validate distributions
working-directory: integrations/langchain
working-directory: examples/langchain
run: python -m twine check dist/*
- name: Upload distributions
uses: actions/upload-artifact@v7
with:
name: langchain-openviking-distributions
path: integrations/langchain/dist/*
path: examples/langchain/dist/*
publish-testpypi:
name: Publish to TestPyPI
+1 -1
View File
@@ -57,7 +57,7 @@ glob = [
# Worktree scratch spaces
'.worktrees/**',
# Build support (C++ profiles, not core logic)
'build_support/**',
'scripts/build_support/**',
]
# ---------------------------------------------------------------------------
+1 -1
View File
@@ -89,7 +89,7 @@ only the contacts relevant to the change.
| Storage | RAGFS, PathLock, QueueFS, and encryption | `openviking/storage`, `openviking/pyagfs`, `openviking/crypto`, `crates/ragfs*` | `@baojun-zhang` |
| Integration | Agent plugins and MCP | `agent-plugins`, memory plugin examples, server MCP | `@t0saki`, `@ZaynJarvis` |
| Integration | VikingBot and agent compilation | `bot`, `ov compile` | `@yeshion23333`, `@fujiajie666` |
| Client | SDKs, CLI, and LangChain | `sdk`, `crates/ov_cli`, `integrations/langchain` | `@zhoujh01`, `@t0saki`, `@ehz0ah` |
| Client | SDKs, CLI, and LangChain | `sdk`, `crates/ov_cli`, `examples/langchain` | `@zhoujh01`, `@t0saki`, `@ehz0ah` |
| Product | Web Studio | `web-studio` | `@yufeng201`, `@ZaynJarvis` |
| Project | Documentation, CI, and plugin releases | `docs`, `.github/workflows` | `@yufeng201`, `@ZaynJarvis` |
+1 -1
View File
@@ -77,7 +77,7 @@ OpenViking 重视聚焦且经过充分理解的改动。无论是否使用 AI
| Storage | RAGFS、PathLock、QueueFS 与加密 | `openviking/storage`、`openviking/pyagfs`、`openviking/crypto`、`crates/ragfs*` | `@baojun-zhang` |
| Integration | Agent Plugin 与 MCP | `agent-plugins`、记忆插件示例、Server MCP | `@t0saki`、`@ZaynJarvis` |
| Integration | VikingBot 与 Agent 编译 | `bot`、`ov compile` | `@yeshion23333`、`@fujiajie666` |
| Client | SDK、CLI 与 LangChain | `sdk`、`crates/ov_cli`、`integrations/langchain` | `@zhoujh01`、`@t0saki`、`@ehz0ah` |
| Client | SDK、CLI 与 LangChain | `sdk`、`crates/ov_cli`、`examples/langchain` | `@zhoujh01`、`@t0saki`、`@ehz0ah` |
| Product | Web Studio | `web-studio` | `@yufeng201`、`@ZaynJarvis` |
| Project | 文档、CI 与 Plugin 发布 | `docs`、`.github/workflows` | `@yufeng201`、`@ZaynJarvis` |
+1 -1
View File
@@ -83,7 +83,7 @@ OpenVikingでは、焦点が絞られ、十分に理解された変更を重視
| Storage | RAGFS、PathLock、QueueFS、暗号化 | `openviking/storage`、`openviking/pyagfs`、`openviking/crypto`、`crates/ragfs*` | `@baojun-zhang` |
| Integration | Agent PluginとMCP | `agent-plugins`、メモリPluginの例、Server MCP | `@t0saki`、`@ZaynJarvis` |
| Integration | VikingBotとAgentコンパイル | `bot`、`ov compile` | `@yeshion23333`、`@fujiajie666` |
| Client | SDK、CLI、LangChain | `sdk`、`crates/ov_cli`、`integrations/langchain` | `@zhoujh01`、`@t0saki`、`@ehz0ah` |
| Client | SDK、CLI、LangChain | `sdk`、`crates/ov_cli`、`examples/langchain` | `@zhoujh01`、`@t0saki`、`@ehz0ah` |
| Product | Web Studio | `web-studio` | `@yufeng201`、`@ZaynJarvis` |
| Project | ドキュメント、CI、Pluginリリース | `docs`、`.github/workflows` | `@yufeng201`、`@ZaynJarvis` |
+3 -3
View File
@@ -53,7 +53,7 @@ WORKDIR /app
# Copy source required for setup.py artifact builds and native extension build.
COPY Cargo.toml Cargo.lock ./
COPY pyproject.toml uv.lock setup.py README.md ./
COPY build_support/ build_support/
COPY scripts/build_support/ scripts/build_support/
COPY bot/ bot/
COPY crates/ crates/
COPY openviking/ openviking/
@@ -113,8 +113,8 @@ COPY --from=py-builder /app/.venv /app/.venv
# Fail the image build if VikingBot and the separately released SDK drift apart.
RUN /app/.venv/bin/python -I -c "import inspect; from importlib.metadata import version; from openviking_sdk.client import AsyncHTTPClient; signature = inspect.signature(AsyncHTTPClient.get_skill); raise SystemExit(0 if 'include_integrity' in signature.parameters else f\"incompatible openviking-sdk {version('openviking-sdk')}: AsyncHTTPClient.get_skill{signature} lacks include_integrity\")"
RUN /app/.venv/bin/python -I -c "from importlib.util import find_spec; from pathlib import Path; spec = find_spec('openviking.web_studio'); locations = list(spec.submodule_search_locations or ()) if spec else []; root = Path('/app/.venv').resolve(); p = (Path(locations[0]).resolve() / 'dist/index.html').resolve() if len(locations) == 1 else None; valid = p is not None and p.is_file() and p.is_relative_to(root); raise SystemExit(0 if valid else f'missing or misplaced Studio bundle: spec_found={spec is not None}, locations={locations!r}, resource={p}')"
COPY docker/openviking-entrypoint.sh /usr/local/bin/openviking-entrypoint
COPY docker/pending_health_server.py /usr/local/bin/openviking-pending-health
COPY deploy/docker/openviking-entrypoint.sh /usr/local/bin/openviking-entrypoint
COPY deploy/docker/pending_health_server.py /usr/local/bin/openviking-pending-health
RUN mkdir -p /app/.openviking \
&& sed -i 's/\r$//' /usr/local/bin/openviking-entrypoint /usr/local/bin/openviking-pending-health \
&& chmod +x /usr/local/bin/openviking-entrypoint /usr/local/bin/openviking-pending-health
@@ -72,7 +72,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
"volcengine>=1.0.216"
COPY examples/cuvs_smoke.py /opt/openviking-cuvs/cuvs_smoke.py
COPY benchmark/cuvs/ /opt/openviking-cuvs-benchmark/
COPY docker/cuvs-dev/entrypoint.sh /usr/local/bin/openviking-cuvs-dev
COPY deploy/docker/cuvs-dev/entrypoint.sh /usr/local/bin/openviking-cuvs-dev
RUN cp -a /opt/openviking-native-engine/. \
/opt/openviking/openviking/storage/vectordb/engine/ \
&& chmod +x /usr/local/bin/openviking-cuvs-dev \
@@ -10,7 +10,7 @@ Build it from the repository root:
```bash
docker build \
-f docker/cuvs-dev/Dockerfile \
-f deploy/docker/cuvs-dev/Dockerfile \
-t openviking-cuvs:dev \
.
```
@@ -22,7 +22,7 @@ driver/runtime policy requires it:
docker build \
--build-arg CUVS_PACKAGE=cuvs-cu12==26.6.0 \
--build-arg 'CUPY_PACKAGE=cupy-cuda12x[ctk]==14.1.1' \
-f docker/cuvs-dev/Dockerfile \
-f deploy/docker/cuvs-dev/Dockerfile \
-t openviking-cuvs:dev-cu12 \
.
```
@@ -64,7 +64,7 @@ For an Enroot/Pyxis environment, export the already-built Docker image once to
a shared SquashFS file:
```bash
docker/cuvs-dev/export-sqsh.sh \
deploy/docker/cuvs-dev/export-sqsh.sh \
openviking-cuvs:dev \
/shared/images/openviking-cuvs-dev.sqsh
```
-1
View File
@@ -1 +0,0 @@
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

-1
View File
@@ -1 +0,0 @@
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -308,11 +308,11 @@ finish an async lifecycle with `recorder.close()`.
The repository includes runnable examples that work without model credentials using an in-memory test client:
```bash
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langchain/rag/quick_app.py
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langchain/context-backend/quick_app.py
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langchain/message-history/quick_app.py
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langgraph/agent/quick_app.py
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langgraph/middleware/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langchain/rag/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langchain/context-backend/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langchain/message-history/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langgraph/agent/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langgraph/middleware/quick_app.py
```
For a real OpenViking server and OpenAI-compatible model flow, see the [live LangGraph app](https://github.com/volcengine/OpenViking/blob/main/examples/langchain-langgraph/langgraph/agent/live_app.py).
@@ -1,112 +0,0 @@
# Shared Temp Upload Timestamp Directory Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Store each shared temporary upload in a timestamp-prefixed directory so cleanup can determine expiry from one root listing without filesystem modification times.
**Architecture:** Generate shared upload IDs as a fixed-width Unix-millisecond timestamp followed by a UUID. Use that ID as the directory name under `viking://upload`, with `content` written before `meta`. Consumers construct both paths directly from `temp_file_id`; cleanup parses timestamps from first-level directory names and removes expired directories recursively.
**Tech Stack:** Python async server, VikingFS, pytest, Markdown documentation.
---
### Task 1: Define directory-layout cleanup behavior
**Files:**
- Modify: `tests/server/test_temp_upload_store.py`
**Step 1: Write the failing test**
Replace the flat-object cleanup test with a test that returns first-level timestamp directories and verifies only expired valid directories are removed recursively without `modTime`.
**Step 2: Run test to verify it fails**
Run: `uv run --active pytest tests/server/test_temp_upload_store.py -q`
Expected: FAIL because cleanup still expects `.content` and `.meta` objects.
**Step 3: Implement minimal cleanup behavior**
Parse a fixed-width millisecond timestamp from directory names, skip malformed entries, and remove only expired directories.
**Step 4: Run test to verify it passes**
Run: `uv run --active pytest tests/server/test_temp_upload_store.py -q`
Expected: PASS.
### Task 2: Switch shared upload save and resolve paths
**Files:**
- Modify: `openviking/server/temp_upload_store.py`
- Modify: `tests/server/test_api_resources.py`
**Step 1: Write the failing test**
Assert uploaded shared files are stored in `viking://upload/<timestamp>-<uuid>/content` and `meta`, while the returned `temp_file_id` stays `shared_<upload_id>`.
**Step 2: Run test to verify it fails**
Run: `uv run --active pytest tests/server/test_api_resources.py -q -k shared_temp_upload`
Expected: FAIL because the implementation writes flat `.content` and `.meta` objects.
**Step 3: Implement minimal storage behavior**
Generate timestamp-prefixed IDs, construct directory child URIs, write content before metadata, and clean up the directory recursively after a partial write failure.
**Step 4: Run test to verify it passes**
Run: `uv run --active pytest tests/server/test_api_resources.py -q -k shared_temp_upload`
Expected: PASS.
### Task 3: Update user-deletion cleanup
**Files:**
- Modify: `openviking/service/user_deletion.py`
- Modify: `tests/server/test_temp_upload_store.py`
**Step 1: Write the failing test**
Make user deletion list upload directories, read each directory’s `meta`, and remove only directories owned by the deleted user.
**Step 2: Run test to verify it fails**
Run: `uv run --active pytest tests/server/test_temp_upload_store.py -q`
Expected: FAIL because deletion still scans root-level `.meta` objects.
**Step 3: Implement minimal deletion behavior**
Read `viking://upload/<upload_id>/meta` for each valid upload directory and recursively remove the owned directory.
**Step 4: Run test to verify it passes**
Run: `uv run --active pytest tests/server/test_temp_upload_store.py -q`
Expected: PASS.
### Task 4: Document and verify the layout
**Files:**
- Modify: `docs/en/api/02-resources.md`
- Modify: `docs/zh/api/02-resources.md`
- Modify: `docs/en/guides/01-configuration.md`
- Modify: `docs/zh/guides/01-configuration.md`
**Step 1: Update documentation**
Describe the timestamp-prefixed directory layout and timestamp-based one-listing cleanup, without referring to object modification times.
**Step 2: Run focused verification**
Run:
```bash
uv run --active pytest tests/test_config_loader.py tests/server/test_temp_upload_store.py -q
uv run --active pytest tests/server/test_api_resources.py -q -k shared_temp_upload
uv run --active ruff check openviking/server/temp_upload_store.py openviking/service/user_deletion.py tests/server/test_temp_upload_store.py tests/server/test_api_resources.py
```
Expected: all commands pass.
@@ -1,114 +0,0 @@
# Memory Overview Lock Coverage Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Ensure each streaming memory update's exact-path batch lease covers the `.overview.md` file regenerated for every upserted or deleted memory directory.
**Architecture:** Extend `_operation_lock_paths` at the point where it still has the logical operation URI set. Derive one sibling `.overview.md` URI per directly mutated memory file, merge those URIs into the existing exact-lock set, then retain the current URI-to-path conversion, deduplication, sorting, lease acquisition, and propagation.
**Tech Stack:** Python 3.10+, pytest, Ruff, VikingFS/RagFS pathlock APIs.
## Global Constraints
- Use exact locks for `.overview.md`; do not replace file locks with directory tree locks.
- Add overview paths only for upsert and delete targets that cause `MemoryUpdater` to regenerate an overview.
- Preserve existing replacement and link endpoint lock coverage.
- Keep empty-directory recursive deletion behavior outside this focused change.
---
### Task 1: Cover derived overview files in the streaming update lease
**Files:**
- Modify: `tests/session/memory/test_streaming_memory_updater.py:406-445`
- Modify: `openviking/session/memory/streaming_memory_updater.py:1840-1864`
**Interfaces:**
- Consumes: `_operation_uri_set(operations: ResolvedOperations | None) -> set[str]` and `_uri_lock_paths(uris: set[str], viking_fs: Any | None, ctx: RequestContext) -> list[str]`.
- Produces: `_operation_lock_paths(operations: ResolvedOperations, viking_fs: Any | None, ctx: RequestContext) -> list[str]` with exact paths for directly mutated files and one `.overview.md` per directly affected parent directory.
- [x] **Step 1: Extend the existing regression expectation**
Update `test_operation_lock_paths_cover_deletes_replacements_and_link_endpoints` so its literal expected list contains:
```python
assert _operation_lock_paths(operations, fs, _ctx()) == [
"/user/u/memories/notes/.overview.md",
"/user/u/memories/notes/deleted.md",
"/user/u/memories/notes/inherited_neighbor.md",
"/user/u/memories/notes/neighbor.md",
"/user/u/memories/notes/replacement.md",
"/user/u/memories/notes/updated.md",
]
```
This catches the production bug where `_operation_lock_paths` omits the sidecar path. Because the fixture contains both an upsert and delete in the same directory, it also proves set-based deduplication produces one overview path while replacement and link endpoint paths remain covered.
- [x] **Step 2: Run the regression test and verify RED**
Run:
```bash
uv run pytest tests/session/memory/test_streaming_memory_updater.py::test_operation_lock_paths_cover_deletes_replacements_and_link_endpoints -q
```
Expected: FAIL because the actual list does not contain `/user/u/memories/notes/.overview.md`.
- [x] **Step 3: Add a distinct-directory regression test**
Add a test that builds one upsert under `notes/` and one delete under `events/2023/02/01/`, then asserts the literal result contains both memory paths and exactly these two derived paths:
```python
[
"/user/u/memories/events/2023/02/01/.overview.md",
"/user/u/memories/events/2023/02/01/deleted.md",
"/user/u/memories/notes/.overview.md",
"/user/u/memories/notes/updated.md",
]
```
- [x] **Step 4: Implement the minimal URI derivation**
In `_operation_lock_paths`, retain the directly mutated URI set separately and add each valid parent overview URI before collecting replacements and link endpoints:
```python
operation_uris = _operation_uri_set(operations)
uris = set(operation_uris)
for uri in operation_uris:
normalized_uri = str(uri).rstrip("/")
directory, separator, _ = normalized_uri.rpartition("/")
if separator and directory:
uris.add(f"{directory}/.overview.md")
```
Do not derive overview paths from `resolved_links`, `delete_replacements`, or inherited link endpoints because those URI categories do not directly drive the overview-generation loop in `MemoryUpdater.apply_operations`.
- [x] **Step 5: Run focused tests and verify GREEN**
Run:
```bash
uv run pytest tests/session/memory/test_streaming_memory_updater.py::test_operation_lock_paths_cover_deletes_replacements_and_link_endpoints tests/session/memory/test_streaming_memory_updater.py::test_operation_lock_paths_cover_each_affected_overview_directory -q
```
Expected: 2 passed.
- [x] **Step 6: Run relevant regression suites and static checks**
Run:
```bash
uv run pytest tests/session/memory/test_streaming_memory_updater.py tests/session/memory/test_memory_updater.py -q
uv run ruff check openviking/session/memory/streaming_memory_updater.py tests/session/memory/test_streaming_memory_updater.py
uv run ruff format --check openviking/session/memory/streaming_memory_updater.py tests/session/memory/test_streaming_memory_updater.py
git diff --check
```
Expected: all tests and checks pass with no warnings introduced by the change.
- [x] **Step 7: Commit the implementation**
```bash
git add openviking/session/memory/streaming_memory_updater.py tests/session/memory/test_streaming_memory_updater.py docs/superpowers/plans/2026-08-10-memory-overview-lock-coverage.md
git commit -m "fix(memory): cover overview files in update leases"
```
@@ -1,268 +0,0 @@
# Memory Link Lock Stabilization Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Ensure a streaming memory update acquires one stable exact-path lease covering relationship endpoints discovered from persisted replacement sources before any mutation begins.
**Architecture:** Add a pre-apply lease helper that acquires the current operation paths, rereads persisted deleted memories under that lease, and releases/reacquires the complete batch whenever new endpoints appear. The helper revalidates after every reacquisition and aborts after three acquisitions, while `MemoryUpdater.apply_operations` continues to receive and release only the final stable lease.
**Tech Stack:** Python 3.10+, asyncio, pytest, Ruff, VikingFS/RagFS PathLock APIs.
## Global Constraints
- Never wait for newly discovered paths while retaining a narrower lease.
- Do not call `MemoryUpdater.apply_operations` until the lock set is stable.
- Preserve exact locks, deterministic path sorting, overview lock coverage, and the existing 300-second acquisition timeout.
- Treat persisted memory content as authoritative only for link endpoint discovery; do not replace extraction output.
- Limit stabilization to three total batch acquisitions and release the current lease before propagating failure.
---
### Task 1: Stabilize authoritative relationship lock coverage before mutation
**Files:**
- Modify: `tests/session/memory/test_streaming_memory_updater.py:43-110,385-470`
- Modify: `openviking/session/memory/streaming_memory_updater.py:450-490,1840-1890`
**Interfaces:**
- Consumes: `_operation_lock_paths(operations, viking_fs, ctx) -> list[str]`, `_uri_lock_paths(uris, viking_fs, ctx) -> list[str]`, `MemoryFileUtils.read(content, uri=uri) -> MemoryFile`, and `ResolvedOperations.delete_replacements`.
- Produces: `_acquire_stable_operation_lease(operations, viking_fs, ctx) -> Any | None`, returning a lease whose requested path set covers every persisted replacement-source link and backlink endpoint observed under that lease, or raising before mutation after three expanding observations.
- [x] **Step 1: Write all failing stabilization tests**
Extend `RecordingPathlockClient` so each acquisition returns a distinct lease while preserving the first lease value expected by existing tests:
```python
lease_number = len([event for event in self.events if event[0] == "acquire"]) + 1
lease_ref = "memory-batch-lease" if lease_number == 1 else f"memory-batch-lease-{lease_number}"
lease = {"lease_ref": lease_ref}
```
Add a `ChangingReadPathlockedInMemoryVikingFS` fake for TOCTOU cases:
```python
class ChangingReadPathlockedInMemoryVikingFS(PathlockedInMemoryVikingFS):
def __init__(self, files, changing_uri, changing_contents):
super().__init__(files)
self.changing_uri = changing_uri
self.changing_contents = list(changing_contents)
self.changing_read_count = 0
async def read_file(self, uri: str, ctx=None):
if uri == self.changing_uri and self.changing_read_count < len(self.changing_contents):
content = self.changing_contents[self.changing_read_count]
self.changing_read_count += 1
self.events.append(("read", uri, self._async_agfs.active_lease))
return content
return await super().read_file(uri, ctx=ctx)
```
Add `test_streaming_memory_updater_reacquires_for_persisted_delete_links_before_writes`. Build a persisted deleted `MemoryFile` whose `links` contains a neighbor absent from the `delete_file_contents` fixture, then call `_apply_operations` and assert:
```python
assert neighbor_path not in first_acquire[1]
assert neighbor_path in second_acquire[1]
assert events.index(first_release) < events.index(second_acquire)
assert all(event[2] == {"lease_ref": "memory-batch-lease-2"} for event in write_events)
assert events.index(second_acquire) < min(events.index(event) for event in write_events)
```
This test catches removal of authoritative persisted-link discovery, failure to release before expansion, and writes performed under the narrow lease.
Add `test_streaming_memory_updater_revalidates_changed_persisted_links`. Return persisted content containing neighbor A on the first read and content containing both A and B on subsequent reads. Assert exactly three acquisitions, both neighbor paths in the third batch, and every write event uses `{"lease_ref": "memory-batch-lease-3"}`.
Add `test_streaming_memory_updater_aborts_after_three_expanding_lock_acquisitions`. Return three successive persisted contents that add neighbors A, B, and C. Assert:
```python
with pytest.raises(RuntimeError, match="after 3 acquisitions"):
await updater._apply_operations(
operations=operations,
request=request,
messages=messages,
)
assert len([event for event in fs.events if event[0] == "acquire"]) == 3
assert len([event for event in fs.events if event[0] == "release"]) == 3
assert fs.writes == []
```
- [x] **Step 2: Run the new tests and verify RED**
Run:
```bash
uv run pytest tests/session/memory/test_streaming_memory_updater.py -k 'reacquires_for_persisted_delete_links_before_writes or revalidates_changed_persisted_links or aborts_after_three_expanding_lock_acquisitions' -q
```
Expected: all three tests FAIL because the current implementation acquires only once and never discovers persisted endpoints before mutation.
- [x] **Step 3: Implement persisted endpoint discovery and stable acquisition**
Add a constant and two module-level helpers near the existing lock-path helpers:
```python
_MEMORY_APPLY_LOCK_MAX_ACQUISITIONS = 3
async def _persisted_replacement_relation_uris(
operations: ResolvedOperations,
viking_fs: Any,
ctx: RequestContext,
) -> set[str]:
uris: set[str] = set()
for deleted_uri in dict(operations.delete_replacements or {}):
try:
content = await viking_fs.read_file(deleted_uri, ctx=ctx)
except (FileNotFoundError, NotFoundError):
continue
if not content:
continue
memory_file = MemoryFileUtils.read(content, uri=deleted_uri)
for link in list(memory_file.links or []) + list(memory_file.backlinks or []):
from_uri = link.get("from_uri") if isinstance(link, dict) else link.from_uri
to_uri = link.get("to_uri") if isinstance(link, dict) else link.to_uri
if from_uri:
uris.add(str(from_uri))
if to_uri:
uris.add(str(to_uri))
return uris
```
Implement `_acquire_stable_operation_lease` with a monotonic `required_paths` set:
```python
async def _acquire_stable_operation_lease(
operations: ResolvedOperations,
viking_fs: Any | None,
ctx: RequestContext,
) -> Any | None:
lock_paths = _operation_lock_paths(operations, viking_fs, ctx)
if not lock_paths:
return None
required_paths = set(lock_paths)
for acquisition in range(1, _MEMORY_APPLY_LOCK_MAX_ACQUISITIONS + 1):
lease = await viking_fs._async_agfs.pathlock_acquire_exact_batch(
sorted(required_paths),
timeout_secs=_MEMORY_APPLY_LOCK_TIMEOUT_SECONDS,
)
try:
relation_uris = await _persisted_replacement_relation_uris(
operations,
viking_fs,
ctx,
)
expanded_paths = required_paths | set(
_uri_lock_paths(relation_uris, viking_fs, ctx)
)
except BaseException:
await viking_fs._async_agfs.pathlock_release(lease)
raise
if expanded_paths == required_paths:
return lease
await viking_fs._async_agfs.pathlock_release(lease)
required_paths = expanded_paths
if acquisition == _MEMORY_APPLY_LOCK_MAX_ACQUISITIONS:
raise RuntimeError(
"Unable to stabilize memory apply lock coverage after "
f"{_MEMORY_APPLY_LOCK_MAX_ACQUISITIONS} acquisitions"
)
raise AssertionError("unreachable")
```
Import `NotFoundError` from `openviking_cli.exceptions`. Catch only
`FileNotFoundError` and `NotFoundError`; parsing and permission failures must
abort before mutation.
Change `_apply_operations` from its direct `pathlock_acquire_exact_batch` call to:
```python
lease = await _acquire_stable_operation_lease(
operations,
viking_fs,
request.ctx,
)
```
Retain the existing final release around `MemoryUpdater.apply_operations`.
- [x] **Step 4: Run the new tests and verify GREEN**
Run:
```bash
uv run pytest tests/session/memory/test_streaming_memory_updater.py -k 'reacquires_for_persisted_delete_links_before_writes or revalidates_changed_persisted_links or aborts_after_three_expanding_lock_acquisitions' -q
```
Expected: 3 passed.
- [x] **Step 5: Run the existing lease regression test**
Run:
```bash
uv run pytest tests/session/memory/test_streaming_memory_updater.py::test_streaming_memory_updater_holds_batch_pathlock_across_apply -q
```
Expected: 1 passed and exactly one acquisition remains for an operation without replacement inheritance.
- [x] **Step 6: Run focused regressions and static checks**
Run:
```bash
uv run pytest tests/session/memory/test_streaming_memory_updater.py tests/session/memory/test_memory_updater.py -q
uv run ruff check openviking/session/memory/streaming_memory_updater.py tests/session/memory/test_streaming_memory_updater.py
uv run ruff format --check openviking/session/memory/streaming_memory_updater.py tests/session/memory/test_streaming_memory_updater.py
git diff --check
```
Expected: all commands exit 0 without new warnings.
- [x] **Step 7: Commit the implementation**
```bash
git add openviking/session/memory/streaming_memory_updater.py tests/session/memory/test_streaming_memory_updater.py docs/superpowers/plans/2026-08-11-memory-link-lock-stabilization.md
git commit -m "fix(memory): stabilize link update lock coverage"
```
---
### Task 2: Lock remapped post-group link endpoints
**Files:**
- Modify: `tests/session/memory/test_streaming_memory_updater.py`
- Modify: `openviking/session/memory/streaming_memory_updater.py:231-272`
**Behavior:** `_apply_post_group_links` must acquire its exact-path batch lease
from the endpoints produced by `remap_stored_links`, not the original request
endpoints. `filter_valid_links` may reduce that set but cannot introduce an
endpoint outside it.
- [x] **Step 1: Add the failing regression test**
Create a grouped-link scenario with original lower-case endpoint URIs and
`result.operations.delete_replacements` mapping them to differently cased
replacement URIs. Use the pathlock-aware VikingFS fake so a write outside the
active lease raises the real coverage-style error. Assert the replacement
paths are acquired and the remapped endpoint files are updated.
- [x] **Step 2: Verify RED**
Run only the new test and confirm it fails because the old implementation locks
the original paths before remapping.
- [x] **Step 3: Implement the ordering fix**
Move `remap_stored_links` before `_uri_lock_paths` in
`_apply_post_group_links`. Keep merge, filtering, result accounting, timeout,
and lease release behavior unchanged.
- [x] **Step 4: Verify GREEN and focused regressions**
Run the new test, the existing post-group link tests, the focused streaming
memory updater suite, Ruff checks for the touched Python files, and
`git diff --check`.
@@ -1,162 +0,0 @@
# SessionCommit Default 8 with Local Override 50 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make unconfigured SessionCommit workers default to 8 while configuring this developer's local OpenViking instance to use 50.
**Architecture:** `QueueWorkersConfig` remains the server-facing configuration source and explicit values continue through `OpenVikingService` into `QueueManager`. Repository fallbacks and documentation use 8; `~/.openviking/ov.conf` supplies a local explicit override of 50 without entering Git.
**Tech Stack:** Python 3.13, Pydantic v2, pytest, Ruff, JSON configuration
## Global Constraints
- Change only the `SessionCommit` queue default; other queue defaults remain 4.
- `examples/ov.conf.example` explicitly shows 8.
- `~/.openviking/ov.conf` explicitly sets 50 and preserves every existing setting.
- Do not restart the running OpenViking service.
---
### Task 1: Repository defaults and documentation
**Files:**
- Modify: `tests/test_config_loader.py:125-146`
- Modify: `tests/storage/test_queue_manager.py:21-24`
- Modify: `openviking_cli/utils/config/queue_worker_config.py:20-29`
- Modify: `openviking/storage/queuefs/queue_manager.py:24-97`
- Modify: `openviking/service/core.py:146-157`
- Modify: `examples/ov.conf.example:85-89`
- Modify: `docs/en/configuration/01-server.md:217-221`
- Modify: `docs/zh/configuration/01-server.md:217-221`
**Interfaces:**
- Consumes: `OpenVikingConfig.from_dict(data: dict) -> OpenVikingConfig` and `QueueManager(..., max_concurrent_session_commit: int = 8)`.
- Produces: an unconfigured `session_commit.max_concurrent` of 8 and preservation of explicit values such as 50.
- [ ] **Step 1: Change the behavior tests to require default 8 and explicit 50**
In `tests/test_config_loader.py`, make the default assertion and explicit override read:
```python
assert config.queue_workers.session_commit.max_concurrent == 8
# In test_queue_worker_concurrency_accepts_separate_values:
"session_commit": {"max_concurrent": 50},
assert config.queue_workers.session_commit.max_concurrent == 50
```
In `tests/storage/test_queue_manager.py`, rename and update the focused test:
```python
def test_session_commit_concurrent_defaults_to_eight() -> None:
manager = QueueManager(agfs=object(), max_concurrent_external_parse=9)
assert manager._max_concurrent_for_queue(manager.SESSION_COMMIT) == 8
```
- [ ] **Step 2: Run the focused tests to verify RED**
Run:
```bash
uv run pytest tests/storage/test_queue_manager.py tests/test_config_loader.py -q --no-cov
```
Expected: the two default-value assertions fail with actual value 50; the explicit value 50 assertion passes.
- [ ] **Step 3: Implement the repository default of 8**
Use 8 for `DEFAULT_MAX_CONCURRENT_SESSION_COMMIT` in `queue_manager.py`, for the `session_commit` default factory in `queue_worker_config.py`, and for `OpenVikingService._init_storage()`'s fallback. Keep the existing explicit-value data flow unchanged.
```python
DEFAULT_MAX_CONCURRENT_SESSION_COMMIT = 8
session_commit: QueueWorkerConfig = Field(
default_factory=lambda: QueueWorkerConfig(max_concurrent=8)
)
max_concurrent_session_commit: int = 8,
```
- [ ] **Step 4: Update repository examples and documentation**
Set `examples/ov.conf.example` to:
```json
"session_commit": {"max_concurrent": 8}
```
Set the English and Chinese `queue_workers.session_commit.max_concurrent` default tables to `8`.
- [ ] **Step 5: Run focused tests and static checks**
Run:
```bash
uv run pytest tests/storage/test_queue_manager.py tests/test_config_loader.py tests/unit/service/test_core_consistency.py -q --no-cov
uv run ruff check openviking/storage/queuefs/queue_manager.py openviking/service/core.py openviking_cli/utils/config/queue_worker_config.py tests/storage/test_queue_manager.py tests/test_config_loader.py tests/unit/service/test_core_consistency.py
uv run ruff format --check openviking/storage/queuefs/queue_manager.py openviking/service/core.py openviking_cli/utils/config/queue_worker_config.py tests/storage/test_queue_manager.py tests/test_config_loader.py tests/unit/service/test_core_consistency.py
git diff --check
```
Expected: 44 tests pass, Ruff reports no errors or formatting changes, and Git reports no whitespace errors.
- [ ] **Step 6: Commit the repository change**
```bash
git add docs/en/configuration/01-server.md docs/zh/configuration/01-server.md examples/ov.conf.example openviking/service/core.py openviking/storage/queuefs/queue_manager.py openviking_cli/utils/config/queue_worker_config.py tests/storage/test_queue_manager.py tests/test_config_loader.py
git commit -m "perf(queue): default session commit concurrency to 8"
```
---
### Task 2: Local OpenViking override
**Files:**
- Modify outside Git: `~/.openviking/ov.conf`
**Interfaces:**
- Consumes: the root JSON object in `~/.openviking/ov.conf`.
- Produces: `queue_workers.session_commit.max_concurrent == 50` while every unrelated JSON value remains identical.
- [ ] **Step 1: Create a protected temporary copy and backup**
Run these exact commands. The first command prevents overwriting an earlier backup:
```bash
test ! -e /tmp/openviking-ovconf-session-commit-50
mkdir /tmp/openviking-ovconf-session-commit-50
cp ~/.openviking/ov.conf /tmp/openviking-ovconf-session-commit-50/ov.conf.edit
cp ~/.openviking/ov.conf /tmp/openviking-ovconf-session-commit-50/ov.conf.backup
```
Do not print either file's full contents.
- [ ] **Step 2: Insert the local override with apply_patch**
Patch the temporary `ov.conf.edit` immediately before the existing root-level `embedding` section:
```json
"queue_workers": {
"session_commit": {"max_concurrent": 50}
},
```
- [ ] **Step 3: Validate the edit before copying it back**
Run:
```bash
/usr/bin/python3 -c 'import copy, json, pathlib; root=pathlib.Path("/tmp/openviking-ovconf-session-commit-50"); before=json.loads((root/"ov.conf.backup").read_text()); after=json.loads((root/"ov.conf.edit").read_text()); expected=copy.deepcopy(before); expected.setdefault("queue_workers", {}).setdefault("session_commit", {})["max_concurrent"]=50; assert after == expected; print("local_config_valid=true")'
```
Expected output: `local_config_valid=true`. This proves unrelated local settings were preserved without printing secrets.
- [ ] **Step 4: Install and verify the local configuration**
Copy the validated temporary edit to `~/.openviking/ov.conf`, preserving the original as `ov.conf.backup` in the temporary directory. Parse the installed file and print only `queue_workers.session_commit.max_concurrent`; expected output is `50`. Do not restart OpenViking.
```bash
cp /tmp/openviking-ovconf-session-commit-50/ov.conf.edit ~/.openviking/ov.conf
/usr/bin/python3 -c 'import json, pathlib; data=json.loads((pathlib.Path.home()/".openviking"/"ov.conf").read_text()); print(data["queue_workers"]["session_commit"]["max_concurrent"])'
```
@@ -1,70 +0,0 @@
# Memory Overview Lock Coverage
## Problem
`StreamingMemoryUpdater` currently acquires one exact-path batch lease for the
memory files, replacement targets, and link endpoints touched by an update. It
passes that lease into `MemoryUpdater`, which later reuses it to write each
affected directory's derived `.overview.md` file.
The overview path is not part of the original lock batch. Rust pathlock coverage
validation therefore rejects the write with `does not cover the requested
operation`, leaving the memory file updated but its overview stale or missing.
## Options
1. Add each affected `.overview.md` path to the original exact-path batch. This
preserves one atomic lease acquisition, serializes updates that share an
overview, and avoids locking unrelated descendants.
2. Acquire a separate exact lock inside `generate_overview`. This holds the
overview lock for less time, but introduces nested acquisition and permits
memory files to change before overview generation obtains its lock.
3. Replace the exact file locks with directory tree locks. This covers every
derived operation but unnecessarily serializes all mutations below a memory
directory.
## Design
Use option 1. Extend `_operation_lock_paths` so every upserted or deleted memory
URI contributes both its own exact path and its parent directory's
`.overview.md` exact path. Continue adding replacement and link endpoint paths
as today; those files do not independently trigger overview generation, so they
do not contribute additional overview paths unless they are also an upsert or
delete target.
Normalize directory URIs by removing trailing slashes before appending
`.overview.md`, convert all URIs through `VikingFS._uri_to_path`, and retain the
existing set-based deduplication and sorted result. Multiple operations in one
directory therefore add only one overview lock.
The resulting batch lease is acquired before any memory mutation and remains
held until `MemoryUpdater.apply_operations` finishes. The existing
`lease_ref=self._transaction_handle` propagation then becomes valid for the
overview write without changes to `generate_overview`.
This change intentionally uses an exact lock for `.overview.md`, not a tree lock
for its directory. It fixes overview write coverage while preserving concurrency
for unrelated files. Recursive removal of an empty memory directory requires a
tree lock and is outside this focused change; its current best-effort behavior
remains unchanged.
## Error Handling
Lock acquisition retains the existing 300-second timeout and all-or-nothing
batch semantics. If the overview path conflicts with another updater, the whole
memory update waits or fails before mutating storage rather than updating memory
and discovering invalid coverage at overview write time.
## Verification
Add regression assertions covering:
- one upsert includes its memory file and sibling `.overview.md` paths;
- multiple operations in the same directory deduplicate the overview path;
- operations in distinct directories include one overview path per directory;
- delete targets contribute their overview paths;
- replacement and link endpoint coverage remains unchanged.
Run the focused streaming-memory-updater tests, the memory-updater overview
tests, formatting/lint checks for touched Python files, and the broader session
memory test suite if focused tests pass.
@@ -1,52 +0,0 @@
# Service Package Circular Import Fix
## Problem
Importing a session training submodule in a fresh Python process currently fails:
```python
from openviking.session.train.components.progress import ProgressSummaryColumn
```
The import enters `openviking.storage.queuefs`, whose `named_queue` module imports
`openviking.service.task_work_index`. Python initializes the parent
`openviking.service` package first. Its eager exports import `resource_service`,
which asks the still-partially-initialized QueueFS package for `QueueManager` and
raises a circular-import error.
## Options
1. Make `openviking.service` exports lazy. This follows the existing patterns in
`openviking.core`, `openviking.storage`, and `openviking.client`, preserves the
public API, and changes only the package entry point.
2. Move `task_work_index` into QueueFS and retain a compatibility shim. This
improves dependency direction but touches many imports and broadens the fix.
3. Defer the imports inside QueueFS methods. This breaks the immediate cycle but
hides the dependency until runtime and is harder to maintain.
## Design
Use option 1. Replace eager imports in `openviking/service/__init__.py` with:
- `TYPE_CHECKING` imports for static analysis;
- a map from each existing public name to its defining module;
- module-level `__getattr__` that imports and caches an export on first access;
- `__dir__` and the unchanged `__all__` list for discoverability and compatibility.
No service class, QueueFS implementation, or benchmark code changes. Existing
imports such as `from openviking.service import OpenVikingService` retain their
behavior, while importing `openviking.service.task_work_index` no longer loads
the whole service layer.
## Verification
Add an isolated-process regression test so prior imports in the pytest process
cannot mask the order-dependent bug. The subprocess will verify, in a fresh
interpreter:
- the LoCoMo-triggering `ProgressSummaryColumn` import;
- direct `QueueManager` and `VikingDBManager` imports;
- existing top-level Service exports and service-submodule imports.
Run the focused regression test, relevant service/QueueFS tests, Ruff, and the
original import command before committing.
@@ -1,98 +0,0 @@
# First Failing Patch Block Diagnostic Design
## Context
`ExtractLoop._validate_patch_operations()` currently applies every SEARCH/REPLACE block in a field as one `StrPatch`. If any later block fails, the validator catches the field-level failure and then reports the first non-empty block instead of the block that actually failed.
This produces misleading repair prompts. In observed traces, an early block matched exactly once while a later `- Values friendship and compassion` block matched twice. The repair prompt identified the valid early block, so the model modified that block and left the duplicate SEARCH unchanged. The second validation failed for the same underlying reason.
## Goal
Report the first block that actually fails in patch execution order, with enough structured information for the repair model to correct it.
The change must preserve:
- `PatchOp` matching and application behavior, including fuzzy fallback;
- sequential block semantics, where an earlier replacement can affect a later SEARCH;
- plain-content validation introduced for rendered Markdown links;
- the existing single repair retry limit;
- existing empty-SEARCH and SEARCH-equals-REPLACE handling;
- cross-file SEARCH diagnostics.
## Selected Approach
Validate each active block sequentially through the existing `PatchOp` implementation.
For each operation field containing a `StrPatch`:
1. Initialize `working_content` from `operation.old_memory_file_content.plain_content()`.
2. Iterate over blocks in output order.
3. Skip blocks that the existing patch implementation treats as inactive, including empty SEARCH and SEARCH equal to REPLACE.
4. Wrap the current block in a one-block `StrPatch` and apply it to `working_content` with `PatchOp.apply()`.
5. If it succeeds and changes the content, retain the returned value as the next `working_content` and continue.
6. If it raises or leaves the content unchanged, report that block as the first actual failure and stop validating the field.
7. If all active blocks apply, return no validation error for the field.
This mirrors real patch execution without duplicating the patch handler's exact, fuzzy, line-based, marker-unescaping, or sequencing behavior inside `ExtractLoop`.
## Error Shape
The existing error fields remain, with these additions:
```json
{
"uri": "viking://user/default/peers/conv-26/memories/profile.md",
"page_id": 1,
"field": "content",
"block_index": 8,
"search": "- Values friendship and compassion",
"reason": "non_unique",
"match_count": 2,
"found_in_other_uris": []
}
```
`block_index` is one-based so it is easy to relate to model output.
`reason` is classified as:
- `non_unique` when the current working content contains the effective SEARCH more than once;
- `not_found` when it contains the effective SEARCH zero times and application makes no change;
- `not_applied` for any other unchanged result;
- `apply_error` for an exception that cannot be classified by the exact match count.
The effective SEARCH used for `match_count` is `unescape_markers(block.search)`, matching the exact-search preprocessing in the patch handler.
`match_count` is calculated against the current sequential `working_content`, not the original file. It is diagnostic metadata; `PatchOp.apply()` remains authoritative for whether a block succeeds.
`found_in_other_uris` continues to compare against each read file's `plain_content()`. It is most useful for `not_found`, but remains present for a stable repair-prompt schema.
## Failure and Retry Behavior
Only the first actual failure for a field is reported because real patch execution cannot reliably evaluate later blocks after an earlier block fails. The repair prompt continues to request a complete regenerated operations object.
The retry policy is unchanged:
- the first failed validation adds the repair instruction and grants one extra iteration;
- a second failed validation is logged but does not grant another repair iteration.
With accurate block diagnostics, the repair model receives the SEARCH it must change instead of an unrelated earlier block.
## Testing
Add focused tests to `TestExtractLoopPatchRepair`:
1. A valid first block followed by a duplicate second block reports the second block, `block_index = 2`, `reason = non_unique`, and `match_count = 2`.
2. The repair response makes the duplicate SEARCH unique and completes after one repair iteration.
3. A missing SEARCH reports `reason = not_found` and `match_count = 0`.
4. Sequential validation applies an earlier successful replacement before checking the next block.
5. All valid blocks complete without a repair retry.
6. Existing Markdown-link plain-content regressions continue to pass.
## Non-Goals
- Changing the number of allowed repair retries.
- Changing prompt wording beyond exposing the new structured fields.
- Reimplementing or simplifying `PatchOp` matching.
- Changing memory storage, rendered links, or link metadata.
- Reporting speculative errors in blocks after the first actual failure.
@@ -1,161 +0,0 @@
# Memory Link Lock Stabilization
## Problem
`StreamingMemoryUpdater` acquires one exact-path batch lease before calling
`MemoryUpdater.apply_operations`. The lock set is computed from the resolved
operations, including the links and backlinks present on
`delete_file_contents`.
When a deleted memory is replaced, `MemoryUpdater` later rereads the persisted
deleted file and inherits its links onto the replacement and neighboring
memories. The persisted file may contain link endpoints that were absent from
the resolved operation object. Those neighbors are therefore outside the
original lease, and RagFS rejects their writes with `pathlock lease ref ...
does not cover the requested operation`.
Lock expansion cannot safely happen inside relation inheritance. Upserts have
already been written by that point, so releasing the original lease there
would expose a partially applied update. Waiting for additional locks while
holding the original lease could also produce a cross-owner circular wait.
## Options
1. Stabilize an exact-path lock set before applying any mutation. Acquire the
initial operation locks, reread persisted replacement sources, calculate the
complete relation endpoint set, and reacquire the complete batch if it grew.
Revalidate after reacquisition and retry if the source relationships changed.
2. Dynamically acquire additional exact locks during relation inheritance with
the original lease as `owner_lease_ref`. This avoids self-conflict, but two
owners can hold disjoint initial locks and wait for each other's expansion.
It also occurs after earlier writes, so failure cannot restore atomicity.
3. Acquire a tree lock for the entire peer memory subtree. This covers every
possible relationship endpoint, but serializes otherwise independent memory
updates and would significantly reduce Locomo commit concurrency.
Use option 1. It preserves exact-lock concurrency and makes the correctness
boundary explicit: no memory mutation starts until one lease covers every path
known from authoritative persisted relationship data.
## Design
Add a focused asynchronous helper in `streaming_memory_updater.py` that returns
the stable lease used by `MemoryUpdater`:
```python
async def _acquire_stable_operation_lease(
operations: ResolvedOperations,
viking_fs: Any,
ctx: RequestContext,
) -> Any | None:
"""Acquire one lease covering authoritative relation endpoints."""
```
The helper performs these steps:
1. Compute the initial exact paths with `_operation_lock_paths` and acquire them
as one batch.
2. While holding that lease, reread every URI in `delete_replacements`. Parse
each persisted file and union all link and backlink endpoints into the
required URI set.
3. If the required lock set is unchanged, return the current lease.
4. If it grew, release the current lease and acquire the full required set in
one all-or-nothing batch. Do not wait for expanded paths while retaining the
narrower lease.
5. Reread the replacement sources after reacquisition and recompute the set. If
the set grew again, release and repeat. Limit stabilization to three total
batch acquisitions; failure raises before `MemoryUpdater.apply_operations`
starts.
The authoritative reread is used only to discover relation endpoints. It does
not replace the resolved operation objects or change the memory content chosen
by extraction. Existing URI conversion, deduplication, deterministic sorting,
overview coverage, and the 300-second batch acquisition timeout remain intact.
`StreamingMemoryUpdater._apply_operations` calls the helper inside its existing
process-local `_apply_lock`, constructs `MemoryUpdater` with the returned lease,
and releases that final lease in its existing `finally` block. No lock lifecycle
logic is added to `_inherit_deleted_link_relations`.
## Concurrency and Consistency
Releasing a narrow lease before acquiring the complete set creates an unlocked
window. The post-acquisition reread closes that TOCTOU gap: mutations proceed
only when the relation endpoint set observed under the final lease is a subset
of that lease's coverage. If another process changes a replacement source in
the window, the changed endpoint set triggers another full-batch acquisition.
Each acquisition requests the complete known set at once. RagFS normalizes and
sorts batch requests deterministically and applies all-or-nothing rollback on
conflict. The updater never waits for an expanded path while retaining a
narrower lease, eliminating the staged A-then-B/B-then-A circular-wait pattern.
The three-acquisition limit prevents an adversarially changing relationship
graph from keeping a commit in an unbounded stabilization loop. On exhaustion,
the helper releases its current lease and the update fails before writing any
memory file. The helper also releases any lease it still owns before propagating
an unexpected discovery or parsing exception.
## Error Handling
- A missing persisted deleted file contributes no additional endpoints; the
existing delete/update path remains responsible for reporting its result.
- Malformed persisted memory content fails lock preparation and aborts the
update before mutation rather than silently applying with incomplete locks.
- Failure to acquire or release a lease propagates through the existing commit
error path.
- Stabilization exhaustion raises an explicit error that includes the number of
acquisitions and does not call `MemoryUpdater.apply_operations`.
## Verification
Add regression coverage for these observable behaviors:
- a persisted replacement source containing a neighbor absent from
`delete_file_contents` causes the final lease to include that neighbor before
any write;
- when the first authoritative reread expands the set, the narrow lease is
released before the complete batch acquisition;
- a source that changes during release and reacquisition is reread and causes a
bounded second stabilization acquisition;
- exhaustion raises before any memory write;
- operations without replacement inheritance retain one acquisition and the
existing lease propagation behavior.
Run the focused streaming memory updater tests, the memory updater regression
tests, Ruff checks for touched files, and `git diff --check`.
## Follow-up: Post-group Link Remapping
Grouped streaming updates have a second link-write path in
`_apply_post_group_links`. All grouped requests have completed before this
method runs, so both inputs that determine the final link endpoints are already
stable: the request's resolved links and the combined result's
`delete_replacements` map.
The current ordering acquires exact locks for the original link endpoints and
then remaps those endpoints. A case-normalizing replacement such as
`entities/person/andrew.md` to `entities/person/Andrew.md` can therefore make
`write_stored_links` write a path outside the lease.
Three approaches were considered:
1. Remap links first, then acquire exact locks for the remapped endpoints.
This is the smallest fix and covers every later write because validation
only filters links; it never changes or adds endpoints.
2. Lock the union of original and remapped endpoints. This is correct but holds
locks that the post-remap validation and write path never use.
3. Acquire the original range and release/reacquire if remapping expands it.
This adds an unnecessary unlocked window and retry lifecycle even though the
complete remapped range is already computable.
Use option 1. `_apply_post_group_links` will merge and remap links before
calling `_uri_lock_paths`. It will then acquire one exact-path batch lease,
filter the remapped links, and write only endpoints already covered by that
lease. No dynamic expansion or tree lock is needed on this path.
Add a regression test whose original links use pre-replacement URIs and whose
combined result maps them to differently cased replacement URIs. The test must
observe that the acquired batch contains the replacement paths and that all
link writes complete under that lease. The test must fail against the old
ordering by surfacing the same lease-coverage rejection seen in Locomo.
@@ -1,23 +0,0 @@
# Plain-Content Patch Validation Design
## Problem
The memory read tool exposes `MemoryFile.plain_content()` to the extraction model, so generated SEARCH text does not contain rendered Markdown links. The extract-loop pre-validation currently applies that SEARCH text to `MemoryFile.content`, which may contain rendered links. Valid patches are therefore rejected before the real updater runs, even though both updater paths already apply patches to `plain_content()`.
## Design
Align pre-validation with the established read and apply contract:
- Use `operation.old_memory_file_content.plain_content()` as the target text passed to `PatchOp.apply`.
- Search other previously read files through `memory_file.plain_content()` when populating `found_in_other_uris`.
- Keep persisted Markdown link rendering and link metadata unchanged. The updater continues to apply patches to plain content and render links during serialization.
No retry policy, patch algorithm, prompt, or storage format changes are included.
## Error Handling
Existing validation behavior remains unchanged: an exception or unchanged result is reported as a repairable patch error. Only the text representation used for validation changes.
## Testing
Add a regression test with stored content containing a rendered Markdown link and a patch whose SEARCH text matches the plain visible form. Assert that pre-validation returns no patch errors. Existing invalid-patch tests continue to prove that genuinely missing SEARCH text still triggers one repair attempt.
@@ -1,24 +0,0 @@
# SessionCommit Default 8 with Local Override 50
## Goal
Change the repository-wide default `SessionCommit` worker concurrency from 4 to 8. Keep the developer's local OpenViking instance at 50 through an explicit `~/.openviking/ov.conf` override.
## Repository behavior
An installation that does not configure `queue_workers.session_commit.max_concurrent` uses 8. The configuration model, `QueueManager` constructor and initializer, and `OpenVikingService` storage fallback all use the same value so different construction paths cannot silently select different limits.
`examples/ov.conf.example` explicitly shows 8, and the English and Chinese server configuration references document 8 as the default. Other queue concurrency defaults remain unchanged.
An explicit configuration value continues to take precedence. Existing validation still rejects zero and negative concurrency values.
## Local behavior
Update only `queue_workers.session_commit.max_concurrent` in `~/.openviking/ov.conf` to 50, preserving all other local configuration. This local override is not committed to the repository. Do not restart the running OpenViking service as part of this change; the new local value takes effect on its next restart.
## Testing
- Verify an empty `OpenVikingConfig` selects 8 for `session_commit` while the other queue defaults remain 4.
- Verify `QueueManager` direct construction selects 8 for `SessionCommit`.
- Verify an explicit `session_commit.max_concurrent` value, including 50, is preserved.
- Run focused configuration, queue-manager, and service consistency tests plus Ruff and whitespace checks.
@@ -292,11 +292,11 @@ commit 策略。如果后续批次或写入后的 commit 失败,`OpenVikingPar
仓库内提供了可直接运行的最小示例,使用内存测试客户端,无需模型凭证:
```bash
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langchain/rag/quick_app.py
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langchain/context-backend/quick_app.py
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langchain/message-history/quick_app.py
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langgraph/agent/quick_app.py
uv run --project integrations/langchain --extra langgraph python examples/langchain-langgraph/langgraph/middleware/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langchain/rag/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langchain/context-backend/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langchain/message-history/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langgraph/agent/quick_app.py
uv run --project examples/langchain --extra langgraph python examples/langchain-langgraph/langgraph/middleware/quick_app.py
```
连接真实 OpenViking 服务和 OpenAI 兼容模型的示例见 [live LangGraph app](https://github.com/volcengine/OpenViking/blob/main/examples/langchain-langgraph/langgraph/agent/live_app.py)。
@@ -6,7 +6,7 @@ from typing import Mapping
SCM_TAG_REGEX = r"^v(?P<version>[0-9]+(?:\.[0-9]+)*)$"
SCM_GIT_DESCRIBE_COMMAND = "git describe --dirty --tags --long --match v[0-9]*"
PROJECT_ROOT = Path(__file__).resolve().parent.parent
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
def _get_scm_version(project_root: Path) -> str:
+2 -2
View File
@@ -22,10 +22,10 @@ if str(SETUP_DIR) not in sys.path:
sys.path.insert(0, str(SETUP_DIR))
get_host_engine_build_config = importlib.import_module(
"build_support.x86_profiles"
"scripts.build_support.x86_profiles"
).get_host_engine_build_config
resolve_openviking_version = importlib.import_module(
"build_support.versioning"
"scripts.build_support.versioning"
).resolve_openviking_version
CMAKE_PATH = shutil.which("cmake") or "cmake"
@@ -50,7 +50,7 @@ def test_build_docker_workflow_does_not_force_zero_version_on_main_builds():
assert "fetch-depth: 0" in workflow
assert "id: openviking-version" in workflow
assert "from build_support.versioning import resolve_openviking_version" in workflow
assert "from scripts.build_support.versioning import resolve_openviking_version" in workflow
assert "OPENVIKING_VERSION=${{ steps.openviking-version.outputs.version }}" in workflow
assert zero_build_arg not in workflow
assert "fallback to 0.0.0" not in workflow
+1 -1
View File
@@ -26,7 +26,7 @@ def test_python_sdk_versioning_uses_sdk_only_at_sign_tags() -> None:
def test_build_support_versioning_uses_main_release_tags_only(monkeypatch) -> None:
from build_support import versioning
from scripts.build_support import versioning
captured_kwargs = {}
fake_setuptools_scm = ModuleType("setuptools_scm")
+1 -1
View File
@@ -1,4 +1,4 @@
from build_support.x86_profiles import get_host_engine_build_config
from scripts.build_support.x86_profiles import get_host_engine_build_config
def test_x86_host_uses_sse3_extension_baseline():