Skip to content

feat(simulation): deliver event_config.scheduled_events end to end - #780

Open
conradorebuffo wants to merge 3 commits into
666ghj:mainfrom
conradorebuffo:feat/scheduled-events
Open

feat(simulation): deliver event_config.scheduled_events end to end#780
conradorebuffo wants to merge 3 commits into
666ghj:mainfrom
conradorebuffo:feat/scheduled-events

Conversation

@conradorebuffo

Copy link
Copy Markdown

Closes #779

event_config.scheduled_events is declared on the EventConfig dataclass but never reaches the
simulation: the event-config prompt does not ask for it, _parse_event_config overwrites whatever
the model returned with a hardcoded [], and no runner reads the key. The README advertises
injecting variables mid-simulation from a "God's-eye view", which is exactly this field.

This wires the existing mechanism rather than inventing a new one.

Goals

  • Make event_config.scheduled_events reach the simulation, so the README's mid-run
    "God's-eye view" injection is backed by code.
  • Keep the change strictly additive: with an empty list — what the generator emits today — the
    execution path must be indistinguishable from current behaviour.
  • Cover both platforms symmetrically, Reddit and Twitter.

Non-goals

  • The five other fields reported in the issue. They are not the same case and are argued there,
    not here — see "Not included, on purpose" below.
  • Any change to the recommender, to OASIS internals, or to camel-oasis.
  • Any reordering of prepare_simulation. That is a maintainer decision, not an outside PR.

Acceptance criteria

  1. An event with round: N is published in round N, by an agent whose poster_type matches the
    request, and before the agents act in that round.
  2. The same content does not appear in earlier rounds, and does not appear at all on unpatched
    code with the same config.
  3. With scheduled_events: []: no event log lines, same round count, same normalised log flow and
    same initial posts as before the change.
  4. A malformed round logs a warning and is skipped — the run continues rather than aborting.
  5. An event whose round falls outside the actual round count logs a warning instead of being
    dropped silently (total_rounds can be truncated by --max-rounds).
  6. cd backend && python -m pytest tests -q is green.

All six were checked on a clean b5b53acc checkout; evidence in Testing below.

Why this is mergeable: it is the same publication path, moved inside the loop

initial_posts is already published through ManualAction(action_type=ActionType.CREATE_POST) in a
single env.step() before the round loop (run_reddit_simulation.py:597-620). A scheduled event is
that same publication, fired from inside the loop at its target round.

With an empty scheduled_events the execution path is identical to today. That is not an
argument, it is measured: two runs from the same config, one with events and one with [], produced
the same number of env.step() calls per round, the same log flow and the same initial posts. Since
the current generator always emits [], existing configs are unaffected.

Changes

backend/app/services/simulation_config_generator.py

  • ask for scheduled_events in the event-config prompt, constraining round to the simulation
    horizon, and keep the failure-path dict the same shape
  • stop discarding the parsed value in _parse_event_config
  • generalize _assign_initial_post_agents into _assign_agents_to_posts, so initial posts and
    scheduled events share one type index, one alias table and one anti-repetition counter
    instead of duplicating the matching logic

backend/scripts/run_reddit_simulation.py, backend/scripts/run_twitter_simulation.py

  • read the events and group them by round
  • publish the round's events at the top of the loop, before agents act
  • a malformed round logs a warning and is skipped rather than aborting the run
  • warn when an event's round falls outside the actual round count — total_rounds can be truncated
    by --max-rounds, and such events would otherwise be dropped silently

backend/tests/test_scheduled_event_assignment.py — four tests for the shared assignment helper:
resolution across both lists, anti-repetition carrying across them, empty lists as a no-op, and the
unknown-poster_type fallback.

Both platforms are changed, symmetrically: a fix that repairs one and leaves the other broken is an
easy excuse to close the PR.

Testing

  • cd backend && python -m pytest tests -q133 passed (129 existing + 4 new)
  • Functional, on a clean b5b53acc checkout: a config with one event at round: 3 produced that
    post in round 3, published by an agent of the requested poster_type, and absent from rounds 1
    and 2 — with the same config on unpatched code producing nothing.
  • Regression: same config with scheduled_events: [] → no event log lines, same round count, same
    execution flow.
  • End to end: a full pipeline run had the model return four events (rounds 5, 8, 12, 15), each
    assigned to a distinct agent matching its requested poster_type, and all four fired in their
    round.

