Skip to content

ADR-004: Python and Swift responsibilities

Status: accepted · Date: 2026-09-10

Context

Python has the ecosystem for document parsing, model clients, graph drivers, and reporting. Apple’s Vision and PDFKit APIs are most direct in Swift.

Decision

Python owns settings, contracts, inventory, stage ledger, orchestration, model adapters, embeddings, ranking, graph sync, planning, Anki export, and reports. Swift is a narrow process adapter for PDFKit extraction, page rendering, and Vision OCR, emitting versioned JSON plus artifact paths. Domain scoring and validation remain pure Python.

Mechanics and tests

Swift exits non-zero on malformed input and writes no canonical notes. Python validates every Swift response against a schema, captures stderr as redacted diagnostics, and records tool versions. Contract fixtures test both directions; domain tests run without Swift installed.

Consequences

There is a process boundary and an additional fixture set, but Apple APIs stay isolated and the core remains portable. Revisit Swift only if Apple OCR becomes unavailable or a portable OCR quality gate is met.

Alternatives considered

An all-Swift implementation was rejected because document, graph, and model ecosystems are broader in Python. An all-Python OCR implementation was rejected because Vision/PDFKit provide the native macOS path required by the pilot. A shared mutable library between languages was rejected because it would blur ownership and make failures difficult to reproduce.

Implementation detail and example

The Python boundary sends a PDF path and render settings to the Swift executable and expects versioned JSON containing page index, displayed label, dimensions, regions, text, confidence, and extraction method. Python converts that response into the Extraction contract; it never imports Swift implementation details. Pure functions for hashing, chunking, ranking, mastery, and cycle detection accept typed values and can run with fake adapters.

Configuration and failure behavior

The Swift helper is selected with DEADWIRE_OCR_BIN; rendering starts at 300 DPI, enforces the configured pixel cap, and bounds page concurrency. Non-zero exit, malformed JSON, missing fields, or a tool-version mismatch marks the stage failed and preserves stderr as redacted diagnostics. Python timeouts and retry counts are explicit; no broad exception handler converts an OCR failure into empty text.

Verification and revisit trigger

Use synthetic PDFs for direct text, scanned pages, columns, Unicode, and command tokens. Contract tests validate a known Swift JSON fixture and malformed responses, while domain tests run without Swift. Revisit the split only after a portable OCR implementation meets the same fidelity and command-token gates on the held-out sample.