feat: Milestone 6 — mechanism layer v1#11
Merged
Conversation
Implements the full mechanism library in leadforge/mechanisms/:
base.py — Mechanism ABC, MechanismContext, MechanismSummary,
MechanismAssignment
static.py — CategoricalDraw, BoundedNumericDraw, MixtureDraw
influence.py — AdditiveInfluence, LogisticInfluence, SaturatingInfluence,
ThresholdInfluence, InteractionTerm
scores.py — LatentScore (logistic from weighted latent combination)
hazards.py — ConversionHazard (daily P(convert) = base + scale*score)
transitions.py — StageSequence (funnel order + terminal detection),
HazardTransition (dwell-aware daily advancement hazard)
counts.py — PoissonIntensity (log-linear), RecencyDecayIntensity (decay+floor)
categorical.py — CategoricalInfluence, CHANNEL_QUALITY_SCORES
measurement.py — NoisyProxy, NoisyCategorization, ProxyCompression
policies.py — assign_mechanisms(): motif-family-aware MechanismAssignment
factory with tuned score weights for all 5 v1 motif families
74 tests covering all mechanism types, serialisation roundtrips, monotonicity
properties, edge-case validation, and cross-motif hazard ordering.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Introduces the Milestone 6 “mechanism layer v1” into leadforge/mechanisms/, providing a composable set of stochastic/deterministic mechanism primitives (scores, hazards, transitions, intensities, proxies) plus a motif-family-aware wiring policy, with a comprehensive new test suite.
Changes:
- Added mechanism base contracts (
Mechanism,MechanismContext,MechanismAssignment,MechanismSummary) and multiple mechanism families (static draws, influences, scores/hazards, transitions, counts, categorical lookup, measurement/proxy transforms). - Added
assign_mechanisms()policy to construct a fully wired mechanism assignment per motif family, plus an inspection helper. - Added a large test module covering validation, ranges, serialization, determinism, and cross-motif ordering expectations.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| leadforge/mechanisms/base.py | Defines the core mechanism interface, shared context carrier, and serializable assignment/summary containers. |
| leadforge/mechanisms/static.py | Implements static distribution draws (categorical, bounded Gaussian, mixture). |
| leadforge/mechanisms/influence.py | Implements latent-to-latent influence transforms (additive/logistic/saturating/threshold/interaction). |
| leadforge/mechanisms/scores.py | Implements LatentScore for deterministic logistic scoring over latent weights. |
| leadforge/mechanisms/hazards.py | Implements ConversionHazard as a daily boolean event driven by latent score. |
| leadforge/mechanisms/transitions.py | Implements stage sequencing and a dwell-aware advancement hazard. |
| leadforge/mechanisms/counts.py | Implements Poisson-like count/intensity mechanisms used for event generation. |
| leadforge/mechanisms/categorical.py | Adds categorical lookup-based influence and default channel quality scores. |
| leadforge/mechanisms/measurement.py | Implements noisy proxy observation and compression to categorical tiers. |
| leadforge/mechanisms/policies.py | Wires motif-specific parameter tables into a complete MechanismAssignment. |
| tests/mechanisms/test_mechanisms.py | Adds broad test coverage for parameter guards, output bounds, serialization, determinism, and policy wiring. |
| tests/mechanisms/init.py | Adds the test package marker for mechanisms tests. |
| .agent-plan.md | Updates the milestone tracking/status to reflect M6 completion and M7 focus. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This comment has been minimized.
This comment has been minimized.
- MixtureDraw: guard against negative mix_weights (COPILOT-1) - ProxyCompression: strict adjacent-pair increase + (0,1) range checks (COPILOT-2) - LatentScore.score: numerically stable two-branch sigmoid (COPILOT-3) - influence._sigmoid: numerically stable two-branch sigmoid (COPILOT-4) - Tests: overflow, duplicate/boundary threshold, negative weight cases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
pr-agent-context report: This run includes unresolved review comments on PR #11.
For each unresolved review comment, recommend one of: resolve as irrelevant, accept and implement
the recommended solution, open a separate issue and resolve as out-of-scope for this PR, accept and
implement a different solution, or resolve as already treated by the code.
After I reply with my decision per item, implement the accepted actions, resolve the corresponding
PR comments, and push all of these changes in a single commit.
# Copilot Comments
## COPILOT-1
Location: leadforge/mechanisms/static.py:125
URL: https://github.com/leadforge-dev/leadforge/pull/11#discussion_r3151683664
Root author: copilot-pull-request-reviewer
Comment:
`MixtureDraw` normalizes `mix_weights` but does not reject negative weights. Passing a negative value will later raise from `random.choices` at sample-time; validate `mix_weights` are all non-negative (and ideally mirror `CategoricalDraw`'s checks) so failures are early and consistent.
## COPILOT-2
Location: leadforge/mechanisms/measurement.py
URL: https://github.com/leadforge-dev/leadforge/pull/11#discussion_r3151683683
Status: outdated
Root author: copilot-pull-request-reviewer
Comment:
`ProxyCompression` claims thresholds must be *strictly* increasing, but the check `thresholds != sorted(thresholds)` allows duplicates (e.g. `[0.5, 0.5]`) and does not enforce thresholds are within (0, 1) as documented. Tighten validation to require `0 < t < 1` and `thresholds[i] < thresholds[i+1]` for all adjacent pairs.
## COPILOT-3
Location: leadforge/mechanisms/scores.py
URL: https://github.com/leadforge-dev/leadforge/pull/11#discussion_r3151683693
Status: outdated
Root author: copilot-pull-request-reviewer
Comment:
`LatentScore.score()` uses `1 / (1 + exp(-linear))`, which can raise `OverflowError` for sufficiently negative `linear`. Consider a numerically-stable sigmoid implementation (branch on sign or use `math.exp` in a stable form) so extreme weights/biases don't crash scoring.
## COPILOT-4
Location: leadforge/mechanisms/influence.py
URL: https://github.com/leadforge-dev/leadforge/pull/11#discussion_r3151683705
Status: outdated
Root author: copilot-pull-request-reviewer
Comment:
`_sigmoid()` uses `1 / (1 + exp(-x))`, which can overflow for large negative `x` and crash callers. Switching to a numerically-stable sigmoid implementation avoids runtime errors for extreme parameterizations.Run metadata: |
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.
Summary
mechanisms/base.py—MechanismABC (sample(context, rng)),MechanismContext(latents + stage + t + extra),MechanismSummary(serialisable),MechanismAssignment(named instances for sim engine)mechanisms/static.py—CategoricalDraw,BoundedNumericDraw,MixtureDrawmechanisms/influence.py—AdditiveInfluence,LogisticInfluence,SaturatingInfluence,ThresholdInfluence,InteractionTermmechanisms/scores.py—LatentScore: logistic score from weighted latent combination; exposes.score()for deterministic usemechanisms/hazards.py—ConversionHazard:base_rate + scale * score, hard-capped atmax_daily_ratemechanisms/transitions.py—StageSequence: funnel stage registry + terminal detection;HazardTransition: dwell-aware daily advancement hazardmechanisms/counts.py—PoissonIntensity(log-linear latent modulation),RecencyDecayIntensity(decay + floor)mechanisms/categorical.py—CategoricalInfluence,CHANNEL_QUALITY_SCORESmechanisms/measurement.py—NoisyProxy,NoisyCategorization,ProxyCompressionmechanisms/policies.py—assign_mechanisms(motif_family, rng): builds a fully wiredMechanismAssignmentwith motif-tuned parameters for all 5 v1 motif families;mechanism_params_for_motif()for inspectionTest plan
tests/mechanisms/test_mechanisms.pyLatentScore: monotonicity, missing-key zero-contribution, empty-weights guardConversionHazard: probability bounds, monotone in fit, invalid-param guardsHazardTransition: min-dwell blocks at zero, invalid-param guardsStageSequence: next-stage chaining, terminal detectionPoissonIntensity/RecencyDecayIntensity: non-negative counts, decay monotonicity, floorassign_mechanismsfor all 5 motif families: callable, serialisable, summary roundtrip🤖 Generated with Claude Code