Decisions as a Service
Sign Up Now →Sign Up Now

Python

Every public class, method and field in helixor_runtime 0.3.1 that you use to evaluate payloads, filter streams and serve decisions.

Runtime 0.3.1Python 3.11+Tier 0

Generated Python SDKs

helixor-sdk-gen 0.1.0 also supports Python as both an OpenAPI client target and an embedded decision SDK target. This page documents the packaged 0.3.1 decision runtime. See SDK generation for the target matrix and release boundary.

About the example

The examples on this page use the built-in data-protection pack so you can run everything without writing a pack first. The same calls work for any decision pack: eligibility, limits, routing and so on. See Core concepts.

from helixor_runtime import HelixorEngine
from helixor_runtime.server import start_server, stop_server

Import only from helixor_runtime. Other modules in the distribution are implementation details and can change without notice.

HelixorEngine#

The engine holds one pack and evaluates payloads against it in your process. It is safe to create once and reuse. Evaluation makes no network calls and spends no model tokens.

HelixorEngine() → HelixorEngine

Creates an engine with the built-in example pack (data protection, compliance.regulatory_pii_guard.v1) and the bundled Community license. Needs no files, key or network. The examples on this page use that pack; the API is the same for any pack.

The constructor also accepts an optional guard argument. It is reserved for internal use; leave it out.

engine = HelixorEngine()
print(engine.pack_id, engine.version, engine.tier)
# compliance.regulatory_pii_guard.v1 1.0.0 COMMUNITY
@classmethod load_pack(pack_path: str | PathLike | None = None, *, license_file: str | PathLike | None = None) → HelixorEngine

Loads a compiled .hxpack and unseals it in memory with your .hxlic. With no arguments it returns the same engine as HelixorEngine(). Once you give a pack path, the pack is unsealed or the call raises; it never falls back to the built-in example pack.

pack_pathstr | PathLike | None
Path to a compiled pack; ~ is expanded. See Packs and manifest.
license_filestr | PathLike | None
Path to your license file. Keyword-only. When omitted, the license is searched for in HELIXOR_LICENSE_FILE, then ./helixor.hxlic, then ~/.helixor/helixor.hxlic; the older name helixor.lic is accepted in those two directories. See where the runtime looks.
engine = HelixorEngine.load_pack("internal_ids.hxpack", license_file="~/.helixor/helixor.hxlic")
engine.pack_id      # 'custom.internal_ids.v1'
engine.tier         # 'DEVELOPER'

Raises FileNotFoundError when the pack does not exist; LicenseNotFoundError (LICENSE_NOT_FOUND, also a FileNotFoundError) when the explicit license path or HELIXOR_LICENSE_FILE names a missing file or no license is found, listing the locations checked; LicenseLocationAmbiguousError when one directory holds both helixor.hxlic and helixor.lic; ValueError for license_file without pack_path; PackUnsealError when the pack is invalid, tampered or keyed to another license, UnsupportedPackDeclarationError when the pack declares something the engine does not execute, and a license error when the signature, dates or entitlements fail.

Properties#

PropertyTypeValue
pack_idstrThe loaded pack, for example compliance.regulatory_pii_guard.v1.
versionstrThe pack version, for example 1.0.0.
licensed_tostrFor a loaded pack, the organization in the license. For the built-in pack, Community.
tierstrCOMMUNITY, DEVELOPER, LAUNCH_TRIAL or ENTERPRISE. Falls back to COMMUNITY.
evaluate(text: str | dict) → EvaluationResult

Runs the pack's extraction steps, checks its rules, builds the remedy and returns the result. The call is synchronous and does no I/O.

textstr | dictrequired
The payload. For a dict, it must have exactly one key, one of text, message or payload, whose value is a string (it may be empty). A dict with more than one key raises AmbiguousPayloadError (a CodonRuntimeError, with the keys in .keys, sorted). A dict with none of those keys, or whose value is not a string, raises CodonRuntimeError. Changed in 0.3.0: 0.2.1 read the first of the three keys that was present and ignored the other keys.

Rule order. Every rule is checked, in a fixed order, and every match is listed in triggers. The first fatal match sets action and reason; with no fatal match, the first warning does. The remedy covers everything that was found. In the example pack the order is Social Security number, card number, health record ID, then contact details (email, phone, IPv4), and the remedy redacts every category that was found.

