Rule-Based Household
The RuleBasedHousehold is a
deterministic-policy alternative to an LLM household. It combines stylized
models of sleep, hunger, meals, inventory replenishment, store choice, and
spatial movement. Random store choice uses the household’s injected
random.Random instance, so a fixed seed and fixed observation order are
reproducible.
This model is intended as a transparent simulation heuristic. Its parameters are not estimated automatically and its \((s,S)\) rule is not the solution of a household dynamic program.
Configuration overview
A typical configuration has the following structure:
"Household": {
"type": "RuleBasedHousehold",
"isHousehold": true,
"foodItems": ["Rice", "Chocolate", "Sushi"],
"cashName": "Yen",
"startHour": 0.0,
"stepHours": 1.0,
"sleepRule": { ... },
"mealRule": { ... },
"sSinventoryRule": { ... },
"budgetRule": { ... },
"pricePriors": { ... },
"itemImportance": { ... },
"storeChoice": { ... }
}
The agent should request the observations required by its policy. In
particular, others_pos supplies store locations and
others_inventory supplies offers observed after co-location. Using
"requestObs": ["all"] includes both built-in observations.
Decision priority
At each step, the household updates store beliefs from co-located offers and
then applies a strict priority order. Active environment sleep is honored
first; critical hunger, sleep, return travel, scheduled meals, shopping, and
ordinary return-home behavior follow. Only the first applicable core branch
emits an action. Actions listed in disabledActions are removed from the
feasible policy.
SNS behavior
SNS behavior is optional. Set socialRule.enabled to true to create a
SocialMediaPolicy and register it as
a supplemental policy. It is evaluated through the same supplemental-policy
interface as ProposalReactionPolicy,
after the core household decision. Its fragment may contain follow,
unfollow, and tweet alongside the core economic action.
The policy does not modify or replace the social-network recommender. It only
consumes recommended_follows from the configured recommender. For example,
TwoHopRecommenderSystem.temperature continues to control how uniformly
randomized recommendations are sampled, including choices among candidates
with equal scores.
A complete household and text-service configuration is:
"Household": {
"type": "RuleBasedHousehold",
"isHousehold": true,
"requestObs": ["all"],
"socialRule": {
"enabled": true,
"topicPriority": [
"health", "finances", "shopping", "social",
"consumption", "mobility", "daily_life"
],
"tweet": {
"textGeneratorService": "tweetTextClient",
"baseIntensity": 0.0001,
"selfExcitation": 0.1,
"decayRate": 0.8,
"memoryExcitation": 0.0001,
"stressExcitationScale": 0.000001,
"maxMemoryExcerptCharacters": 320,
"language": "English",
"maxCharacters": 280
},
"follow": {
"probability": 0.0005,
"cooldownSteps": 24
},
"unfollow": {
"probability": 0.0001,
"cooldownSteps": 48,
"emptyTweetSteps": 168
}
}
},
"tweetTextClient": {
"type": "TransformersTextClient",
"modelName": "HuggingFaceTB/SmolLM2-360M-Instruct",
"device": "cpu",
"dtype": "float32",
"maxModelParameters": 1000000000,
"maxPromptTokens": 384,
"maxNewTokens": 32,
"temperature": 0.4,
"topP": 0.9,
"repetitionPenalty": 1.1,
"numThreads": 4,
"maxConcurrentGenerations": 1
}
Tweet timing
Tweet occurrence follows a discrete exponential-kernel Hawkes process rather than a minimum posting interval. Before sampling step \(t\), the policy updates
and emits a tweet with probability
Here baseIntensity is \(\mu\), decayRate is \(\beta\),
memoryExcitation contributes \(w_M\) for each changed memory category,
and stressExcitationScale scales the corresponding summarized stress.
After a tweet is successfully rendered, selfExcitation is added to
\(h_t\). Consequently, successive tweets can occur in adjacent steps and
form bursts; there is no minIntervalSteps setting.
Tweet content
Rules first construct a structured
TweetIntent containing topic,
sentiment, style, and a compact memory excerpt. Topic is selected from changed
memory categories in topicPriority order. Summarized stress and negative
language influence sentiment, while rules map topic and sentiment to style.
Only after the intent has been fixed does
TweetRenderer ask the configured text
service to realize its wording.
The default output language is English. maxCharacters is a configurable
upper bound, not a fixed 140-character Twitter limit; the baseline example uses
280. The renderer normalizes whitespace, removes a leading Tweet: or
Post: label, rejects empty text and an exact repeat of self_tweet, and
otherwise preserves the Tiny LM wording. It does not apply a factual-grounding
fallback, so a very small model may paraphrase or supplement details from the
memory excerpt.
TransformersTextClient is a plain-text service separate from the existing
schema-based TransformersClient. At startup it counts model parameters and
raises ValueError if the model exceeds maxModelParameters. Setting that
limit to 1000000000 enforces the intended maximum of one billion
parameters. The baseline uses SmolLM2-360M-Instruct.
Candidate tweet interventions
An experiment may replace ordinarily generated tweet text with a line from a plain-text candidate file during a configured step range:
"candidateTweetIntervention": {
"path": "tweet_candidates/positive_information.txt",
"startStep": 144,
"endStep": 720,
"probability": 0.5
}
The path is read when the household is initialized. Blank lines and lines
starting with # are ignored; every remaining line must fit the configured
tweet.maxCharacters limit. The active interval is half-open. The
probability is conditional on the normal Hawkes process already deciding to
tweet, so this option changes content without increasing posting frequency.
The immediately preceding exact tweet is excluded when another candidate is
available. Candidate tweets are recorded normally and contribute the same
Hawkes self-excitation as generated tweets.
Memory used by SNS rules
Content and external Hawkes excitation use the summarized memory mapping
already present in the household observation. Consumption, purchases, sales,
state evaluations, sleep, movement, social actions, and inner thoughts are
mapped to suitable tweet topics. TweetLog entries are also retained as
tweet_history by MemoryHandler, allowing an
agent’s earlier posts to become social-topic memory. The stress-aware
summarizer exposes tweet history but assigns it no stress score.
Follow and unfollow rules
A follow is sampled with follow.probability after its cooldown and is
chosen from valid recommended_follows candidates. Self-following, already
followed agents, the same-step unfollow target, and choices beyond
follow_cap are excluded. If an unfollow in the same step frees capacity, a
replacement follow may be emitted.
An unfollow becomes eligible when a visible followee has produced an empty
timeline entry for emptyTweetSteps, or its message contains a configured
negativeKeywords value. The fallback random-unfollow probability is
unfollow.probability. Sleeping households make no SNS action, and entries
listed in disabledActions remain unavailable.
Sleep and hunger
sleepRule configures an exponential homeostatic sleep stock plus a
sinusoidal circadian contribution. mealRule configures hunger accumulation,
meal-time signals, and minimum spacing between meals. See
PhysiologyModel for the complete
state equations and default values.
Meal composition
mealRule.composition is the fraction of current hunger energy assigned to
each food in one meal. For hunger \(G_t\), configured share \(s_i\),
energy per unit \(e_i\), and inventory \(I_t(i)\), consumption is
For example:
"mealRule": {
"composition": {
"Rice": 0.7,
"Eggs": 0.2,
"Chocolate": 0.1
},
"energyPerUnit": {
"Rice": 1.0,
"Eggs": 1.0,
"Chocolate": 1.0
}
}
If composition omits an item, its default share is \(1/F\), where
\(F\) is the number of configured foodItems. Consequently, explicitly
assigning 0.043478260869565216 to each of 23 foods is simply equal sharing:
The implementation deliberately remains simple:
shares are not normalized or validated to sum to one;
a meal may consume every configured food simultaneously;
unavailable food is not replaced by another food; and
unallocated hunger remains after the meal.
These properties make equal shares useful as a neutral placeholder, but they are usually not a realistic diet model. Simulations that distinguish staples, prepared meals, and preference goods should configure meaningful shares or provide a custom physiology model.
Inventory replenishment
The shopping trigger is an \((s,S)\)-style rule. For reorder point \(s_i\), target stock \(T_i\), and current inventory \(I_t(i)\), a shopping trip is due when
and desired replenishment is
These parameters are configured per item:
"sSinventoryRule": {
"reorderPoints": {"Rice": 1.0, "Chocolate": 0.5},
"targetStocks": {"Rice": 4.0, "Chocolate": 1.0}
}
Shopping budget
For cash \(Y_t\), reserve \(R\), and maximum basket share \(\alpha\), the available shopping budget is
"budgetRule": {
"cashReserve": 20000.0,
"maxBasketShare": 0.1
}
The budget affects both store choice and the quantities that can actually be ordered.
Item importance
itemImportance gives each food a nonnegative replenishment weight. A
larger weight makes availability for that shortage more important in store
choice. When the budget is tight, orders are also processed in descending
importance, with foodItems order as the stable tie break.
"itemImportance": {
"Rice": 3.0,
"Chocolate": 0.5,
"Sushi": 0.25
}
An omitted item defaults to 1.0 and a negative configured value is clamped
to zero. Reorder points and target stocks remain independent parameters, so
itemImportance does not by itself change when the shopping trigger fires.
Stores and household beliefs
Every visible agent whose observation has is_household: false is treated
as a store. Agent names and name prefixes do not affect this classification.
Households are excluded from store choice, belief updates, and order targets.
Each household has private, store-specific mappings:
expected_price[store_id][item]
expected_availability[store_id][item]
For an unvisited store, expected price starts at the item’s pricePriors
value and expected availability starts at initialAvailability. A
co-located offer updates the beliefs by exponential smoothing:
An offered item with a positive or masked amount has observed availability
\(a_{ij}=1\); a missing item has \(a_{ij}=0\). Price is updated only
for offered items. beliefLearningRate supplies the default for both
learning rates, while priceLearningRate and
availabilityLearningRate can override it separately.
Store-choice utility
For item importance \(w_i\), desired replenishment \(q_i\), expected availability \(\hat a_{ij}\), expected price \(\hat p_{ij}\), and price prior \(\bar p_i\), define weighted need
Expected coverage and relative price savings are
The price term measures a store’s expected price relative to the household’s prior for the same item. An expensive luxury item is therefore not penalized merely for costing more than rice. Absolute affordability is represented by expected expenditure and budget pressure:
The distance \(D_{tj}\) is Chebyshev grid distance. Deterministic utility is
A store is sampled with multinomial-logit probability
The budget-pressure term means that the same expected premium basket deters a cash-constrained household more strongly than a wealthy household. Coverage is retained as a separate positive term so that a store cannot appear attractive merely because carrying fewer desired goods makes its expected expenditure small.
storeChoice parameters
Key |
Default |
Meaning |
|---|---|---|
|
|
Weight on expected relative price savings \(R_{tj}\). |
|
|
Weight on importance-adjusted expected coverage \(C_{tj}\). |
|
|
Penalty weight on expected expenditure divided by shopping budget. |
|
|
Penalty per unit of Chebyshev grid distance. |
|
|
Initial availability belief, clamped to |
|
|
Default smoothing rate for both beliefs, clamped to |
|
|
Optional price-specific smoothing rate. |
|
|
Optional availability-specific smoothing rate. |
With identical initial beliefs, price, availability, and budget-pressure terms are initially common across stores; early choices are therefore mainly distance-driven. Store differentiation emerges as households observe offers.
Ordering at the selected store
After arrival, the household uses the seller’s observed price and stock rather than its beliefs. Items are considered by descending importance. For positive price \(p_i\), available stock \(A_i\), remaining budget \(B_i\), and desired amount \(q_i^*\), the affordable amount is bounded by
The current implementation truncates the emitted order amount to an integer. Unspent budget can then be used by the next item in importance order.
Extending the policy
The adapter, decision arbiter, and stylized models are separate classes.
Custom behavior can replace the default
HouseholdDecisionPolicy or add a
supplemental policy without modifying the environment action protocol. See the
API reference for
econsimulacra.agents.households.household,
econsimulacra.agents.households.policy, and
econsimulacra.agents.households.stylized_models.