Session-Engine Probe Memo#

myform.Session is the smallest reusable in-process boundary for a future repeated caller. It is not a daemon, transport API, console command, background service, or LSP.

1. Decision#

Session(config=..., target_path=...) first normalizes every configuration input to MyformConfig, then takes an independent Pydantic deep snapshot that preserves model_fields_set. That keeps nested compatibility mappings from aliasing callers while retaining the explicit-section provenance that drives non-fatal applicability warnings.

The public readers, writers, lowering registries, extension identities, and potentially stateful writer objects are process-global. One fail-fast process-wide guard therefore spans Session construction and extension registration plus every Session or disposable public conversion. Concurrent work must be serialized by the caller; creating separate concurrent Sessions is not safe advice. A failed setup or conversion releases the guard for a later sequential call.

The existing myform.convert* functions construct a disposable Session, so the typed reader -> degradation -> writer pipeline has one implementation. convert, convert_with_report, and format preserve the public one-shot contract while accepting explicit per-call options.

2. Launcher-Side Harness Data#

Note

The following numbers are launcher-side harness data, not a portable performance guarantee. They use the benchmark’s default MyST-to-Typst fixture with ten samples per path. Both disposable workers exited with status 0.

Path

Median latency

Evidence

Cold public conversion

250.105 ms

Launcher-side harness, 10 samples

Warm public convert

0.974 ms

Launcher-side harness, 10 samples

Warm Session conversion

0.436 ms

Launcher-side harness, 10 samples

Persistent stdio NDJSON

0.531 ms

Launcher-side harness, 10 samples; worker exit 0

Disposable Unix-socket NDJSON

0.579 ms

Launcher-side harness, 10 samples; worker exit 0

2.1. Earlier Card-Ledger Baseline#

  • import myform measured 1.254 ms median and 1.448 ms p95 across 20 fresh interpreter samples.

  • A tiny MyST-to-Typst conversion measured 176.830 ms cold median and 185.011 ms p95 across 12 fresh interpreter samples.

  • The same tiny conversion measured 0.737 ms per warm in-process call across 200 calls.

The differing fixture, sample counts, and launch environment mean the ledger and launcher harness are complementary evidence, not a single comparable time series.

3. Disposable Measurement Boundary#

benchmarks/session_engine.py is an uninstalled developer probe, deliberately limited to Pydantic plus the standard library until a child actually imports myform. Its normal invocation writes one JSON document to stdout and all diagnostics to stderr. The stdio and Unix-domain-socket workers are child processes for one probe run, require explicit shutdown, and use bounded terminate/kill/reap cleanup.

uv run python benchmarks/session_engine.py --samples 5

Unix-socket support receives a direct bind preflight. Only an EPERM preflight denial is an environment skip in tests; child startup, import, protocol, or worker failures remain ProbeError failures with bounded diagnostics. After bind, the worker records the socket device/inode pair and unlinks only that same socket object, never a replacement pathname.

4. Decision Table#

Path

Latency / evidence

Lifecycle owner

Authentication boundary

Crash recovery

Version handshake

Fallback

Direct convert

Cold 250.105 ms; warm public 0.974 ms, launcher 10-sample data

Caller / process

Python call boundary

Caller retries a new call

Package version at install/import

Direct call is the baseline

In-process Session

Warm 0.436 ms, launcher 10-sample data

Caller / process; all calls serialize process-wide

Python call boundary

Failed call releases guard

Package version at install/import

Disposable convert

Disposable stdio fixture

0.531 ms, launcher 10-sample data; worker exit 0

Benchmark parent

Parent-owned stdin/stdout only

Explicit shutdown, then bounded terminate/reap

None: private same-run fixture

Parent uses direct conversion

Disposable Unix socket fixture

0.579 ms, launcher 10-sample data; worker exit 0

Benchmark parent

Filesystem path in one temporary directory

Explicit shutdown, bounded terminate/reap, identity-safe unlink

None: private same-run fixture

Parent uses direct conversion

No fixture is a durable protocol. No named caller currently exists. A future opt-in service would need a versioned request/response handshake, compatible-version behavior, authenticated local ownership, restart policy, and direct myform.convert fallback before it could become product surface.

5. Daemon and LSP Gates#

Reopen daemon design only when one named external workflow trace satisfies every condition:

  1. It records at least 100 conversions.

  2. Its active bursts reach at least 10 requests per minute.

  3. It demonstrates at least 50 ms median end-to-end benefit against process-per-call.

  4. Lifecycle, authentication, versioning, and crash-recovery owners have explicitly accepted their responsibilities.

Any auto-start, service installation, socket persistence, network listener, or systemd unit remains a separate human-gated capability decision. This probe creates none.

An LSP remains gated on cross-format editor value not already supplied by Tinymist. myform translates and formats across document formats; Tinymist is a Typst-native editor/LSP tool. Neither role subsumes the other, and this probe implements no LSP request surface.

The accepted Rust/Python integration remains the typst-syntax parser bridge packaged through PyO3/Maturin as an ABI3 extension. ttt remains unsuitable as a dependency because it is an unlicensed binary application, not a parser library. Those are unchanged, already-landed decisions; this memo adds no live-source claim to them.

6. Problem space#

  • Probe terminus — No further daemon, LSP, listener, or service work belongs in this bounded probe.

    • ⭐ Named external workflow trace — The keystone: capture the 100-conversion, 10-request/minute, 50-ms-benefit evidence before reopening daemon design.

      • Accepted operational owners — A future proposal needs explicit lifecycle, authentication, versioning, and crash-recovery ownership.

    • Cross-format editor value — Revisit LSP scope only if a concrete need exceeds Tinymist’s Typst-native remit.

  • No next steps — The current probe campaign is closed at its no-service terminus; a qualified external caller reopens the decision rather than extending this implementation.