Skip to content
github-actions[bot] edited this page Jul 23, 2026 · 2 revisions

Overview

In F-Mesh, the data exchanged between components is represented as signals — flexible units of information that can carry any type of payload. Signals serve as the foundation for communication within the mesh, enabling the dynamic flow of data between components.

Each signal consists of three main elements:

  1. Payload - The actual data being transferred (can be any Go type)
  2. Labels - Optional string key-value metadata for categorization and filtering
  3. Scalars - Optional numeric key-value metadata (stringfloat64)

To explore the full capabilities of signals, refer to the signal package API.

Immutability (copy-on-write)

Signals are immutable. Mutating methods such as WithLabel, WithLabels, or MapPayload return a new signal; the original is never modified. This is important when signals are fanned out through pipes — multiple ports may share the same signal pointer, and immutability prevents one component from corrupting another's view of the data.

original := signal.New("hello")
tagged := original.WithLabel("source", "sensor1")

// original is unchanged
original.Labels().Has("source") // false
tagged.Labels().Has("source")   // true

When you need to modify a signal that may be shared, always assign the result of a With* method:

sig = sig.WithLabel("processed", "true")

Payload

The payload of a signal can be any valid Go data type, offering immense flexibility. This includes primitive types like integers and strings, complex types like structs and maps, or even nil.

It's important to note that every signal in F-Mesh always has a payload, even if that payload is nil. There is no concept of an "empty" signal, ensuring consistency in data handling across the mesh.

Example of creating a simple signal:

mySignal := signal.New("example payload") // A signal with a string payload

Signal Groups

While individual signals are useful for most cases, there are scenarios where working with a group of signals simplifies the design. A signal group aggregates multiple signals, each potentially carrying a different type of payload.

// Create a group from multiple payloads
mySignals := signal.NewGroup(1, nil, 3, []int{4, 5, 6}, map[byte]byte{7: 8})

// Work with signals in a port (also a group)
port.Signals().Filter(signal.HasLabel("priority"))

Signal groups are part of F-Mesh's consistent Collections and Groups API. For complete documentation on filtering, iterating, and working with groups, see the Collections and Groups page.

Labels

Signals can be tagged with labels - key-value pairs that provide metadata about the signal without affecting the payload. Labels are useful for categorization, filtering, routing, and debugging.

Because signals are copy-on-write, use With* methods to add labels:

// Create signal with labels
sig := signal.New("data").WithLabels(map[string]string{
    "priority": "high",
    "source":   "sensor1",
})

// Or add labels one at a time
sig = sig.WithLabel("priority", "high").WithLabel("source", "sensor1")

// Query labels
if sig.Labels().ValueIs("priority", "high") {
    // Handle high priority signal
}

// Filter signals by labels using predicate helpers
highPriority := port.Signals().Filter(signal.LabelEquals("priority", "high"))

Labels are preserved when signals flow through pipes and can also be used with components and ports. For complete documentation on labels and scalars, see the Labels page.

Scalars

Signals can also carry scalars — numeric metadata stored as float64 values. Scalars are useful for weights, priorities, timestamps, or any numeric annotation.

sig := signal.New("measurement").
    WithScalar("weight", 0.85).
    WithScalar("confidence", 0.97)

weight := sig.Scalars().ValueOrDefault("weight", 0)

Signal groups support cross-entity scalar aggregation (MinScalar, MaxScalar, AvgScalar, SumScalar) — see the Collections and Groups page.

Predicate helpers

The signal package provides composable predicate helpers for filtering:

port.Signals().Filter(signal.And(
    signal.HasLabel("priority"),
    signal.LabelEquals("priority", "high"),
))

port.Signals().Filter(signal.Or(
    signal.LabelEquals("source", "sensor1"),
    signal.LabelEquals("source", "sensor2"),
))

Available helpers: Not, And, Or, HasLabel, LabelEquals, LabelContains, HasAllLabels, HasAnyLabel.

Scalar copy-on-write API

Signals support the same CoW pattern for scalars:

Method Description
WithScalar(name, value) Add or update one scalar
WithScalars(map) Add or update many
WithOnlyScalars(map) Replace all scalars
WithNoScalars() Remove all scalars
WithoutScalars(names...) Remove specific scalars

Advanced signal.Group operations

Beyond Filter and Map, groups support:

Method Description
With(signals...) / WithPayloads(...) / Join(other) Build new groups
Contains(sig) / ContainsPayload(v) Membership checks
MapPayloads(mapper) / MapPayloadsIf(pred, mapper) Transform payloads, preserve metadata
Reduce(initial, fn) / ReducePayloads(initial, fn) Fold group into single signal or value
ForEachIf(pred, action) Iterate matching signals only
Every(pred) / Any(pred) / Count(pred) Predicate queries

Function types

Type Signature Used by
Predicate func(*Signal) bool Filter, Any, Every
Mapper func(*Signal) *Signal Map, MapIf
PayloadMapper func(any) any MapPayload, MapPayloads
Reducer func(acc, s *Signal) *Signal Reduce
PayloadReducer func(acc, payload any) any ReducePayloads

Signal errors

Error When
ErrNoSignalsInGroup FirstPayload() on empty group
ErrPayloadNotComparable ContainsPayload with non-comparable type — use ContainsPayloadFunc instead

Payload() always succeeds on a valid signal; FirstPayload() returns an error on empty groups.

Clone this wiki locally