diff --git a/.claude/logs/agent-2-planning-velt-py-0.1.10.md b/.claude/logs/agent-2-planning-velt-py-0.1.10.md
new file mode 100644
index 00000000..9e1828c7
--- /dev/null
+++ b/.claude/logs/agent-2-planning-velt-py-0.1.10.md
@@ -0,0 +1,45 @@
+# Release Update Plan for velt-py v0.1.10
+
+## Overview
+- Release Type: Patch (typed field additions + sentinel value; no breaking changes)
+- Key Changes: `PartialCommentAnnotation` gains 4 first-class typed fields; new `PartialTargetTextRange` dataclass; `BaseMetadata` now preserves `sdkVersion`/`documentMetadata`; `UNSET` sentinel on `resolvedByUserId`
+- Breaking Changes: No — `extra_fields` pass-through is removed but the promoted fields are now typed; existing call sites that read `extra_fields` will need to switch to named attributes (document as a behavioral note, not a migration step)
+
+## Areas Requiring Updates
+
+### 1. Data Models — SKIP
+Per the v0.1.9 precedent, Python dataclasses must NOT be added to `/api-reference/sdk/models/data-models.mdx`. Document all new types inline on the Python SDK page only.
+
+### 2. API Methods — SKIP
+`/api-reference/sdk/` covers the Web/React SDK. Python-only types must NOT be added there.
+
+### 3. Primary Documentation: `/Users/yoenzhang/Downloads/docs/backend-sdks/python.mdx`
+- File: `/Users/yoenzhang/Downloads/docs/backend-sdks/python.mdx`
+- Changes:
+ - Locate the existing `PartialCommentAnnotation` reference section (imported from `velt_py` in the self-hosting handlers at lines ~270–274). Add or extend a dataclass reference block covering:
+ - `PartialCommentAnnotation` — table of typed fields: `annotationId`, `metadata`, `comments`, plus the four newly promoted fields: `from_` (aliased from `from`; type `dict`), `assignedTo` (`dict | None`), `targetTextRange` (`PartialTargetTextRange | None`), `resolvedByUserId` (`str | None | UNSET`). Note the `UNSET` sentinel meaning vs explicit `None`.
+ - `PartialTargetTextRange` — new dataclass, export path `from velt_py.models import PartialTargetTextRange`. Fields: document whatever the PyPI source exposes (minimally: `startIndex`, `endIndex`, `text` — note to Agent-3: verify exact fields against PyPI 0.1.10 source before publishing; use `str` for all if uncertain).
+ - In the `BaseMetadata` reference block (if one exists on python.mdx — check; if not, add it): note that `sdkVersion` (`str | None`) and `documentMetadata` (`dict | None`) are now preserved in Mongo writes. They were previously silently dropped.
+ - Add a short note under the `resolvedByUserId` field entry explaining the `UNSET` sentinel: "If absent from the incoming payload, `resolvedByUserId` is `UNSET` and Velt skips the Mongo write for that field. An explicit `null` from the frontend (unresolve action) arrives as `None` and writes `null` to Mongo."
+ - Add an import snippet: `from velt_py.models import PartialTargetTextRange, UNSET`
+- Priority: High
+
+### 4. UI Customization / Wireframes / Primitives — N/A
+Backend-only release. No frontend components affected.
+
+### 5. Upgrade Guide — N/A
+No breaking changes requiring migration steps. The `extra_fields` removal is behavioral — access promoted fields by name instead. No separate upgrade-guide.mdx entry needed.
+
+## Implementation Sequence
+1. Update `python.mdx` — add/extend `PartialCommentAnnotation` dataclass reference with 4 new fields, add `PartialTargetTextRange` dataclass reference, update `BaseMetadata` note for `sdkVersion`/`documentMetadata`, add `UNSET` sentinel explanation.
+2. Verify `PartialTargetTextRange` field names against PyPI 0.1.10 before finalizing field table.
+
+## Quality Checklist
+- [ ] `PartialCommentAnnotation` table lists all 7 fields including `from_`, `assignedTo`, `targetTextRange`, `resolvedByUserId`
+- [ ] `PartialTargetTextRange` dataclass documented with export path `from velt_py.models import PartialTargetTextRange`
+- [ ] `UNSET` sentinel explained: absent → no Mongo write; `None` → writes null
+- [ ] `BaseMetadata` note added: `sdkVersion` and `documentMetadata` now preserved
+- [ ] Import snippet `from velt_py.models import PartialTargetTextRange, UNSET` included
+- [ ] No Python types added to `/api-reference/sdk/models/data-models.mdx`
+- [ ] No breaking changes in upgrade-guide.mdx
+- [ ] Log written to `.claude/logs/agent-2-planning-velt-py-0.1.10.md`
diff --git a/.claude/logs/agent-7-qa-velt-py-0.1.10.md b/.claude/logs/agent-7-qa-velt-py-0.1.10.md
new file mode 100644
index 00000000..74db3cbe
--- /dev/null
+++ b/.claude/logs/agent-7-qa-velt-py-0.1.10.md
@@ -0,0 +1,40 @@
+## QA Summary for velt-py v0.1.10
+
+### Issues Found: 0
+
+No corrective changes were required. All touched files are consistent and correct.
+
+### Verification Coverage
+
+**release-notes/version-5/velt-py-changelog.mdx**
+- Section order confirmed: New Features → Improvements → Bug Fixes (no Breaking Changes section)
+- v0.1.10 and v0.1.9 entries both present; v0.1.9 correctly retained as historical record
+- All `[Learn more →]` links resolve to `/backend-sdks/python` (valid path)
+- `from` vs `from_` in changelog prose: accurate — changelog describes the JSON/Mongo key name (`from`); python.mdx clarifies `from_` is the Python alias. Not an inconsistency.
+- "Bug Fixes: None." is unique formatting; acceptable — communicates intent clearly
+
+**release-notes/version-5/sdk-changelog.mdx**
+- No velt-py content present (confirmed clean — v0.1.9 entry correctly removed to velt-py-changelog.mdx)
+
+**docs.json**
+- Python SDK Changelog group nested correctly under Version 5.0.0 alongside sdk-changelog and crdt-core-changelog
+- Page path `release-notes/version-5/velt-py-changelog` confirmed valid
+
+**backend-sdks/python.mdx — Data Models section (lines 3674–3749)**
+- `PartialCommentAnnotation`, `PartialTargetTextRange`, `UNSET`, `BaseMetadata` all documented
+- `#unset-sentinel` anchor: heading "UNSET Sentinel" → slug `unset-sentinel`; cross-reference on line 3698 matches
+- `from_` field note accurately explains Python alias for reserved keyword `from`
+- No `extra_fields` stale terminology present anywhere in the file
+- `BaseMetadata` import note correctly states no import change required
+- JS/TS `PartialCommentAnnotation` in `api-reference/sdk/models/data-models.mdx` is the TypeScript interface — distinct from Python dataclass, no naming conflict
+
+**Repo-wide stale terminology scan (release-notes/, backend-sdks/)**
+- No "Cloud Functions", "Recorder / Player", "Comments / Wireframes", "Added group support" in touched files
+- No `extra_fields` or `pass-through` stale terms surviving in any documentation path
+- `PartialCommentAnnotation` references in `self-host-data/comments.mdx` and `data-models.mdx` refer to the JS/TS type only — correct, no update needed
+
+### Summary
+- Files corrected: 0
+- Critical issues: 0
+- Terminology alignments: 0
+- Agent-6 assessment confirmed: alignment is clean, no propagation needed
diff --git a/backend-sdks/python.mdx b/backend-sdks/python.mdx
index 1c6326c1..ff48faca 100644
--- a/backend-sdks/python.mdx
+++ b/backend-sdks/python.mdx
@@ -3671,6 +3671,97 @@ Common self-hosting error codes returned in the response `errorCode` field:
- `INTERNAL_ERROR` — Server-side error during processing
- `NOT_FOUND` — Requested resource was not found
+## Data Models
+
+Python-SDK-specific dataclasses used by the self-hosting backend. These are not in the shared API reference because they are only relevant to `sdk.selfHosting.*` implementations.
+
+### `PartialCommentAnnotation`
+
+Represents a partial update payload for a comment annotation. Starting in **v0.1.10**, `from`, `assignedTo`, `targetTextRange`, and `resolvedByUserId` are first-class typed fields (previously silent pass-through via `extra_fields`).
+
+```python
+from velt_py.models import (
+ PartialCommentAnnotation,
+ PartialTargetTextRange,
+ PartialUser,
+ PartialComment,
+ BaseMetadata,
+)
+from velt_py.models.comment import UNSET
+
+@dataclass
+class PartialCommentAnnotation:
+ annotationId: str
+ metadata: Optional[BaseMetadata] = None
+ comments: Optional[Dict[str, PartialComment]] = None
+ from_: Optional[PartialUser] = None # 'from' on the wire; from_ avoids Python keyword.
+ assignedTo: Optional[PartialUser] = None
+ targetTextRange: Optional[PartialTargetTextRange] = None
+ resolvedByUserId: Any = UNSET # Tri-state: UNSET | None | str. See below.
+ extra_fields: Optional[Dict[str, Any]] = None # Catch-all for customer-configured custom keys.
+```
+
+**Field notes**
+
+- `from_` — Python alias for the JSON key `from` (reserved keyword). Serialized as `from` in the Mongo document.
+- `assignedTo` — Typed as `PartialUser`. Omit to leave unchanged; the Mongo write skips the field when `None`.
+- `targetTextRange` — Typed `PartialTargetTextRange` for the selected text snippet a comment is anchored to.
+- `resolvedByUserId` — See [UNSET sentinel](#unset-sentinel) for the difference between absent (`UNSET`) and explicit `None`.
+- `extra_fields` — Retained as a catch-all because the frontend contract includes `[key: string]: any` for customer-configured `fieldsToRemove` and `additionalFields`.
+
+### `PartialTargetTextRange`
+
+Anchors a comment to a selected text snippet.
+
+```python
+from velt_py.models import PartialTargetTextRange
+
+@dataclass
+class PartialTargetTextRange:
+ text: str = '' # The selected text content.
+```
+
+### UNSET Sentinel
+
+`UNSET` is a sentinel value exported from `velt_py.models.comment`. It signals that a field was not included in the incoming request payload and should be skipped entirely during the MongoDB write.
+
+```python
+from velt_py.models.comment import UNSET
+
+# Field absent from payload → UNSET → field is NOT written to Mongo
+annotation = PartialCommentAnnotation()
+# annotation.resolvedByUserId is UNSET → Mongo $set will not include this field
+
+# Field explicitly set to None → None → field IS written as null to Mongo
+annotation = PartialCommentAnnotation(resolvedByUserId=None)
+# annotation.resolvedByUserId is None → Mongo $set writes null (frontend unresolve action)
+```
+
+| Value | Payload | Mongo write |
+|-------|---------|-------------|
+| `UNSET` | Field absent | Field skipped — no change |
+| `None` | Field present, value `null` | Field written as `null` |
+
+### `BaseMetadata`
+
+Base metadata attached to every comment annotation. Starting in **v0.1.10**, `sdkVersion` and `documentMetadata` are fully modeled and preserved in MongoDB writes. These fields were previously sent by the frontend on every annotation save but silently dropped.
+
+```python
+@dataclass
+class BaseMetadata:
+ apiKey: Optional[str] = None
+ documentId: Optional[str] = None
+ clientDocumentId: Optional[str] = None
+ organizationId: Optional[str] = None
+ clientOrganizationId: Optional[str] = None
+ folderId: Optional[str] = None
+ veltFolderId: Optional[str] = None
+ documentMetadata: Optional[Dict[str, Any]] = None # New in v0.1.10.
+ sdkVersion: Optional[str] = None # New in v0.1.10.
+```
+
+No import change is required — `BaseMetadata` is populated automatically from the incoming request payload. The fix applies transparently to all `saveComments` calls.
+
## Resources
- [Velt-Py PyPI Package](https://pypi.org/project/velt-py)
diff --git a/docs.json b/docs.json
index ad72d0b2..96884b91 100644
--- a/docs.json
+++ b/docs.json
@@ -1002,6 +1002,12 @@
"pages": [
"release-notes/version-5/crdt-core-changelog"
]
+ },
+ {
+ "group": "Backend SDKs",
+ "pages": [
+ "release-notes/version-5/velt-py-changelog"
+ ]
}
]
},
diff --git a/release-notes/version-5/sdk-changelog.mdx b/release-notes/version-5/sdk-changelog.mdx
index 6727bdab..31c96c4d 100644
--- a/release-notes/version-5/sdk-changelog.mdx
+++ b/release-notes/version-5/sdk-changelog.mdx
@@ -8,7 +8,6 @@ description: Release Notes of changes added to the core Velt SDK
- `@veltdev/react`
- `@veltdev/client`
- `@veltdev/sdk`
-- `velt-py`
@@ -82,25 +81,6 @@ commentElement.on('addCommentDraft').subscribe((event) => {
-
-
-### New Features
-
-- [**REST API Backend**]: The `sdk.api.*` namespace is now available, introducing a REST API backend for the Python SDK with feature parity with the Velt Node SDK. [Learn more →](/backend-sdks/python)
-
-- [**REST API Services**]: 17 services are included: `organizations`, `folders`, `documents`, `users`, `userGroups`, `notifications`, `commentAnnotations`, `activities`, `accessControl`, `crdt`, `presence`, `livestate`, `recordings`, `rewriter`, `gdpr`, `workspace`, and `token`.
-
-- [**Typed Request Dataclasses**]: Every `sdk.api.*` method accepts a typed `@dataclass` request object (e.g., `AddOrganizationsRequest`) and returns the raw Velt API response without local reshaping or validation.
-
-- [**camelCase Method Names**]: All `sdk.api.*` method names are camelCase and match the JavaScript/Node SDK one-to-one.
-
-- [**Custom Error Classes**]: Three new error classes are available: `VeltValidationError`, `VeltTokenError`, and `VeltApiError`, all extending the base `VeltSDKError`.
-
-- [**Environment Variable Support**]: The SDK reads `VELT_API_KEY`, `VELT_AUTH_TOKEN`, `VELT_WORKSPACE_ID`, and `VELT_WORKSPACE_AUTH_TOKEN` from the environment for credential configuration.
-
-- [**PyPI Distribution**]: The package is published to PyPI as `velt-py`. Requires Python 3.8+; the `requests` library is installed automatically as a dependency.
-
-
diff --git a/release-notes/version-5/velt-py-changelog.mdx b/release-notes/version-5/velt-py-changelog.mdx
new file mode 100644
index 00000000..6722eaaf
--- /dev/null
+++ b/release-notes/version-5/velt-py-changelog.mdx
@@ -0,0 +1,47 @@
+---
+title: "Python SDK (velt-py) Changelog"
+description: Release notes for the velt-py Python SDK for self-hosting
+---
+
+### Libraries
+- `velt-py`
+
+
+
+### New Features
+
+- [**Self Hosting**]: `PartialCommentAnnotation` now exposes `from` (comment author), `assignedTo`, `targetTextRange`, and `resolvedByUserId` as first-class typed fields. These four keys previously fell through silently via `extra_fields`. [Learn more →](/backend-sdks/python)
+
+- [**Self Hosting**]: New `PartialTargetTextRange` dataclass (exported from `velt_py.models`) represents the selected text snippet a comment annotation is anchored to. [Learn more →](/backend-sdks/python)
+
+### Improvements
+
+- [**Self Hosting**]: `BaseMetadata` now models `sdkVersion` and `documentMetadata`, which were previously dropped silently; self-hosted Mongo writes now preserve both fields.
+
+- [**Self Hosting**]: Introduced an `UNSET` sentinel on `resolvedByUserId` to distinguish "field absent from payload" (no Mongo write) from "explicit null" (the frontend's unresolve action — writes null to Mongo).
+
+### Bug Fixes
+
+None.
+
+
+
+
+
+### New Features
+
+- [**REST API Backend**]: The `sdk.api.*` namespace is now available, introducing a REST API backend for the Python SDK with feature parity with the Velt Node SDK. [Learn more →](/backend-sdks/python)
+
+- [**REST API Services**]: 17 services are included: `organizations`, `folders`, `documents`, `users`, `userGroups`, `notifications`, `commentAnnotations`, `activities`, `accessControl`, `crdt`, `presence`, `livestate`, `recordings`, `rewriter`, `gdpr`, `workspace`, and `token`.
+
+- [**Typed Request Dataclasses**]: Every `sdk.api.*` method accepts a typed `@dataclass` request object (e.g., `AddOrganizationsRequest`) and returns the raw Velt API response without local reshaping or validation.
+
+- [**camelCase Method Names**]: All `sdk.api.*` method names are camelCase and match the JavaScript/Node SDK one-to-one.
+
+- [**Custom Error Classes**]: Three new error classes are available: `VeltValidationError`, `VeltTokenError`, and `VeltApiError`, all extending the base `VeltSDKError`.
+
+- [**Environment Variable Support**]: The SDK reads `VELT_API_KEY`, `VELT_AUTH_TOKEN`, `VELT_WORKSPACE_ID`, and `VELT_WORKSPACE_AUTH_TOKEN` from the environment for credential configuration.
+
+- [**PyPI Distribution**]: The package is published to PyPI as `velt-py`. Requires Python 3.8+; the `requests` library is installed automatically as a dependency.
+
+