synalinks-control-flow/SKILL.md
Use when routing or composing Synalinks programs — Decision, Branch (return_decision, inject_decision), parallel branches via asyncio, self-consistency with multiple Generators + temperature, XOR input/output guard patterns, And/Or modules, merging branches with `|`, or anywhere you need conditional execution paths in a Program graph.
npx skillsauth add synalinks/synalinks-skills synalinks-control-flowInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Patterns for routing, parallelism, conditional execution, and guards in Synalinks programs.
Control flow in Synalinks is implemented through:
+ & | ^) on DataModels — see synalinks-coreNone short-circuit downstream &/+ operationsAPI conventions
- All module constructors take keyword-only arguments — call as
Decision(question=..., labels=..., language_model=lm), never positionally.language_modelmay be omitted if a process-wide default is set viasynalinks.set_default_language_model(lm);ops.predictresolves it at call time.LanguageModel/EmbeddingModelaccept**default_kwargs(e.g.temperature,top_p,max_tokens) forwarded to every call.
Single-label classification for routing. Output includes a choice field constrained to the provided labels (plus a thinking field with step-by-step reasoning).
decision = await synalinks.Decision(
question="What type of query is this?",
labels=["factual", "opinion", "creative"],
language_model=lm,
)(inputs)
# Output: { "thinking": "...", "choice": "factual" | "opinion" | "creative" }
labels constrain the LLM output (a dynamic Enum on the choice field),
preventing hallucination of new categories. Note: Decision does not accept
return_inputs; concatenate manually with inputs & decision if you need
the original input alongside the choice.
All Synalinks module constructors are keyword-only (* after self), so
always pass arguments by name.
Conditional routing to one of N modules based on a Decision.
(easy_answer, hard_answer) = await synalinks.Branch(
question="Evaluate query difficulty",
labels=["easy", "difficult"],
branches=[
synalinks.Generator(data_model=SimpleAnswer, language_model=lm),
synalinks.Generator(data_model=DetailedAnswer, language_model=lm),
],
language_model=lm,
return_decision=True, # Default True — decision is concatenated into each branch output
inject_decision=True, # Default True — decision is injected into each branch's inputs
)(inputs)
# Merge with logical or — non-activated branches are None
final = easy_answer | hard_answer
Key Branch behaviors:
None (not just empty — they don't run at all)labels constrain LLM output to valid choicesreturn_decision=True does not add an extra tuple element — it concatenates the decision into each selected branch's output, so destructuring stays the same:
(easy, hard) = await synalinks.Branch(
...,
return_decision=True,
)(inputs)
# easy / hard now include the decision fields
(easy, hard) = await synalinks.Branch(
...,
inject_decision=True, # Each branch receives inputs + decision before computation
)(inputs)
Multiple modules consuming the same input run concurrently via asyncio:
x1 = await synalinks.Generator(data_model=Answer, language_model=lm)(inputs)
x2 = await synalinks.Generator(data_model=Answer, language_model=lm)(inputs)
# Both run in parallel — combine afterwards
combined = x1 + x2
Generate multiple answers with temperature > 0, then merge them:
async def build_self_consistency_program(lm):
inputs = synalinks.Input(data_model=Query)
b0 = await synalinks.Generator(data_model=AnswerWithRationale, language_model=lm, temperature=1.0)(inputs)
b1 = await synalinks.Generator(data_model=AnswerWithRationale, language_model=lm, temperature=1.0)(inputs)
b2 = await synalinks.Generator(data_model=AnswerWithRationale, language_model=lm, temperature=1.0)(inputs)
merged = b0 & b1 & b2 # Concatenate (auto-renames colliding fields)
outputs = await synalinks.Generator(
data_model=AnswerWithRationale,
language_model=lm,
instructions="Critically analyze the given answers to produce the final answer.",
)(inputs & merged)
return synalinks.Program(inputs=inputs, outputs=outputs)
Use ^ to bypass computation conditionally. The truth table for x1 ^ x2:
| x1 | x2 | Result | |----|----|--------| | set | set | None | | set | None | x1 | | None | set | x2 | | None | None | None |
class InputGuard(synalinks.Module):
"""Block invalid inputs."""
async def call(self, inputs, training=False):
if self._is_blocked(inputs):
return synalinks.ChatMessage(role="assistant", content="Cannot process this request")
return None # Allow through
async def build_guarded_program(lm):
inputs = synalinks.Input(data_model=synalinks.ChatMessages)
warning = await InputGuard()(inputs)
# XOR: if warning exists, inputs becomes None (bypassing generator)
guarded_inputs = warning ^ inputs
# Generator only runs if guarded_inputs is not None
answer = await synalinks.Generator(language_model=lm)(guarded_inputs)
# OR: return warning if it exists, otherwise return answer
outputs = warning | answer
return synalinks.Program(inputs=inputs, outputs=outputs)
async def build_output_guarded_program(lm):
inputs = synalinks.Input(data_model=synalinks.ChatMessages)
answer = await synalinks.Generator(language_model=lm)(inputs)
warning = await OutputGuard()(answer)
# XOR + OR: if warning exists, replace answer with warning
outputs = (answer ^ warning) | warning
return synalinks.Program(inputs=inputs, outputs=outputs)
Equivalent to & and | operators, but accept N inputs at once:
merged = await synalinks.And()([b0, b1, b2])
result = await synalinks.Or()([b0, b1, b2])
| Pattern | When to use |
|---------|-------------|
| b1 \| b2 | One of two branches activates — pick whichever is non-None |
| b1 & b2 | Both required — None if either is None |
| b1 + b2 | Both required, strict — raises if either is None |
| await synalinks.Or()([b0, b1, b2, ...]) | N branches, pick non-None |
| await synalinks.And()([b0, b1, b2, ...]) | All N required |
return_decision=True does NOT change tuple length — it concatenates the decision into each branch output. Destructure as usual.None for skipped paths so ^ and & operators short-circuit correctly.Branch trains each branch independently — small specialist modules can outperform a single big module on routed sub-tasks.+ after a Branch — non-activated branches are None, which makes + raise. Use | instead.development
Use when training Synalinks programs — program.compile() / fit() / evaluate() / predict(), validation_split, validation_data, batch_size, epochs, callbacks (ProgramCheckpoint, custom Callback subclasses), History, in-context reinforcement learning workflow. For reward functions see synalinks-rewards; for optimizer internals see synalinks-optimizers.
development
Use when configuring or writing Synalinks reward functions and metrics — ExactMatch, CosineSimilarity, LMAsJudge, ProgramAsJudge, RewardFunctionWrapper, custom reward functions (async, register_synalinks_serializable), in_mask / out_mask filtering, F1Score / FBetaScore / BinaryF1Score / ListF1Score metrics, MeanMetricWrapper, or whenever you're shaping the signal that drives optimization.
tools
Use when integrating Synalinks with LM providers — picking the right model prefix (openai/, anthropic/, ollama/, groq/, cohere/, openrouter/, bedrock/, deepseek/, together_ai/, doubleword/, hosted_vllm/ (alias vllm/), gemini/, xai/, mistral/, azure/), env vars per provider, structured-output dispatch (constrained json_schema vs tool-call), local OpenAI-compatible servers (LMStudio, vLLM) requiring litellm.register_model and a dummy OPENAI_API_KEY, and OpenRouter embeddings (LiteLLM doesn't support them — use OpenRouterEmbeddingModel).
development
Use when building or composing a Synalinks Program — the four building APIs (Functional, Sequential, Subclassing, Mixed), Input nodes, multi-input/multi-output graphs, the call/build lifecycle, training=True/False semantics, summary, get_module, plot_program, save/load, get_state_tree/set_state_tree, get_config/from_config and custom serialization. For DataModel/Field, JSON operators (+ & | ^ ~), and LanguageModel/EmbeddingModel basics see synalinks-core. For inner modules see synalinks-modules; for compile/fit/evaluate/predict see synalinks-training.