Penelope

Production checklist — running Penelope past the demo stage.

What "production" means here

Penelope is good enough for: small services, durable workflows, agent runtimes, side-projects with snapshot-based state. It is not a substitute for a tier-1 language runtime at scale — there is no GC tuning, no JIT bailout machinery, no contiguous-arena allocator. The thesis is correct and the implementation is clean, but you should expect to operate it the way you'd operate a careful in-house tool.

Snapshot retention

The .penz format is a versioned JSON blob (gzip-compressed by default since 5.D). It includes the program path, the program SHA-256, the paused IP, and the full VM state. Treat snapshots as durable values: store them in an object store, rotate them on a TTL, audit them when you redeploy.

ConcernRecommendation
Long-lived snapshotsRetain in S3 / GCS with a TTL matching your workflow SLA.
Schema migrationIf you rev the source, the hash check rejects old snapshots by default. Use --force only when you've audited the IP map (see live editing).
Sensitive dataSnapshots include all live bindings. Encrypt at rest. Audit which workflows could capture PII into the state.
Sizegzip default: ~5–20× smaller than JSON. Inspect with pen inspect <snap.penz>.

Effect replay invariants

The VM records every external effect (print, net_fetch, now, random_int, read_file, write_file, wait_until, wait_for) into the snapshot's effect log. On resume, those effects are not re-performed: the recorded value is reused. This is what makes resume safe.

The implications:

Observability

The VM has a tracer hook (src/tracer.ts). Pass an implementer of the Tracer interface to run(prog, state, profile, tracer), and you'll get structured events: fn_call, fn_return, effect, pause, resume, error. The shape mirrors OpenTelemetry's span events closely enough that a 50-line adapter ports it.

import { run, freshState } from 'penelope-lang';
import { JsonLinesTracer } from 'penelope-lang/dist/tracer';

run(prog, freshState(), undefined, new JsonLinesTracer(process.stderr));

Built-in tracers:

Distributed runtime

The coordinator in src/dist/coordinator.ts is single-node. That is fine for workloads up to ~thousands of in-flight jobs per process. For HA, run the coordinator behind a process supervisor (systemd, supervisord, k8s Deployment) and back it with a FileStore pointing at a persistent volume — when the coordinator restarts, jobs survive.

What is not built in: consensus / leader election. If you need multi-coordinator HA, you're looking at a different project (Raft + state replication). For most use cases, "one coordinator with a persistent store and quick restart" is fine.

Security

VectorMitigation
Untrusted source codeDon't run untrusted .pen files. The VM has no sandbox — effects (read_file, net_fetch) hit the host directly.
Coordinator APIUnauthenticated. Run behind a private network or wrap with an auth proxy.
Snapshot tamperingThe program-hash check catches accidental drift but is not a cryptographic signature. Verify your own integrity (HMAC, signed envelope) if snapshots cross trust boundaries.

Performance

Versioning

Snapshot format is v3 (since Phase 3). Pre-v3 snapshots are not migratable (the VM model changed). When the format revs again, this page will document the migration path. Until then, treat .penz files as bound to the language version that produced them.