pollard-jev
A Python companion to Pollard that turns timestamped observations and permitted choices into a governed decision, a bounded simulated action, and an outcome record. The first milestone runs entirely offline after installation. It needs no credentials, model weights, or GPU.
python -m pip install pollard-jev
pollard-jev demo --output artifacts
Published under the MIT license. The initial release is 0.1; minor increments
are +0.01, major increments are +0.10, and releases at or above 1.0 require
the owner's explicit approval. See the
release policy.
Run on Windows PowerShell
Requires Python 3.11 or newer. Tested here on Python 3.12.2.
git clone https://github.com/jemsbhai/pollard-jev.git
Set-Location pollard-jev
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[test]"
.\.venv\Scripts\python.exe -m pollard_jev demo --output artifacts
.\.venv\Scripts\python.exe -m pytest -q
Setup uses PyPI; the demo and tests make no network requests. The equivalent
installed entry point is .\.venv\Scripts\pollard-jev.exe demo --output artifacts.
Each demo writes uniquely named JSONL records and a Pollard SQLite ledger.
| Scenario | Synthetic proposal | Final policy | Simulated action |
|---|---|---|---|
| Fresh coherent observations | continue |
accept | move 20 cm |
| Conflicting observations | inspect |
need evidence | none |
| Missing evidence | inspect |
need evidence | none |
| Evidence expires during inference | continue |
defer | none |
| Request budget exhausted | none | defer | none |
| Provider failure | none | defer | none |
The stale scenario advances a virtual clock during inference so it is reproducible. Separate tests exercise real waiting, timeout, and cancellation. The state-machine baseline reads the same request at the final evaluation time. It has no provider and therefore no provider failures or inference-request cost; the table is a behavior comparison, not a performance benchmark.
Typed inputs and outputs
from datetime import datetime, timedelta, timezone
from pollard_jev.contracts import Observation
from pollard_jev.demo import example_request
from pollard_jev.loop import DecisionLoop
from pollard_jev.providers.fixture import FixtureProvider
now = datetime.now(timezone.utc)
range_reading = Observation(
observation_id="range-42", source="front-range-sensor",
feature="front_range_m", value=1.5, unit="m",
observed_at=now, valid_until=now + timedelta(seconds=5),
)
# The demo request supplies all four required sensor features and four choices.
request = example_request(now, request_id="robot-42")
request = request.model_copy(update={
"observations": (range_reading, *request.observations[1:]),
})
with DecisionLoop(FixtureProvider(), max_requests=2,
records_path="artifacts/decisions.jsonl",
pollard_path="artifacts/decisions.db") as loop:
record = loop.decide(request)
print(record.model_dump_json(indent=2))
Run the complete example with
.\.venv\Scripts\python.exe examples\offline_loop.py.
Unknown readings use status="unknown", value=None; absence of a required
feature is also explicit insufficient evidence. Datetimes must have timezones,
validity windows must be positive, and numeric values must be finite.
A successful record contains fields like these (excerpt):
{
"schema_version": "1",
"provider_status": "ok",
"provider_result": {
"semantics": "synthetic_support",
"scores": {"continue": 0.94, "inspect": 0.12, "recover": 0.12, "request_help": 0.12},
"proposed_action": "continue",
"parameters": {"distance_cm": 20},
"evidence": "sufficient"
},
"policy": {"disposition": "accept", "reason": "supported_and_feasible"},
"action": {"action": "continue", "status": "completed", "simulated": true},
"budget_limit_requests": 2,
"budget_spent_requests": 1
}
Full records include observation IDs, timestamps and units; the question and allowed choices; provider identity, version and settings; policy configuration and version; simulated skill version and observed state; timing and budget fields; and Pollard root/model/action node IDs. Records round-trip through validated Pydantic models. The JSONL is a convenient export; the same complete record is also stored as a Pollard note.
How the loop works
DecisionProvider.infer(tuple[DecisionRequest, ...]) returns typed results.
The interface is general; this milestone's policy and skill set are specific to
the robot demonstration. Providers return data and receive no dispatcher.
Pollard 1.6.0 supplies the real model-call ledger, custom request budget, registered action allowlist, integer argument bounds, refusal nodes, action records, SQLite persistence, and integrity verification. The companion supplies contracts, evidence/support policy, sensor validity checks, deadlines, cancellation, simulation, and linked decision records. The older sibling Pollard checkout was inspected but is not modified or used as a dependency. See the API inspection for exact versions and boundaries.
The policy checks four engineered sensor features: range and camera clearance
in metres, battery percentage, and a stuck indicator of 0 or 1. Conflicting,
missing, duplicate, invalid, expired, and future-dated readings block action.
Support threshold 0.80, score margin 0.15, clearance 0.5 m, battery 10%, and
range disagreement 0.4 m are demonstration settings, configurable through
PolicyConfig. Required physical units cannot be relabeled to change their meaning.
The policy also checks feasibility separately from evidence support. Task
utility is represented by the caller's question and choice hypotheses; there
is no trained utility or physical success predictor here.
The bounded simulated skills are:
| Skill | Permitted integer parameter | Default |
|---|---|---|
continue |
distance_cm: 1–50 |
20 |
inspect |
samples: 1–5 |
2 |
recover |
distance_cm: 1–20 |
10 |
request_help |
retries: 1 |
1 |
The request's selected parameter values must also match the provider proposal. Acceptance never expands the caller's available actions. The registered handler rechecks policy, freshness, cancellation, and the monotonic deadline immediately before dispatch. A second action in the same batch requires new observations, including when the first attempted action fails.
Inference runs in one daemon worker per loop. A timeout or cancellation discards
that worker's reply. Until it finishes, later calls return busy; this prevents
unbounded abandoned workers. Python cannot terminate arbitrary native model
compute. A timeout bounds response/dispatch eligibility, not GPU execution or
total process resource use. Cancellation does not undo an action already
dispatched. Actual controllers, watchdogs, immediate stopping, and motion limits
remain outside this supervisor.
The resource budget counts logical provider batch attempts: one
infer(requests) invocation costs one regardless of question count. Success,
malformed output, provider failure, timeout, and cancellation after admission
each consume one. Pre-cancelled, busy, and budget-refused calls consume zero.
Actions and audit notes consume zero model requests. This is not token, HTTP
request, measured energy, or estimated-joule accounting. An adapter may perform
multiple internal forward passes per logical batch. Budgets last for one loop
instance/run; creating a new loop creates a new budget.
Independent support/entailment scores remain independent; they are never normalized into an action distribution. Categorical results are validated as such only when explicitly declared. Neither score type proves physical success. The fixture scores are synthetic and uncalibrated.
History, policy simulation, and live reevaluation
These are separate operations:
$recordFile = (Get-ChildItem artifacts\decisions-*.jsonl | Sort-Object LastWriteTime -Descending | Select-Object -First 1).FullName
.\.venv\Scripts\python.exe -m pollard_jev history $recordFile
.\.venv\Scripts\python.exe -m pollard_jev simulate-policy $recordFile
$ledgerFile = (Get-ChildItem artifacts\pollard-*.db | Sort-Object LastWriteTime -Descending | Select-Object -First 1).FullName
.\.venv\Scripts\pollard.exe verify $ledgerFile --json
inspect_history(path) only loads recorded events. simulate_policy(record,
config, at=...) evaluates recorded observations and provider output with a
specified policy/time. Neither has a dispatcher or a provider. Policy
simulation does not replay the resource ledger, cancellation state, or a new
physical trajectory. A changed action does not reveal what its consequences
would have been.
live_reevaluate(record, loop) is an explicit new provider invocation. It
consumes the loop's request budget, writes a new record linked to the original,
preserves the original timestamps, and always disables dispatch. It uses
whatever provider the caller explicitly put in that loop; with a fixture it
remains synthetic. Old evidence normally defers at today's time. Historical
records are never overwritten.
Credential fields, arbitrary malformed responses, and exception text are not persisted. Provider authentication must remain inside the caller-owned client. Do not put secrets in questions, observations, model identities, or settings: these are intentional audit content, not automatically scrubbed free text. The JSONL export and SQLite ledger are not a cross-file transaction. A crash may leave an intent or action without the final JSONL record; use the ledger for inspection. This is a single-process demonstrator, not crash recovery or exactly-once actuator infrastructure.
Optional real backend and next milestone
The implemented adapter targets the inspected AlexWortega/openjev
qwen3.5-4b-nli-v2 helper at a pinned commit. Its NLI request/result mapping and
local-cache loader are tested with doubles. No real model inference was run.
The default cache contains no selected checkpoint, and the project environment
has no heavy model dependencies. See OpenJev setup and evidence
for the optional dependency/download commands, API, licenses, and limitations.
The next milestone is an explicit pinned real-model benchmark: validate the Windows/CUDA runtime, compare 0.8B and 4B candidates against the state machine on recorded/noisy scenarios, measure decision errors, abstention and end-to-end latency, and calibrate support thresholds on held-out data. Add process-level inference cancellation before device integration. Measure energy with an actual meter when available; keep TOML and other proxies separate from joules. Native sensor encoders, controller connections, and MCU deployment are later work.
See implementation status for verified behavior and remaining limitations.
Release files for pollard-jev 0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pollard_jev-0.1.tar.gz | 46.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pollard_jev-0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.0 kB
Release files / pollard_jev-0.1.tar.gz
| Download URL | pollard_jev-0.1.tar.gz |
|---|---|
| Size | 46.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cd13179254fc3f29c4a1b5265b514ef989f87bd3d19432ebd4ddfa839ce3da66
|
|
BLAKE2b-256 checksum How to use checksums |
4352443607631f09275da5c8399e3e6fc54a491215e52d774b9c20b6b92d55b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.2
|
Release files / pollard_jev-0.1-py3-none-any.whl
| Download URL | pollard_jev-0.1-py3-none-any.whl |
|---|---|
| Size | 27.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9b2879f369329d38b521b0b2f1a6d93661ae4926cfc8c240446a68f39cad2a3f
|
|
BLAKE2b-256 checksum How to use checksums |
94a8902b9eb2044b9d77d0e7a2130d0e843adea1fcc4847c563b699ee575b391
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.2
|