SemJoin
Recover many-to-many semantic matches between an X Space and one or more Y Spaces.
The first input Space supplies X values. Remaining input Spaces form the Y side. For each X,
SemJoin emits the Y values that satisfy the prompt's relationship.
SemJoin(
name="match-needs-to-products",
input_spaces=("customer-needs", "products"),
output_space="recommended-products",
primary_key="name",
prompt=(
"Match a customer need to a product only when the product directly "
"addresses the stated need. Do not infer a match from broad category alone."
),
provider="openai",
model="gpt-5",
reasoning_effort="low",
x_batch_size=8,
y_batch_size=20,
rho=0.20,
max_matches_per_x=5,
max_candidate_pairs=10_000,
match_only_if=lambda need, product:
need["market"] == product["market"],
)
Output shape
Each output row retains the X fields and adds matches. Every entry in
matches is the complete matched Y row—not a wrapper containing a nested
value. A deterministic Transform can project the chosen catalog fields:
Transform(
name="project-selected-product",
input_spaces=("recommended-products",),
output_space="selected-products",
primary_key="name",
fn=lambda row: {
"product_id": row["matches"][0]["product_id"],
"product_name": row["matches"][0]["name"],
},
)| Parameter | Default | Meaning |
|---|---|---|
input_spaces | required | The Space or Spaces read by the task. |
output_space | required | The Space that receives the task output. |
name | generated | A stable task name used in reviews, sessions, and metrics. |
primary_key | id | The semantically meaningful field that identifies a value for this task. Rows with the same primary key represent one task value and produce one task result. Use Union first when every source identity must remain attached. |
evidence | none | Optional supporting fields shown to the semantic model. Do not repeat a primary-key field or include irrelevant metadata. |
prompt | required | The criterion or transformation written in plain language. |
provider / model | required | The model used for this task's data decisions. |
reasoning_effort | low | OpenAI GPT-5-family reasoning effort. Use none for the lowest-latency, lowest-reasoning-cost path when the task contract is precise; increase it only after representative quality evidence justifies the added time and spend. |
include_reason | true | Whether every model decision includes item-local visible evidence. The compiled prompt defaults to at most ten words; an explicit prompt request for longer visible reasoning is preserved. This is generation guidance; returned reasons are never truncated. Set false only for a validated task where result and confidence suffice. |
confidence_threshold | operator default | Exact boundary between unresolved and accepted evidence. Relifold compiles this value and operator-specific confidence guidance into the effective judge prompt. |
function_parameters | registered gate defaults | Typed values for a generated semantic pair predicate such as merge_only_if, ancestor_only_if, match_only_if, or relate_only_if. The predicate declares the schema and finite candidates; reviewed evidence can then support parameter advice. Closed library gates do not take these bindings. |
max_llm_calls | unlimited | A hard task-level call ceiling. The task stops instead of silently exceeding it. |
max_cost_usd | unlimited | A hard task-level model-cost ceiling. A session-level ceiling may be stricter. |
max_output_tokens | 2048 | Maximum generated tokens per model request. Increase it when one legitimate batch cannot fit its structured result. |
| Parameter | Default | Meaning |
|---|---|---|
x_batch_size | operator default | Number of X values in one request. |
y_batch_size | operator default | Number of Y candidates in one request. |
rho | 0.0 | Additional quality-check allowance relative to initial semantic calls. A value of 0.20 allows up to 20% additional checks. |
max_matches_per_x | unlimited | Maximum Y matches emitted for each X. |
max_matches_per_y | unlimited | Maximum X matches emitted for each Y. |
exact_x | operator default | Fill each X to its declared match bound using evaluated candidates only. |
exact_y | operator default | Fill each Y to its declared match bound using evaluated candidates only. |
max_candidate_pairs | unlimited | Fail before semantic calls when unresolved eligible X/Y pairs exceed this work ceiling. |
match_only_if | none | A deterministic eligibility gate applied before semantic comparison. |
Primary keys across both sides
The declared primary-key columns must exist on both X and Y because the same key shape
identifies values independently on each side. When the two Spaces use different names for their
central fields, first use deterministic transforms to copy them to a common alias, such as
semantic_value. Keep role-specific descriptions as evidence; do not build a composite
key from columns that exist on only one side.
Cost shape
A join considers an X-by-Y candidate rectangle. Reduce it before semantic work: filter
ineligible rows, use category or time gates, and keep only fields needed for the match. Bounds such
as max_matches_per_x describe the result you want; they are not substitutes for a
good eligibility predicate. max_candidate_pairs is a final pre-run guard over the
unresolved eligible hand after compatible prior evidence is restored; it fails rather than
silently returning a partial relationship result.
A rejected match_only_if pair is pair-local. It does not imply that another X or Y
cannot match the same endpoint. The separate cardinality bounds can retire remaining candidates
only after an endpoint reaches its declared match capacity.
Maximum versus exact cardinality
max_matches_per_x=3 with exact_x=false emits zero through three
supported matches. With exact_x=true, every X must emit exactly three matches; the
operator fills any deficit from the least-negative evaluated candidates. Use exactness only when
the business output requires that count for every X. If no match is valid, or the instruction says
“at most” or “only supported matches,” leave exactness false. Failed, exhausted, and budget-skipped
pairs are never converted into matches. If evaluation does not cover enough candidates, the
declared exact count remains unmet and is visible in the task output and failed-call metrics; treat
that as a failed business invariant rather than accepting a partial result.
SemJoin versus SemMatching
Use SemJoin when values may have many matches. Use
SemMatching when the output itself must be one-to-one, optionally perfect.