Technical report · model and reasoning

Time, authority,
and physical work.

A compact derivation of the model behind Pysolate: why a read-only operation is not automatically safe to move, how PLM starts work without moving Python semantics into a dispatcher, and why sharing work does not require sharing a run.

Central separation

A logical run owns authority, mutable state, workspace and output. The Host may change when selected physical work starts or which immutable products serve that run.

01 · The semantic object

A read is a function of more than its visible arguments.

Calling an operation “read-only” says that it does not mutate provider state. It does not make the result independent of context or observation time.

Operation model R(a,h,t)→v  ∣  eR(a,h,t) \rightarrow v \;\mid\; e

The operation returns a value vv or an error ee. Its result depends on three inputs:

aa

Explicit arguments

Values visible at the call site, such as a ticker, object key or query.

hh

Hidden inputs

Provider state, session identity, credentials, policy context and other inputs not written in Python.

tt

Observation time

The instant whose provider state, budget decision or log entry the request observes.

Two calls with the same aa can differ because hh or tt differs. A current price, inventory count, deployment status or expiring credential can change without any change to the source program.

Read-only is an effect property, not a scheduling licence. Starting a request can consume budget, reveal information to a provider and select an earlier value. Pysolate therefore requires a Host-declared temporal contract before it moves Prepare.

02 · Time as a first-class input

Scheduling changes time directly, so the model names time directly.

Time could be hidden inside hh, but that would conceal the variable that scheduling changes on purpose. Pysolate exposes tt so the contract can state whether an observation made at preparation time remains admissible when Python reaches the original call.

Temporal admissibility C(a,h,tP,tL)=true\mathcal{C}(a,h,t_P,t_L)=\mathrm{true}

tPt_P is the time at which Prepare may start provider work. tLt_L is the later point at which Linearize checks the slot and binds it to the run. The contract C\mathcal{C} decides whether the earlier observation is valid for this call and run.

A dated closing price can admit a broad interval because the date fixes the value. A “current inventory” query may admit only a narrow interval or none at all. The Host owns this distinction because the provider adapter, not arbitrary Python analysis, knows the operation’s temporal semantics.

Why Linearize rechecks

Permission at Prepare is necessary but not sufficient. By tLt_L, the run may have reached the call under a different grant, source occurrence, Host plan or temporal condition. Linearize binds the earlier attempt back to the current logical call before Materialize releases its value or error to Python.

03 · Prepare–Linearize–Materialize

PLM separates starting work from observing its outcome.

HostPrepareStart or reuse one provider attempt; return an opaque slot.
CPythonContinueExecute ordinary statements and control flow.
Original callLinearize + MaterializeRecheck and bind, then return or raise.

Prepare returns a handle, not the provider result. The Host holds the result until Python reaches the original call. This preserves the program point at which the value or exception enters the language.

Observation invariant Observe⁡i=M ⁣(L ⁣(si,ρi,ai,tLi))\operatorname{Observe}_i = M\!\left(L\!\left(s_i,\rho_i,a_i,t_{L_i}\right)\right)

sis_i is the opaque slot for source occurrence ii; ρi\rho_i is that run’s Host plan and ownership context. Only PP may move. LL and MM stay at occurrence ii.

04 · Scheduling without a runtime dispatcher

The transformed Python program is the schedule.

Start with the simplest useful case: two independent calls whose arguments are already known. Then add the dependency and branch that make placement matter.

1

Two independent calls

Both calls are dated immutable reads. Prefix analysis can start both requests; the transformed program later adopts their slots at the original calls.

Original Python
apple = stocks.get_close("AAPL", date)
microsoft = stocks.get_close("MSFT", date)
difference = apple - microsoft
Equivalent transformed Python
a_slot = prepare_or_reuse(get_close, "AAPL", date)
m_slot = prepare_or_reuse(get_close, "MSFT", date)
apple = linearize_and_materialize(a_slot)
microsoft = linearize_and_materialize(m_slot)
difference = apple - microsoft

Teal starts Host work. Amber preserves the two original observation points.

No dispatcher is needed: the requests may run together, but ordinary Python still receives apple before it evaluates the next statement and receives microsoft at the second call.

2

Now add a dependency and a branch

The current-price call needs a ticker produced by the first call and exists only on the market-open path. Its Prepare must therefore sit after the dependency and inside the branch:

listing_slot = prepare_or_reuse(stocks.get_listing, "AAPL")
listing = linearize_and_materialize(listing_slot)
ticker = listing["ticker"]

if listing["market_open"]:
    price_slot = prepare(stocks.get_price, ticker)
    label = "Current price for " + ticker
    price = linearize_and_materialize(price_slot)

The listing request can be in flight before Python starts. The price request cannot start until Python binds ticker and enters the branch. If the market is closed, Python never reaches that Prepare and no price request starts.

No Python dispatcher is added. Language-specific dependency reasoning stays in the compiler; provider attempts, slots and lifecycle state stay in the language-independent Host.

05 · The economics of overlap

Preparation pays only when hidden latency exceeds its fixed cost.