result = engine.evaluate("Employee note: SSN is 123-45-6789, email jane@example.com.")
result.action              # 'block_glba_ssn_leakage'
result.remedy.clean_text   # 'Employee note: SSN is [REDACTED_SSN], email [REDACTED_EMAIL].'
[t.rule_id for t in result.triggers]  # ['RULE-GLBA-SSN-BLOCK', 'RULE-GDPR-EMAIL-REDACT']

A compiled pack evaluates your rules first, then the built-in checks, with the same precedence. See Evaluating decisions.

compile_custom_rule(instruction: str) → RemovedCapabilityError

Removed in 0.3.0. No public engine could run it: HelixorEngine() is Community, and an engine from load_pack() is sealed. hasattr(engine, "compile_custom_rule") is False, and a call raises RemovedCapabilityError, an EnclaveCapabilityError that is also an AttributeError, whose message points to the playbook's rules.

Put custom patterns in a playbook's rules, compile it, and load it with load_pack(). See Playbook schema.

Streaming methods#

create_streaming_session(lookahead_chars: int = 28, block_on_fatal: bool = True) → StreamingPIISession

Returns a stateful session that you feed token fragments to. It holds back a lookahead window so a value split across fragments ("123", "-45", "-6789") is caught before any part of it is released.

lookahead_charsint
How many trailing characters are held back. Values below 16 are raised to 16. Default 28.
block_on_fatalbool
When True, a fatal match halts the session. When False, fatal matches are remedied (in the example pack, redacted) and the stream continues. Default True.
stream_filter(token_stream: Iterable[str], lookahead_chars: int = 28, block_on_fatal: bool = True) → Iterator[str]

Wraps a synchronous iterable of token strings and yields filtered text. It creates a session, pushes each token and flushes at the end.

On a fatal match with block_on_fatal=True it raises StreamBlockedError("Stream blocked by statutory invariant: <reason> (<action>)"), with action and reason attributes. It subclasses RuntimeError. Text already yielded stays yielded; nothing from the blocked window is released.

from helixor_runtime import StreamBlockedError

try:
    for text in engine.stream_filter(model_tokens()):
        send_to_client(text)
except StreamBlockedError as exc:
    send_to_client("[response withheld]")
    log.warning("stream blocked: %s", exc.action)
astream_filter(token_stream: AsyncIterable[str], lookahead_chars: int = 28, block_on_fatal: bool = True) → AsyncIterator[str]

The async version of stream_filter. Use async for. It raises the same StreamBlockedError on a fatal block.

See Streaming for patterns and Decide on a stream as it arrives for a walkthrough.

Result objects#

EvaluationResult

The object evaluate() returns, exported as helixor_runtime.EvaluationResult. Use it for type hints and isinstance checks. (helixor_runtime.DecisionResult is a different class, the generated SDK model described below.) It is a Pydantic model, so result.model_dump() gives the same JSON the HTTP API returns.

FieldTypeMeaning
pack_idstrThe pack that decided.
actionstrOne of the pack's declared actions. Values for the example pack: permit_clean_payload, block_glba_ssn_leakage, block_pci_dss_pan_leakage, block_hipaa_phi_leakage, redact_and_permit_contact_pii. A compiled pack adds block_<id> and redact_<id> for its own rules.
invariants_passedboolTrue when no rule fired. False for both fatal and warning results.
reasonstrA readable explanation of the deciding rule.
triggerslist[RegulatoryTrigger]Every rule that fired, in evaluation order.
remedyCounterfactualRemedyThe repaired payload. Always present.
latency_usfloatMicroseconds spent extracting, checking rules and building the remedy, rounded to 2 places. License checks, receipt hashing and building this object are not included.
tokens_spentintAlways 0.
egress_bytesintAlways 0.
receipt_hashstrhx_proof_ followed by 24 hex characters: a truncated SHA-256 over the helixor.decision_receipt.v1 body: schema, pack ID, pack version, action, outcome, fired rule IDs and a SHA-256 of the input. The built-in pack and compiled packs use the same recipe (changed in 0.3.0). It is deterministic, unkeyed and not chained. See Receipts.
license_statusstr | NoneCOMMUNITY_PERPETUAL, ACTIVE, EXPIRING_SOON or IN_GRACE_PERIOD.
active_key_idstr | NoneThe ID of the license key that authorized this evaluation.
active_epochint | NoneThat key's rotation epoch.
editionstrCOMMUNITY, DEVELOPER, LAUNCH_TRIAL or ENTERPRISE.
upgrade_noticestr | NoneHuman-readable licensing text. Do not parse it.
RegulatoryTrigger
FieldTypeMeaning
rule_idstrFor example RULE-GLBA-SSN-BLOCK, RULE-PCI-DSS-PAN-BLOCK, RULE-HIPAA-PHI-BLOCK, RULE-GDPR-EMAIL-REDACT, RULE-TCPA-PHONE-REDACT, RULE-GDPR-IP-REDACT.
lawstrThe regulatory framework the rule enforces, as display text.
severitystrFATAL (do not let the payload proceed) or WARNING (proceed with the remedy).
matched_itemslist[str]The exact values that matched; with the example pack, the sensitive values found.

