Ingress
An ingress connector brings parameter changes into a running simulation from outside: from Kafka, Redis, PostgreSQL or a local schedule. This page shows each connector, the message it reads, how to send one, and when the change lands.
What It Is
An ingress connector listens to a source while the run goes on. Every message it reads is one change: a registry path and its new value, the same two names a scenario step uses. The change reaches the registry, and the simulation follows it, as Live Parameters describes. Four ingress connectors bring changes in. The seven connectors described on Egress write records out:

The tutorial page Live Changes teaches these features step by step.
How to Use It
Each connector is attached with add_ingress in the builder, passed to env.setup_ingress in the low-level API, or listed under ingress in a blueprint with its type and config. In a blueprint, type is one of the short names below, or a !python class from another package. config holds the constructor's arguments. A connector's module is imported only when a blueprint names it. A missing package stops the load with its line and the pip install command. YAML Keys lists the keys of an ingress entry.
| Connector | type |
Source | Message | Extra |
|---|---|---|---|---|
LocalIngress |
Local |
a schedule in the code | (delay in wall-clock seconds, path, value) |
none |
KafkaIngress |
Kafka |
a topic | a JSON object {"path": ..., "value": ...} |
kafka |
RedisIngress |
Redis |
a Pub/Sub channel | the same JSON object | redis |
PostgresIngress |
Postgres |
a table, polled | a row with the columns path and value, the value as JSON text |
postgres |
Local Schedule
LocalIngress applies changes after set delays in wall-clock seconds from the start of the run. It suits a local run. For a change at an exact simulation time, use a scenario step.
The low-level API passes the same LocalIngress to env.setup_ingress, in its list of connectors, as Low-level API shows.
Kafka
KafkaIngress consumes a topic and reads each message as a JSON object with path and value, or through a deserializer of your own, such as Avro, as Use Avro and Pydantic shows. Connect to Secure Kafka connects it to a secured cluster.
The low-level API passes the same KafkaIngress to env.setup_ingress, in its list of connectors, as Low-level API shows.
KafkaAdminConnector.send_config(topic, path, value) sends one such message. The Kafka example listens on sim-config, and this sets its number of packers:
A blueprint has no sender of its own. Send the message from Python, as the Builder tab shows, or from any Kafka client, as below.
The sender does not depend on how the model is written: use the function the Builder tab shows.
From any Kafka client, the message is the JSON object itself:
Redis
RedisIngress subscribes to a Pub/Sub channel and reads each message as the same JSON object.
The low-level API passes the same RedisIngress to env.setup_ingress, in its list of connectors, as Low-level API shows.
With the Redis example running, valkey-cli sends one from a shell:
PostgreSQL
PostgresIngress polls a table, simulation_params by default, every poll_interval seconds, 2 by default. It creates the table when it is missing, with the columns id, path, value, is_applied and created_at. It applies the rows in two steps:
- At the start, it applies the latest applied value of each path, so a restarted run resumes from the last settings.
- At each poll, it applies every row whose
is_appliedis false and marks it true, so the table keeps the history of every change.
value is read as JSON, and as plain text when it is not JSON.
The connection string names odctl, the database that the odctl postgres profile creates, with the user user and the password password.
The low-level API passes the same PostgresIngress to env.setup_ingress, in its list of connectors, as Low-level API shows.
With the PostgreSQL example running, psql adds one from a shell:
How It Works
The connectors run on the ingress thread, apart from the simulation. Each reads its source, turns every message into a (path, value) pair with one shared check, and puts it on a queue. A SimPy process, the ingress monitor, applies everything queued every 0.1 simulation seconds:

At factor: 1.0, 0.1 simulation seconds take 0.1 real seconds, so a change lands within 0.1 seconds of arriving. The simulation time it lands at depends on when it arrives, so two runs with the same seed and the same messages can differ. A change that must land at an exact simulation time needs a scenario step.
A connector is a class with one method, async run(queue), so a source desflow does not have takes one class to write: Write a Connector shows how.
Stability and Limits
- A bad message is skipped. A message that cannot be decoded, that is not an object, or that lacks a non-empty
pathor a non-nullvalueis logged as one WARNING line and skipped, and the run goes on. A PostgreSQL row like that is marked applied, so it is read once. A path the model does not have is a warning from the registry. -
Old keys get a hint. Before 0.21.0, Kafka read
path_id, and Redis and PostgreSQL readparam_pathandparam_value. A message with the old keys is skipped with a line that says so, such as this one, whichparse_changegives for a Kafka message written for 0.20.0: -
Old PostgreSQL columns. A table created before 0.21.0 has the columns
param_pathandparam_value. Reading it fails with an error line that gives the two statements to rename them: -
LocalIngressfollows the wall clock, so atfactor: 0an unpaced run can end before its first change. - Ingress needs an
until. The ingress monitor repeats for as long as the run lasts, so a run with ingress needsuntilor Ctrl+C, as Run Length explains.