Skip to the content.

v1.3.1 Design Plan: Placement Completion & Targeted Backend Follow-ups

Status: Draft for review Target version: v1.3.1 Scope: Manager and standalone modes. Worker mode is execution-only and does not own control-plane behavior.


Goal

v1.3.1 should finish the runtime work that v1.3.0 intentionally deferred, then land a small set of targeted backend follow-ups without mixing in the still-open metrics/stats and UDP broadcast design work.

This release contains:

  1. workers.count: N runtime
  2. workers.list: [...] runtime
  3. Dynamic Kubernetes service provisioning
  4. Scoped filename variables and partitioned file output
  5. SNMP migration to pysnmp 7.x

This release does not contain:

Those move to later releases because they need separate design work and cluster validation.


Design Decisions

1. Filename rendering needs scoped variables, not ad-hoc metadata tokens

This is broader than simply adding source_stem / source_suffix.

TRAM needs a clearer variable model for filename rendering:

Examples:

Implementation still belongs primarily at the shared file-sink helper layer, but the semantics must be defined first. This is not just “whatever happens to be in meta”.

The important point is:

2. Dynamic K8s service provisioning should support both manager and standalone

This should not be manager-only.

The owner should be the control-plane process:

The behavior is mode-aware:

This makes the feature generally useful for creating a dedicated external endpoint where needed, while keeping lifecycle ownership in the control plane.

3. workers.count: N and workers.list are different invariants

These two features share some placement plumbing but must not be treated as one algorithm.

count: N

list: [...]

If these are blurred together, the reconciler will be wrong.


Current Code Baseline

The schema already supports forward-compatible workers config:

The current runtime still blocks the deferred modes:

Relevant code surfaces:


Workstream A: workers.count: N

Objective

Support exact-N placement on healthy workers for valid multi-worker sources.

Runtime Model

count: N means:

Invariants

Code Changes

tram/agent/worker_pool.py

tram/agent/reconciler.py

tram/pipeline/controller.py

tram/persistence/db.py

Tests

Acceptance


Workstream B: workers.list: [...]

Objective

Support named-worker placement with pinned recovery semantics.

Runtime Model

workers.list means:

Invariants

Recovery rule

For workers.list, the pinned identity is the worker_id, not the last known URL.

Recovery logic must therefore re-resolve the current worker_url from the live _worker_ids map when a worker returns. The reconciler must not assume that the previously stored URL is still valid after pod restart, reschedule, or IP change.

Because _worker_ids is refreshed by health polling, dispatch/recovery must re-resolve from the live map at the moment of dispatch rather than blindly reusing slot.worker_url from persisted placement state.

Code Changes

tram/agent/worker_pool.py

tram/agent/reconciler.py

tram/pipeline/controller.py

Tests

Acceptance


Workstream C: Filename variables, classified routing, and dynamic partitioning

Objective

Add a proper filename-variable model for file-based sinks, separating the low-risk naming work from the heavier dynamic partitioning work.

Intended sinks

Variable scopes

Recommended initial filename-variable scopes:

Supported record-field lookup syntax:

Only dotted field syntax is supported in v1.3.1. Do not support colon-based field syntax.

Slice C1: Runtime/source-context naming plus explicit classified routing

This is the lower-risk slice and should land first.

It includes:

Examples:

filename_template: "STP_{epoch}.csv"
filename_template: "{source_stem}_{part}.csv"
sinks:
  - type: sftp
    condition: 'nf_name == "MSC"'
    filename_template: "MSC_{part}.csv"

  - type: sftp
    condition: 'nf_name == "SMSC"'
    filename_template: "SMSC_{part}.csv"

For bounded known classes, the existing per-sink condition path is the preferred approach. No new partitioning engine is required for that pattern.

Source-derived behavior

For file-based sources, derive source variables from source context:

Fallback when source filename is absent:

Slice C2: Dynamic record-field partitioning

This is the heavier slice and depends on executor-side partitioning before serialization.