matched_items contains the raw matched values

matched_items holds the exact values that matched; with the example pack, the unredacted SSNs, card numbers and addresses that were found. Never log, store or return it. Log rule_id, severity and receipt_hash instead.

CounterfactualRemedy
FieldTypeMeaning
clean_textstrThe repaired input. For the example pack, the input with each match replaced by its redaction token: [REDACTED_SSN], [REDACTED_CARD_PAN], [REDACTED_HEALTH_ID], [REDACTED_EMAIL], [REDACTED_PHONE], [REDACTED_IP].
redactions_countintHow many distinct values were replaced.
redacted_categorieslist[str]Display names of the categories found, for example (example pack) SSN (GLBA/FCRA), Email (GDPR/CCPA).

A remedy is computed even for fatal results. It tells you what was found; it does not make a fatal payload safe to send.

StreamingPIISession

Returned by create_streaming_session(). The held-back buffer stays raw, and each push re-checks all of it.

push(delta: str)→ list[StreamChunkResult]
Adds a fragment and returns zero or more chunks that are safe to emit. Text is released up to the last delimiter (whitespace or . , ; ! ? ( ) [ ] { }) that lies outside the lookahead window and does not cut a value in two: the released part and the held part, redacted separately, must give the same text as the whole buffer redacted. If there is no delimiter and the buffer exceeds twice the window, the latest such cut that keeps the window is used. After a block, push returns []. It does not raise.
flush()→ list[StreamChunkResult]
Checks and releases the rest of the buffer, with is_final=True. Call it once when the source stream ends.
is_blockedbool
True once a fatal match has halted the session.
session = engine.create_streaming_session(lookahead_chars=28, block_on_fatal=True)
for token in model_tokens():
    for chunk in session.push(token):
        if chunk.blocked:
            abort(chunk.action, [t.rule_id for t in chunk.triggers])
            break
        emit(chunk.text)
    if session.is_blocked:
        break
else:
    for chunk in session.flush():
        emit(chunk.text)

The concatenated chunks equal evaluate(full_text).remedy.clean_text for any fragment size. In 0.2.0, short fragments could produce text such as [REDACTED_EMAIL]m; 0.2.1 and later fix that.

StreamChunkResult
FieldTypeMeaning
chunk_indexintIncreases by one per emitted chunk, starting at 1.
textstrText to emit. Empty when blocked.
is_finalboolTrue on chunks from flush().
redactedboolTrue when this chunk's text differs from the raw text it replaces.
blockedboolTrue on the single chunk that halts the stream.
actionstrstream_token for normal chunks; the pack action on a blocked chunk.
reasonstr | NoneSet on a blocked chunk.
triggerslist[RegulatoryTrigger]The fatal triggers, on a blocked chunk only. matched_items holds raw values.

Service helpers#

helixor_runtime.server starts the local HTTP, SSE and WebSocket service in a background thread. Use it in tests and in single-process deployments. The endpoints are in the HTTP API reference. The helpers need fastapi and uvicorn installed.

