New helpers - #237
Merged
Merged
Conversation
A payload is an any, because a mesh carries mixed types down one pipe. Every component that reads one therefore has to get it back out, and a bare type assertion is either unchecked -- and takes down the whole run when something upstream changes its payload type -- or checked, and three lines every time. As[T] reports what went wrong, AsOrDefault carries on with a default, and neither panics on a nil signal or a nil payload. The per-type shorthands wrap only the fallible form: AsOrDefault already infers T from the default, so an AsIntOrDefault would be longer than the generic it wraps. AsFloat64OrDefault is the one exception, because an untyped 0 infers int and AsOrDefault(s, 0) would silently return the default for a perfectly good float64 payload. AsNumber is the loose one, widening float32/int/int64/uint64 and encoding bool as 1 and 0. It answers "is this a measurement at all" for code that does not know which components produce which payloads -- a structured signal often uses its payload as a type tag and keeps the values in scalars. The README, the runnable Example and the quick-start now read payloads this way instead of asserting on PayloadOrNil. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wiring code reads as a list of edges, and a bare error from PipeTo gives no clue which of a dozen lines produced it. MultiPipe takes the list and names the edge that failed; MultiForward does the same for the forwarding case, where signals are copied immediately rather than a connection registered. A nil port on either end is reported rather than dereferenced. That is the common case, not a defensive nicety: OutputByName and InputByName return nil for a name no port has, so without the check a typo in a wiring list surfaces as a nil dereference somewhere well past the typo, with nothing naming it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A component has one activation function, but the thing it does is often
several things in a fixed order -- read the controls, advance the state,
publish the result -- and writing them as one closure is how a component
grows to two hundred lines with no seam anywhere. Sequential, When,
RequireInputs and Pipeline build the single function a component is
constructed with. They are values passed to WithActivationFunc, not hooks:
OnActivation stays the tool for behavior added from outside the component.
When and RequireInputs guard on inputs differently, and the distinction is
easy to get backwards. When skips the activation, so a partial set of inputs
is cleared at drain time and never comes back; RequireInputs suspends and
keeps what already arrived.
Port names are resolved before any signal is looked at. Collection.ByNames
silently skips names it cannot find, and AllHaveSignals on the resulting
empty collection is vacuously true, so ByNames("typo").AllHaveSignals()
reports ready and the guard quietly becomes a no-op. RequireInputs fails on
such a name rather than suspending the component for the rest of the run --
nothing ever arrives on a port that does not exist -- and every name is
checked before any signal, or a port that is merely empty hides the typo
behind a wait. Pipeline likewise errors instead of dereferencing a nil port.
Pipeline reads its inputs in the order named rather than through the
collection's map iteration, so a stage that folds or pairs up its group gets
the caller's order rather than an arbitrary one, and a stage returning a nil
group is an error rather than a nil dereference at the output port.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
design.md gains the invariant behind the guard bug the combinators had to fix: lookups by name are silently forgiving -- ByName returns nil, ByNames skips, and Every on the empty result is vacuously true -- so anything taking port names as strings must resolve them all before asking about signals. It also records why AsFloat64OrDefault survives when its siblings were dropped, so nobody restores the symmetry, and adds signal.As[T] to the approved-generics list, which the accessors had quietly broken. Comment hygiene gains a length rule. The three new files were first written with multi-paragraph headers and per-function essays; that material is documentation, and of the two copies the one in source is the one that rots. One line is the default, a short second paragraph the ceiling, and anything longer belongs in docs/wiki. The files were compacted to match: 46 lines of comment removed, every constraint kept. hooks.md separates OnActivation hooks from the combinators, naming.md covers combinator naming and the component-level predicate, and testing.md requires covering the name that resolves to no port -- the case whose absence let the vacuous-truth bug through. Also fixes signal.Group.ForEach in four wiki snippets that showed it returning (*Group, error). It returns error only, so none of them compiled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
hovsep
enabled auto-merge (rebase)
July 30, 2026 17:40
Benchmark report (advisory)Statistically significant changes: 9 regression(s), 17 improvement(s) (PR head vs. base branch).
Higher is worse on every metric. Full benchstat table |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces several improvements and new features to the component activation and data flow system, as well as updates to the documentation and tests. The main focus is on providing a set of composable activation function combinators (
Sequential,When,RequireInputs,Pipeline) incomponent/compose.go, clarifying the behavior of port name resolution, and updating documentation and examples to use safer, more ergonomic signal payload accessors. Tests are added to ensure correct behavior, and documentation is expanded to clarify design constraints and best practices.New Activation Function Combinators and Port Name Handling:
component/compose.gowith composable activation function combinators:Sequential,When,HasSignalsOn,RequireInputs, andPipeline, enabling flexible and safe composition of component logic. These combinators handle port existence checks, error propagation, and input ordering, and provide clear error messages for missing ports.Documentation and Example Improvements:
.agent/docs/design.mdand.agent/docs/naming.mdto describe the new combinators, the rationale for short, constraint-focused comments, and the importance of explicit error handling for unresolved port names and signal payload types. [1] [2]README.mdand quick-start wiki to usesignal.AsOrDefaultandsignal.AsInt, replacing unsafe type assertions with error-checked or default-returning accessors. This makes example code safer and more idiomatic. [1] [2] [3] [4]Testing Enhancements:
component/compose_test.gocovering all new combinators, including edge cases such as missing ports, nil groups, and error propagation. Tests ensure that combinators behave as documented and fail safely on invalid input.Other Notable Updates:
component/doc.goreferencing the new combinators for discoverability.These changes collectively improve the safety, composability, and clarity of the component activation system, and ensure that both the implementation and documentation are robust against common errors.