# 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. ```sh 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.*