start_server(engine: HelixorEngine | None = None, host: str = "127.0.0.1", port: int = 0, log_level: str = "warning", token: str | None = None) → tuple[Server, Thread, str]

Starts the service on a daemon thread, serving engine. It waits up to 5 seconds for GET /v1/health to answer, then returns (server, thread, base_url), for example http://127.0.0.1:53817. If the service does not answer in time, it stops it and raises ServerConfigurationError.

engineHelixorEngine | None
The engine every endpoint evaluates with, for example one from load_pack(). None serves the built-in example pack. Anything that is not a HelixorEngine raises TypeError.
hoststr
Bind address. A non-loopback address without a token raises ServerConfigurationError.
portint
0 picks a free port.
log_levelstr
Server log level.
tokenstr | None
Bearer token every endpoint except GET /v1/health requires. None reads HELIXOR_SERVICE_TOKEN; if that is unset too, the service has no authentication.

The same service runs as a module: python -m helixor_runtime.server [--pack PACK] [--license LICENSE] [--host 127.0.0.1] [--port 18734] [--token TOKEN] [--log-level info]. Without --pack it serves the built-in example pack.

stop_server(server: Server, thread: Thread) → None

Asks the server to exit and waits up to 2 seconds for the thread.

server, thread, base_url = start_server(host="127.0.0.1", port=0)
try:
    ...  # call base_url + "/v1/evaluate"
finally:
    stop_server(server, thread)

Generated SDK models#

helixor_runtime also re-exports a client and models generated from the pack manifest: RegulatoryPiiGuardClient, RegulatoryPiiGuardState, DecisionResult, DecisionVerdict, NecessaryCause, Counterfactual and TrueUpReport. They decide on six booleans you have already extracted (has_ssn … has_ip), not on text. Their shapes match the Java client.

RegulatoryPiiGuardClient(pack_path=None, license_json=None, license_file=None, native_lib_path=None, allow_python_fallback=False, execution_mode=None)

Methods and properties:

  • decide(state) returns a DecisionResult with pack_id, action, verdict, confidence, proof_sha256, latency_us, execution_mode, necessary_causes, counterfactual and is_approved. There is no action argument: the first hard rule that fires sets action, otherwise it is permit_clean_payload. verdict is approve or refuse, and is_approved agrees with it. Each cause names its rule and rule_verdict, and counterfactual.changes has one change per cause. A state missing a field a rule reads raises DecisionStateError.
  • decide_batch(states) calls decide once per state.
  • get_audit_report() returns a TrueUpReport from the native ledger. Without one it raises AuditReportUnavailableError.
  • is_native reports whether a native library was loaded; audit_available whether get_audit_report() can answer.

The client runs on a native runtime library. No native library ships with 0.3.1 (Planned). execution_mode is native by default, so the constructor raises NativeRuntimeUnavailableError. Pass execution_mode="python" to use the generated Python evaluator, or allow_python_fallback=True, which selects native_or_python. On the Python evaluator is_native and audit_available are False and each result's execution_mode is embedded_python. A pack_path that does not exist raises FileNotFoundError.

Changed in 0.3.0

The bundled client is regenerated. In 0.2.1 decide() took a caller-supplied action, the counterfactual described only the first cause, and get_audit_report() raised NativeRuntimeUnavailableError.

Known issue in 0.3.1

Not a HelixorRuntimeError. NativeRuntimeUnavailableError, and AuditReportUnavailableError which derives from it, derive from RuntimeError only. Catch them by name.

Exceptions#

Methods on this page can raise CommunityEditionRestrictionError, EnclaveCapabilityError, DeveloperQuotaExceededError, CodonRuntimeError, StreamBlockedError, PackUnsealError, license errors, ServerConfigurationError and FileNotFoundError (load_pack). Since 0.3.0 they can also raise RemovedCapabilityError, AmbiguousPayloadError, DecisionBudgetExceededError and BeliefSnapshotError. Since 0.3.1 load_pack raises LicenseNotFoundError (a FileNotFoundError) or LicenseLocationAmbiguousError when it cannot pick one license, and using HelixorSalesEngine raises SalesEngineUnavailableError. All but FileNotFoundError derive from HelixorRuntimeError and are exported from helixor_runtime. The full list, with causes, is in Errors and failure modes.