gardener¶
Deterministic planning from needs and offers.
coalitionkit_gardener ¶
The Gardener's deterministic planning core.
Offer
dataclass
¶
One participant's offer for one task: minutes, money in minor units, effort.
Overlap
dataclass
¶
The slots two or more participants share, as PSI revealed them.
Problem
dataclass
¶
Everything the strategies read.
shares are positive integer weights; a participant without one cannot be assigned.
answers maps (participant, task_id) to a vault reply: "no" removes the bid, a
Cost replaces the offered cost, "yes" or no reply leaves the offer as it is.
max_ratio caps hours per share unit for any participant (None means no cap).
Change
dataclass
¶
Tasks that changed directly, and participants who left or can no longer serve.
AuctionStrategy
dataclass
¶
Contract-Net: each task goes to the bid with the lowest cost plus lambda times the
bidder's share ratio after winning, within the Problem.max_ratio cap.
The search is depth-first over bids in score order, so the first complete allocation is the
greedy one and a dead end backtracks. Ties break by participant ID. After node_limit
nodes without a complete allocation it falls back to a greedy partial one.
MatchStrategy
dataclass
¶
Assigns each task the earliest slot every party shares, from a PSI overlap result.
Outcome
dataclass
¶
What a strategy chose (task to assignee, slot or option) and what it could not.
Strategy ¶
Bases: Protocol
Settles task_ids while the fixed assignments stay as they are.
Unresolved
dataclass
¶
A task a strategy could not settle. options lists the tied choices, if any.
VoteStrategy
dataclass
¶
Plurality over Problem.ballots. A tie settles nothing and lists the tied options.
TradeoffPolicy
dataclass
¶
A plan set holds a tension when an objective's (max - min) / max reaches min_gap.
build_propose ¶
build_propose(
plan_set: PlanSet,
*,
deadline: str,
proposal_set_id: str,
revision: int = 1,
) -> ProposePayload
The coalition.propose payload for a non-empty plan set.
decision_deadline ¶
An absolute RFC 3339 UTC deadline window after at.
dominates ¶
a is no worse everywhere and strictly better somewhere (all objectives minimise).
generate_plans ¶
generate_plans(
problem: Problem,
third: ThirdObjective | None = None,
config: PlanConfig | None = None,
) -> PlanSet
Up to config.max_plans complete, cap-respecting, mutually non-dominated plans.
invalidated ¶
The directly changed tasks, the tasks held by a departed participant, and every task that depends on them, directly or through other tasks. Nothing else.
replan ¶
replan(
problem: Problem,
current: Assignments,
invalid: frozenset[str],
*,
departed: frozenset[str] = frozenset(),
strategy: Strategy | None = None,
) -> ReplanResult
Re-plan only invalid tasks against the updated problem. Every other assignment is
returned unchanged and counts toward the fairness load.
ripple_payload ¶
The coalition.ripple payload; task IDs must be ULIDs.
tension_for_plan_set ¶
tension_for_plan_set(
plan_set: PlanSet,
*,
deciders: Sequence[str],
deadline: str,
policy: TradeoffPolicy | None = None,
revision: int = 1,
) -> list[TensionPayload]
Tensions for an empty plan set (hard constraints) or for a set with big trade-offs.
tension_from_plans ¶
tension_from_plans(
plan_set: PlanSet,
*,
deciders: Sequence[str],
deadline: str,
policy: TradeoffPolicy | None = None,
revision: int = 1,
) -> TensionPayload | None
A tension when the Pareto plans differ a lot on some objective, else None.
tensions_from_unresolved ¶
tensions_from_unresolved(
unresolved: Sequence[Unresolved],
*,
deciders: Sequence[str],
deadline: str,
revision: int = 1,
) -> list[TensionPayload]
One tension per tied vote, plus one for all tasks no plan can cover.