For nn independent operations with provider delays d1,…,dnd_1,\dots,d_n, serial execution accumulates the delays. Concurrent preparation approaches the longest delay. If the whole-program pass and bookkeeping cost cc, the idealised comparison is:

Break-even model Tserial≈Tpy+∑i=1ndiT_{\mathrm{serial}} \approx T_{\mathrm{py}} + \sum_{i=1}^{n} d_i TPLM≈Tpy+c+max⁡idiT_{\mathrm{PLM}} \approx T_{\mathrm{py}} + c + \max_i d_i gain≈∑i=1ndi−max⁡idi−c\mathrm{gain} \approx \sum_{i=1}^{n}d_i - \max_i d_i - c
∑idi\sum_i d_i
Expose the serial waste

Synchronous independent calls pay every provider delay in sequence.

max⁡idi\max_i d_i
Replace accumulation with overlap

If all calls can start together, the slowest request determines provider wait.

cc
Pay for knowing the schedule

Parsing, helper insertion and Host bookkeeping are real fixed costs.

Worked example: substitute actual delays

Suppose two independent reads take d1=500 msd_1=500\,\mathrm{ms} and d2=700 msd_2=700\,\mathrm{ms}, while analysis and bookkeeping cost c=100 msc=100\,\mathrm{ms}. These are illustrative values, not benchmark results.

Tserial−Tpy≈500+700=1200 msT_{\mathrm{serial}}-T_{\mathrm{py}} \approx 500+700 = 1200\,\mathrm{ms} TPLM−Tpy≈100+max⁡(500,700)=800 msT_{\mathrm{PLM}}-T_{\mathrm{py}} \approx 100+\max(500,700) = 800\,\mathrm{ms} gain≈1200−800=400 ms\mathrm{gain} \approx 1200-800 = 400\,\mathrm{ms}

The thought process: first identify delay that is independent, then ask how much of it can overlap, and only then subtract the cost of discovering and representing that schedule.

The model predicts the direction, not a universal threshold. Dependencies, release times, Python work and provider concurrency change the available overlap. The measured crossover therefore belongs to the evaluated workload.

A more precise scheduling view

Let rir_i be the earliest time operation ii is semantically ready, si≥ris_i \ge r_i its actual start, and uiu_i the original Python call. If the provider finishes at fi=si+dif_i=s_i+d_i, the remaining stall at the call is:

stall⁡i=max⁡(0,fi−ui)\operatorname{stall}_i = \max(0, f_i-u_i)

PLM cannot move rir_i earlier than the program makes arguments and control dependencies available. It improves latency by choosing sis_i as early as the semantic and authority constraints allow, while keeping uiu_i fixed.

Worked example: a dependency limits the overlap

Return to the branch example. Suppose ticker becomes available at r2=0.6 sr_2=0.6\,\mathrm{s}, the price request takes d2=0.7 sd_2=0.7\,\mathrm{s}, and Python reaches the original price call at u2=0.9 su_2=0.9\,\mathrm{s}.

s2=r2=0.6,f2=s2+d2=1.3 ss_2=r_2=0.6,\qquad f_2=s_2+d_2=1.3\,\mathrm{s} stall⁡2=max⁡(0,1.3−0.9)=0.4 s\operatorname{stall}_2=\max(0,1.3-0.9)=0.4\,\mathrm{s}

Without early preparation, the request would start at u2u_2 and stall for the full 0.7 s0.7\,\mathrm{s}. AST placement exposes only the 0.3 s0.3\,\mathrm{s} window between dependency resolution and use. It cannot lawfully move the request to time zero because its argument and branch decision do not exist yet.

The deeper point: the optimisation moves the physical start from u2u_2 back to r2r_2; it does not move r2r_2 or the Python observation point. Semantics define the earliest legal start, and scheduling exploits only the interval that remains.

06 · One separation, two scopes

The same distinction appears at two points in a run’s life.

Within one run

Prepare early

A provider attempt begins before its Python observation point. The slot belongs to the same run.

Across instances

Share unchanged pages

Private Guest instances map one pre-initialised image. Writes create instance-private dirty pages.

Logical-run invariant. Capability grants, call budget, ownership context, mutable memory, workspace changes, cancellation scope and output remain per run. Pysolate changes physical scheduling and backing, not a prior run’s authority.

07 · The argument in compact form

Each optimisation requires a different Host check.

OptimisationWhat may be reused or movedHost evidence requiredWhat remains private
Early preparationStart time of eligible provider workKnown arguments, dependencies satisfied, permission, effect class and temporal contractPython control flow, observation point, workspace and output
Copy-on-writeUnchanged physical pagesApproved image capture point, runtime identity and backend supportDirty pages, mutable interpreter state and workspace

The model does not argue that shared execution is always safe or always faster. It identifies the exact physical object being moved or reused, the Host evidence needed to admit that move, and the logical state that must remain separate.

That framing turns isolation from a choice between “reuse everything” and “rebuild everything” into a selective scheduling and ownership problem.