Skip to main content

Anchor-addressed DOCX editing for LLM agents — a thin client over Docxodus' DocxSession.

Project description

docx-scalpel

Anchor-addressed DOCX editing for LLM agents — a thin client over Docxodus' DocxSession.

docx-scalpel exposes Docxodus' stateful DOCX editor over a long-running .NET subprocess (docxodus-pyhost). The session lives in the host's memory until you explicitly release it, so an LLM agent can issue dozens of small edits against one document without paying the OOXML parse + Unid annotation + projection cost on every call.

Status: 0.1.0a0 — scaffold complete, smoke + lifecycle tests passing against a dev binary. Wheel packaging (cibuildwheel per-RID, bundled self-contained host) is a follow-up branch.

Quick start

from docx_scalpel import open_session, FormatOp, Position

with open("contract.docx", "rb") as f:
    docx_bytes = f.read()

with open_session(docx_bytes) as session:
    # Walk template placeholders and fill them.
    for placeholder in session.find_placeholders():
        session.replace_match(placeholder.match, "filled value")

    # Add a heading after the first body paragraph.
    proj = session.project()
    first_p = next(
        t for t in proj.anchor_index.values()
        if t.kind in ("p", "h") and t.scope == "body"
    )
    session.insert_paragraph(first_p.id, Position.AFTER, "## Reviewed by counsel")

    # Bold the first 8 characters of that paragraph.
    session.apply_format_by_substring(first_p.id, "Reviewed", FormatOp(bold=True))

    new_bytes = session.save()

with open("filled.docx", "wb") as f:
    f.write(new_bytes)

The with block is the documented lifecycle path — it calls session.close() on the way out, which releases the session from the host's SessionRegistry. A __del__ finalizer is a fallback for forgotten sessions but should not be relied on; interpreter shutdown may skip it.

Why a subprocess?

DocxSession holds a parsed WordprocessingDocument, an AnchorIndex of Unid-stamped block-level targets, a cached MarkdownProjection, and a bounded UndoRing of per-part XDocument snapshots. Recreating it costs tens of ms on small docs and seconds on large ones. The subprocess model lets one Python process drive many sessions across many calls, all in one host's memory, until you decide to close them.

Architecture:

Python process                 docxodus-pyhost (.NET 8)
─────────────                  ──────────────────────────
DocxSession  ──NDJSON──>       Dispatcher
                               │
                               ▼
                               DocxSessionOps
                               │
                               ▼
                               SessionRegistry (handle → DocxSession)

One host per Python process. Many sessions inside the host. atexit sends shutdown and (if the host doesn't comply) terminates / kills.

Full design + wire-protocol spec: docs/architecture/python_docxodus.md. Delta-spec for the docx-scalpel rebrand: docs/superpowers/specs/2026-05-26-docx-scalpel-design.md.

Development

Build the host binary (one-time)

# From the Docxodus repo root:
dotnet build tools/python-host/pyhost.csproj -c Release

This produces tools/python-host/bin/Release/net8.0/docxodus-pyhost. _host_locator.py discovers it automatically when you pip install -e . from a monorepo clone.

For non-monorepo development, set DOCXODUS_HOST=/path/to/docxodus-pyhost to override the discovery path.

Editable install + tests

cd python
python -m venv .venv
.venv/bin/pip install -e .[test]
.venv/bin/pytest -v

Test layout

  • tests/test_smoke.py — end-to-end mirror of Docxodus.Tests/DocxSessionSmokeTest.cs. v1 acceptance gate.
  • tests/test_lifecycle.py — proves session persistence, idempotent close, singleton host, finalizer fallback.

Tests share the Docxodus monorepo's TestFiles/ corpus so divergence between Python and .NET on identical inputs is detectable.

API surface

The DocxSession class exposes every op in Docxodus.Internal.DocxSessionOps as a snake-case method:

Tier Methods
Lifecycle save, close, undo, redo
Projection project, project_anchor
Discovery grep, grep_cross_block, find_placeholders, find_by_text, find_all_by_text, find_by_regex, find_by_kind, find_by_annotation, find_by_label, find_by_bookmark, list_annotations, exists, get_anchor_info, get_anchor_infos, get_edit_summary, remaining_placeholders, get_diff
A: text mutations replace_text, replace_text_range, replace_text_at_span, replace_inner, replace_match, delete_block, delete_range, delete_section
B: structural insert_paragraph, split_paragraph, merge_paragraphs
C: formatting apply_format, apply_format_by_substring, set_paragraph_style, set_list_level, remove_list_membership
D: tables replace_cell_content
Raw XML session.raw.get_xml, session.raw.insert_xml, session.raw.replace_xml

Every mutation method returns an EditResult envelope — transport-level failures raise DocxodusTransportError, but a business outcome (anchor_not_found, malformed_markdown, etc.) returns EditResult(success=False, error=EditError(...)). Never an exception across the API boundary.

License

MIT. Built on top of Docxodus, which is itself a fork of Open-Xml-PowerTools.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

docx_scalpel-0.1.0a1.tar.gz (22.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

docx_scalpel-0.1.0a1-py3-none-manylinux_2_28_x86_64.whl (47.7 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64

File details

Details for the file docx_scalpel-0.1.0a1.tar.gz.

File metadata

  • Download URL: docx_scalpel-0.1.0a1.tar.gz
  • Upload date:
  • Size: 22.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for docx_scalpel-0.1.0a1.tar.gz
Algorithm Hash digest
SHA256 f41d39d20ced9f5d441bc8fc57e0cbe7b395de50da98c134984987c08aadda8b
MD5 337c54fc436a0705894d6f9b72e8e11a
BLAKE2b-256 815a3259a872c0cef480e7dacbcf9bfddb48a08d4ee3f816c50cc36122625a89

See more details on using hashes here.

Provenance

The following attestation bundles were made for docx_scalpel-0.1.0a1.tar.gz:

Publisher: python-publish.yml on JSv4/Docxodus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docx_scalpel-0.1.0a1-py3-none-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for docx_scalpel-0.1.0a1-py3-none-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 abf93edbb372f41e5f182fa52160a2ed9e4b99041cd64a43b4854f8279d63343
MD5 5090b04305e96bf2e86752cf8660b9af
BLAKE2b-256 65511ced5c16ba0625f8c69e0d8c251d38a28a994b3b9c53e3f06a07d063bab9

See more details on using hashes here.

Provenance

The following attestation bundles were made for docx_scalpel-0.1.0a1-py3-none-manylinux_2_28_x86_64.whl:

Publisher: python-publish.yml on JSv4/Docxodus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page