One design question

The trigger is the round number, as described in #779. I raised the alternative there — the
simulated clock would allow "at simulated hour N" — and said I would follow your preference. With no
answer yet I am shipping round, since it is what the loop already indexes and what the generator
can reason about directly. Switching to the clock is a small change confined to the assignment
helper, and I will make it if you prefer.

Relationship to #573

#573 (Narrative Layer / God Mode) lists "Mid-sim OASIS prompt injection" among its explicit
non-goals, since it deliberately stays out of the OASIS core. This PR does that injection inside the
simulation, so the two are complementary: if #573 lands, its God Mode would have a real mechanism to
push events into rather than only the prose layer. There is no file overlap between them.

Not included, on purpose (the non-goals, in detail)

The issue lists five other fields that no runner reads. I left them out:

  • stance / sentiment_bias — reachable only by reordering prepare_simulation, which is your call
  • recency_weight — its counterpart lives in camel-oasis, not in this repo
  • echo_chamber_strength — no destination under recsys_type='reddit'

I would rather ship the one that is verified end to end than bundle four that are not.

No rush on this — I will rebase if it goes stale.

conradorebuffo and others added 3 commits August 16, 2026 09:32
`event_config.scheduled_events` was declared on the EventConfig dataclass but
never reached the simulation: the event-config prompt did not ask for it,
`_parse_event_config` overwrote whatever the LLM returned with a hardcoded `[]`,
and neither runner read the key. The README advertises injecting variables
mid-simulation from a "God's-eye view", which is exactly this field.

This wires the existing mechanism rather than inventing a new one. Initial posts
are already published through `ManualAction(CREATE_POST)` in a single `env.step()`
before the round loop; a scheduled event is the same publication fired from inside
the loop at its target round.

- ask for `scheduled_events` in the event-config prompt, constraining `round` to
  the simulation horizon, and keep the failure-path dict the same shape
- stop discarding the parsed value in `_parse_event_config`
- generalize `_assign_initial_post_agents` into `_assign_agents_to_posts`, so
  initial posts and scheduled events share one type index, one alias table and
  one anti-repetition counter instead of duplicating the matching logic
- read and group the events by round in both the Reddit and Twitter runners, and
  publish them at the top of each round before agents act; a malformed event logs
  a warning and is skipped rather than aborting the run
- thread `stance` / `sentiment_bias` into the persona prompt builders, since the
  persona text is the only channel that reaches the model

Backwards compatible: with an empty `scheduled_events` the execution path is
identical to today, so existing configs are unaffected.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…peline

The persona prompt builders now threaded `stance` / `sentiment_bias`, and
`generate_profile_from_entity` read them off a new `agent_activity_map`. But no
caller ever supplies that map, so every agent kept the `neutral` / `0.0`
defaults and the values never reached the model.

That is not an oversight in the call chain, it is the pipeline order:
`prepare_simulation` generates the profiles before it generates
`simulation_config.json`, and the per-agent stance only exists in the latter.
Making it reachable means either reordering an existing pipeline or rewriting
the generated profile artifact at run time -- both far beyond the scope of this
change, and both would mask the point rather than make it.

`stance` and `sentiment_bias` are therefore reported as inert, like the other
config fields no runner consumes, instead of being wired with plumbing that
cannot carry a value.

This leaves the change to what is verified end to end: `scheduled_events`.

Co-Authored-By: Claude Code <noreply@anthropic.com>
`total_rounds` is derived from the time config, then truncated by `--max-rounds`.
The event-config prompt asks the model to keep each `round` inside the simulation
horizon, but it sees the untruncated horizon: a run configured for 72 rounds and
started with `--max-rounds 24` can be handed events for round 40.

Those events were kept in the by-round index and simply never popped, so they
disappeared without a trace -- the same silent discard this change set out to fix,
reintroduced one level down.

Both runners now report the out-of-range rounds up front, next to the existing
warning for a malformed `round`. Nothing is dropped differently; it is just no
longer dropped quietly.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Six config fields are generated but never read by any runner (and one is never rendered either)

1 participant