This document provides a detailed technical specification for the commitment logic and liability calculation algorithm used in the Carrots application.
The liability calculation is based on the game-theoretic framework described in: "Game-theoretic approaches to conditional commitment" (https://www.mdpi.com/2073-4336/16/6/58)
- Conditional Commitment: A promise to perform an action, contingent on others meeting certain conditions
- Liability: The actual obligation a person has after all commitments are evaluated
- Fixed-Point: A stable state where no liabilities change when commitments are re-evaluated
Each commitment consists of two parts:
{
condition: {
type: 'single_user' | 'aggregate',
targetUserId?: string, // For single_user type
action: string,
minAmount: number,
unit: string
},
promise: {
action: string,
minAmount: number,
unit: string
}
}A commitment based on one specific person's action:
"If Alice does at least 5 hours of work, I will do at least 3 hours of work"
Structure:
{
"condition": {
"type": "single_user",
"targetUserId": "alice-id",
"action": "work",
"minAmount": 5,
"unit": "hours"
},
"promise": {
"action": "work",
"minAmount": 3,
"unit": "hours"
}
}A commitment based on combined actions of all others:
"If others collectively do at least 10 hours of work, I will do at least 5 hours of work"
Structure:
{
"condition": {
"type": "aggregate",
"action": "work",
"minAmount": 10,
"unit": "hours"
},
"promise": {
"action": "work",
"minAmount": 5,
"unit": "hours"
}
}For each user i and action a, the liability L_i(a) is defined as:
L_i(a) = max { c_i(a, C_j) | j ∈ active commitments, condition(C_j) is satisfied }
Where:
L_i(a)= liability of user i for action ac_i(a, C_j)= promised amount from commitment C_j- Condition satisfaction depends on current liabilities (creating interdependency)
Since commitments can depend on other commitments, we use iterative calculation:
- Initialize: Set all liabilities to 0
- Iterate:
- For each commitment C:
- Evaluate condition based on current liabilities
- If condition is met, update promise liability
- Check for convergence
- For each commitment C:
- Converge: Stop when liabilities no longer change
- Result: Return final liabilities
function calculateLiabilities(commitments, users):
// Initialize
L = {}
for each user u in users:
L[u] = {} // Empty action map
// Extract all actions
actions = extractAllActions(commitments)
// Initialize all liabilities to 0
for each user u in users:
for each action a in actions:
L[u][a] = 0
// Fixed-point iteration
iterations = 0
maxIterations = 100
do:
L_prev = copy(L)
for each commitment C in commitments:
conditionMet = evaluateCondition(C.condition, L)
if conditionMet:
creator = C.creator
action = C.promise.action
amount = C.promise.minAmount
// Take maximum (monotonic)
L[creator][action] = max(L[creator][action], amount)
iterations++
while not converged(L, L_prev) and iterations < maxIterations
if iterations >= maxIterations:
throw Error("Did not converge")
return L
function evaluateSingleUserCondition(condition, L):
targetUser = condition.targetUserId
action = condition.action
minAmount = condition.minAmount
currentLiability = L[targetUser][action] || 0
return currentLiability >= minAmount
function evaluateAggregateCondition(condition, L):
action = condition.action
minAmount = condition.minAmount
totalLiability = 0
for each user u in L:
totalLiability += L[u][action] || 0
return totalLiability >= minAmount
Scenario:
- Alice: "If Bob does 5 hours, I'll do 3 hours"
- Bob: "I'll do 5 hours unconditionally" (via external commitment or baseline)
Calculation:
- Initial: Alice = 0, Bob = 0
- Iteration 1:
- Bob's condition met (unconditional): Bob = 5
- Iteration 2:
- Alice's condition met (Bob >= 5): Alice = 3
- Bob still 5
- Converged!
Result: Alice = 3 hours, Bob = 5 hours
Scenario:
- Alice: "If Bob does 3 hours, I'll do 3 hours"
- Bob: "If Alice does 3 hours, I'll do 3 hours"
Calculation:
- Initial: Alice = 0, Bob = 0
- Iteration 1:
- Alice's condition not met (Bob = 0)
- Bob's condition not met (Alice = 0)
- Converged at zero
Result: Alice = 0 hours, Bob = 0 hours
Scenario:
- Alice: "If others do 2 hours total, I'll do 5 hours"
- Bob: "If others do 2 hours total, I'll do 3 hours"
- Charlie: "If others do 2 hours total, I'll do 2 hours"
Calculation:
- Initial: All = 0
- Iteration 1:
- No conditions met (total = 0)
- Converged at zero
Alternative Scenario - Add Bob's unconditional 1 hour:
- Initial: Alice = 0, Bob = 1, Charlie = 0
- Iteration 1:
- Total others for Alice = 1 (not enough)
- Total others for Bob = 0 (not enough)
- Total others for Charlie = 1 (not enough)
- Converged at: Alice = 0, Bob = 1, Charlie = 0
Scenario:
- Alice: "If Bob does 2 hours, I'll do 5 hours"
- Bob: "If others do 3 hours, I'll do 2 hours"
- Charlie: "I'll do 3 hours" (unconditional)
Calculation:
- Initial: Alice = 0, Bob = 0, Charlie = 3
- Iteration 1:
- Bob's condition met (others = 3): Bob = 2
- Iteration 2:
- Alice's condition met (Bob = 2): Alice = 5
- Bob still = 2
- Converged!
Result: Alice = 5, Bob = 2, Charlie = 3
- Result: All liabilities are 0
- Result: All liabilities are 0
- Detection: After MAX_ITERATIONS without convergence
- Handling: Throw error, log warning
- Prevention: Set reasonable MAX_ITERATIONS (e.g., 100)
- Handling: Take maximum (monotonic operator)
- Tracking: Record all effective commitment IDs
- Current: No unit conversion
- Assumption: Same action should use consistent units
- Future: Add unit validation or conversion
The algorithm is monotonic - liabilities never decrease:
L[i][a] = max(L[i][a], promised_amount)
This ensures convergence in finite iterations.
We use a small threshold (0.001) to detect convergence:
|L_new - L_old| < threshold
The algorithm is guaranteed to converge because:
- Liabilities are bounded (finite number of commitments)
- Updates are monotonic (only increase)
- Maximum operator ensures stability
- Time: O(n * m * k)
- n = number of iterations (typically < 10)
- m = number of commitments
- k = number of users
- Space: O(u * a)
- u = number of users
- a = number of unique actions
- Early Termination: Stop when no changes occur
- Caching: Store intermediate results
- Indexing: Index commitments by condition type
- Lazy Evaluation: Only recalculate affected liabilities
- Test individual condition evaluation
- Test liability update logic
- Test convergence detection
- Test edge cases
- Test with real commitment data
- Test various commitment patterns
- Test convergence scenarios
- Test performance with large datasets
- Test monotonicity property
- Test convergence property
- Test idempotency (running twice gives same result)
- Weighted Commitments: Different priorities or weights
- Time-Based Conditions: Deadlines and time windows
- Partial Fulfillment: Track actual vs. liability
- Nested Conditions: More complex logical expressions
- Probabilistic Commitments: Uncertainty in conditions
- Original paper: https://www.mdpi.com/2073-4336/16/6/58
- Fixed-point theory: https://en.wikipedia.org/wiki/Fixed-point_theorem
- Game theory basics: https://plato.stanford.edu/entries/game-theory/