Record-field behavior

If a filename template references a record field such as {field.nf_id}, the sink should use partitioned output:

Example:

filename_template: "{field.nf_id}_{timestamp}_{part}.ndjson"
file_mode: append
max_records: 1000

If incoming records contain SMSC, MME, and UPF, the sink maintains separate rolling files for each partition key.

Default partitioning rule:

Examples:

Validation / lint behavior

Do not block expert usage of arbitrary fields.

But add lint warnings for suspicious filename fields that are likely to create runaway file counts or meaningless file naming, for example:

The warning should be advisory, not blocking.

Design implications

This feature is not only token rendering.

It also requires:

Chosen architectural direction

Record-field partitioned output does not fit cleanly into the current sink contract unless partitioning happens before serialization:

write(data: bytes, meta: dict)

Today the sink receives already-serialized bytes. Partitioned output by {field.nf_id} or {field.ne_id} therefore must split records by partition key before bytes are finalized.

Chosen direction for v1.3.1:

Sink-side partitioning is explicitly out of scope for v1.3.1.

Implementation status gate

Workstream C is unblocked only with the executor-side partitioning model above. Implementation should not redesign the sink interface in v1.3.1.

Implementation

Slice C1 implementation:

Slice C2 executor work:

Shared helper change in tram/connectors/file_sink_common.py:

Model updates in tram/models/pipeline.py:

Per-sink work:

Tests

Acceptance


Workstream D: SNMP migration to pysnmp 7.x

Objective

Migrate SNMP runtime and tooling to pysnmp 7.x while preserving poll, trap, and MIB-related functionality.

Areas affected

Connector runtime:

MIB tooling:

Tests:

Implementation plan

  1. inventory all imports and runtime assumptions tied to legacy PySNMP packaging
  2. migrate connector runtime imports and API calls
  3. migrate MIB view / utility code
  4. migrate CLI and API MIB compile/download paths
  5. update dependency pins and docs
  6. update error messages that currently instruct users to install legacy PySNMP package names

Important rule

This migration is library/runtime work only.

It must not be mixed with UDP broadcast enablement in the same implementation slice.

Acceptance


Workstream E: Dynamic K8s Service Provisioning

Objective

Allow a pipeline to request a dedicated Kubernetes Service-managed endpoint when needed.

Ownership model

Control-plane owned:

Source scope

Recommended initial scope:

UDP sources stay out of scope until v1.3.2.

Mode-aware target model

Standalone:

Manager:

Worker:

To keep v1.3.1 contained, recommend starting with NodePort only.

This gives a usable endpoint-management feature without expanding immediately into:

Required design points

Pipeline config block, likely:

kubernetes:
  enabled: true
  service_type: NodePort
  node_port: 30042
  service_name: ""

Resolved decisions for v1.3.1:

Default naming rule:

Recommended default shape:

This is safer than embedding the full pipeline name without truncation logic.

Implementation areas

Lifecycle rules

Tests

Acceptance


  1. workers.count: N
  2. workers.list: [...]
  3. filename variables + classified routing (C1)
  4. dynamic field partitioning (C2)
  5. SNMP migration
  6. K8s service provisioning

This keeps the placement runtime work isolated first, then lands the independent follow-ups in separate slices.


Test Matrix

Unit

Integration

Manual / cluster validation


Risks


Review Questions

These are the points to confirm before implementation:

  1. Is the separation between count: N and workers.list invariants correct?
  2. Is the filename-variable model correctly framed as scoped variables plus partitioned file output, rather than a narrow source_stem / source_suffix token addition?
  3. Should K8s service provisioning explicitly support both standalone and manager, with worker mode excluded?
  4. Is NodePort-only the right boundary for the first K8s provisioning slice?
  5. Should SNMP migration remain strictly separate from UDP broadcast enablement?
  6. Is executor-side partitioning before serialization acceptable as the fixed v1.3.1 direction, with sink-side partitioning explicitly deferred?