Skip to content
Back to skills

Sv Build Environment

ASecurity

Build the dynamic environment E for a SocioVerse2 study — the two-axis layers (physical/information × macro/local), scheduled interventions, and audience-scoped information broadcasts. Use after sv-init, before sv-build-population. This is the "information retriever / environment builder" stage.

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 26, 2026
ai-agentspythongobashapi

Works with

  • claude web
  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned September 26, 2026

npx -y skills add sii-research/SocioVerse2 --skill sv-build-environment --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sv Build Environment?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Sv Build Environment
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sii-research-sv-build-environment/badge)](https://www.skillsdirectory.com/skills/sii-research-sv-build-environment)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: sv-build-environment
description: Build the dynamic environment E for a SocioVerse2 study — the two-axis layers (physical/information × macro/local), scheduled interventions, and audience-scoped information broadcasts. Use after sv-init, before sv-build-population. This is the "information retriever / environment builder" stage.
---

# sv-build-environment — construct E (the dynamic environment)

Produce a validated `EnvironmentBundle`. E is decomposed on two axes — **modality** (`physical` vs `information`) and **scope** (`macro` vs `local`) — plus a **dynamics** tag (`static` / `endogenous` / `scheduled`). This is where you make E *dynamic*: scheduled numeric interventions and audience-scoped information broadcasts over time.

**An intervention is one entry in this bundle** — a `ScheduledEvent` (a numeric change to a declared
layer) or a `Broadcast` (information delivered to an audience). Nothing else counts as an intervention.
Each one fires in the **exogenous update** phase of its `at_step`, the first of the step's five phases
(`exogenous update → observe → decide → apply → record`), so it lands **before any agent observes** and
every agent in that step faces the same updated E.

## Strict contract
- **Read** `study.yaml` via `validate_handoff(path, StudySpec)`.
- **Write** `environment/environment.json` validated as `socioverse.schemas.EnvironmentBundle`.
- Every layer MUST carry `(modality, scope, dynamics)`. Scheduled events must target a declared layer; broadcasts require ≥1 `information` layer (the validators enforce both).

## Capability check (external services — stable hook)
List the registry entries anchored to this stage; the registry — not this skill — carries the
per-service details, so adding/removing a service never edits this text:
```python
from skills.sv_workspace import capability_view
for c in capability_view(stage="sv-build-environment"):   # resources/capabilities.yaml
    print(c["name"], "| available:", c["available"], f"({c['availability_note']})",
          "\n   use_when:", c["use_when"])
```
For each entry whose `use_when` matches this study — or that `sv-init` already pinned in
`resources.json`: probe its `health`; honor `confirm: true` (ask before a call that spends
budget or sends data out; free/read-only entries are just reported at this stage's gate);
fetch at **build time only**; materialize the result into this stage's artifact(s); record
provenance in `grounding/grounding.json`; and make sure the service is pinned by NAME in
`resources.json` (`McpServerDecl` — endpoints stay in `.mcp.json`/`.env`). Unavailable →
follow the entry's `fallback` and record that instead. Per-service how-to lives in each
entry's `howto` — `event_tool`'s points at the "External events" section below.

## Steps
1. Decide the layers across the 4 quadrants. Minimum for a physical study: a macro-physical aggregate + a local-physical neighbourhood. Add `information` layers if the study has news/social/broadcast content.
2. Encode **interventions** (one-shot policy geo-modifications, now time-scheduled):
   - numeric env change → `ScheduledEvent(at_step, target_layer, op, property_name, value, selector, note)`
   - information content → `Broadcast(content, at_step, ttl, audience)` where `audience="all"` (macro) or a selector dict (local). Scenario 1 = two broadcasts (policy A `audience="all"` + policy B `audience={...tract...}`).
3. Pick the `provider_ref` (registry key for the study's `EnvironmentProvider`) and `provider_args` (e.g. geo data path, scale). For **user-supplied data (scenario 3b)** set `provider_args["geojson"]` to the path from the ResourceManifest.

## Writing E for a branch (the environment bundle of a branch)

A **branch** is a counterfactual version that keeps the same individuals, the same behaviour function,
the same seed and the same history, and differs only in E from the fork step `t*` onward — which is why
its steps `0..t*-1` can be replayed from the parent instead of re-run. When `/sv-iterate` routes here as
a branch, the environment bundle is the only artifact you may touch, and only forward in time:
- **Allowed**: add `ScheduledEvent`s / `Broadcast`s with `at_step >= fork_step`; **add** a new
  `information` layer when a new broadcast needs a channel the parent doesn't have.
- **Not allowed**: change or delete any event/broadcast at `at_step < fork_step`, rewrite or remove a
  layer the parent already had, or touch `provider_ref` / `provider_args`.
- Everything outside E (population, the study's own `*.py`, seed, `interaction_rounds`, …) stays
  byte-identical — that is exactly what makes the parent's early steps replayable.
- **Moving an intervention** (both its old and new `at_step >= fork_step`): change only `at_step`
  and keep the original duration unless the user asked otherwise — a `Broadcast` stays visible for
  the same number of steps within the horizon (`ttl` kept, or shortened if the old window was
  clipped at `n_steps`), never stretched to reach the end. If the move still changes how long it
  acts (it now ends before the horizon, or a persisting `ScheduledEvent` fires earlier), say so in
  this stage's summary. The `/sv-iterate` classification section has the worked example.

Touch anything before the fork step and `check_branch_invariant` **downgrades this version to a plain
version** at run time: no error, but the full horizon cold-runs and the paired Δ_t against the parent is
lost. If the change genuinely belongs before `t*`, say so and let `/sv-iterate` record it as a **version**
instead.

## Grounding-first — layer baselines, provider_args, broadcast numbers

The external-events chain below covers EVENT context; this rule covers E's numbers. Any
load-bearing magnitude in `layers[*]` or `provider_args` (base prices, capacities, rates,
drifts) is grounded before invention — check `grounding/grounding.json` first
(`from skills import sv_grounding; g = sv_grounding.load("studies/<id>")`), and for
anything missing:
1. **always a quick live-search pass** (1–2 queries — also when the Event service is
   up: it covers what the structured sources don't, e.g. local-language news, policy and prices).
   The default route is the built-in **WebSearch** (+ WebFetch for the 1–2 load-bearing
   pages; `via="web_search"`). **Optional upgrade:** when the Event service is configured
   and its in-session **`search_web` MCP tool** is visible, use it for news/event/public-opinion
   queries (multilingual news search; `via="event_service"`), and switch to it instead of
   retrying when WebSearch fails (403 / "No links found"). Either way record url +
   accessed date;
2. served by the materialized external events (fred/census/monthly news) → merge a fact,
   `via="event_service"`, `source.title="<source> <month>"`;
3. nothing usable → an `assumptions` entry, or `basis="proxy"` when anchoring on a
   related quantity (say which in `note`).
Budget ≤3 targeted searches; ground only values that could change conclusions.
```python
from skills import sv_grounding
sv_grounding.merge("studies/<id>", facts=[{
    "id": "f-base-rent-d1", "claim": "Median asking rent for a one-bedroom flat in district D1", "value": 1450,
    "unit": "EUR/month", "as_of": "2026-05", "basis": "sourced",
    "source": {"title": "…", "url": "https://…", "via": "web_search", "accessed": "2026-07-06"},
    "applies_to": ["environment.provider_args.base (D1)"]}])
```
- A layer built on a grounded baseline may set its (otherwise unused) `source` field to
  `"grounding:<fact_id>"` — a machine-readable back-link, zero schema change.
- Broadcast text that quotes a real number quotes a fact in the file — never an
  unsourced figure.
- A small reference table (district price sheet, census extract; MB-scale) may be
  downloaded to `grounding/data/` with the fact recording url + accessed +
  `source.local_path` (+ `provider_args` pointing at it when a provider consumes it).
  Heavy ETL / large datasets stay out.
Append `sv_grounding.summary("studies/<id>")` to this stage's narrative.

## External events — retrieval priority & fallback

When the study's E needs real-world macro/news/market context (broadcast content, layer
baselines), retrieve it BEFORE authoring broadcasts, via `socioverse.external_events`.
The priority chain is implemented in the client — you only check the outcome:

1. **remote Event MCP** (optional service; `SV_EVENT_API_URL` = the full MCP endpoint,
   e.g. `https://your-event-host/event_mcp`, + `SV_EVENT_API_KEY` from env/`.env`) — used
   when configured. The service returns structured SourceResults only and has NO query
   parser, so always pass explicit `months=` (+ `sources=`); it fetches per source, so
   one slow upstream just becomes an error entry, not a lost batch;
2. **local cache dir** (`SV_EVENT_LOCAL_CACHE`, `<source>/<YYYY-MM>.json`) — offline/dev;
3. **unavailable** → YOU are the fallback: use Claude WebSearch for the events and
   author broadcasts by hand. Every number/claim used this way lands in
   `grounding/grounding.json` via `sv_grounding.merge` (`via="web_search"`, url +
   accessed date) — provenance needs a landing place, not a passing note.

Web evidence is never the service's job: pair the structured numbers with your own
WebSearch when the narrative needs events/context, and ground both.

```python
from socioverse import materialize_external_events, month_range
from skills.sv_workspace import study_paths

path, ev = materialize_external_events(
    "studies/<id>", "US tariff shock and consumer prices",
    months=month_range("2025-04", "2025-06"),      # from study.yaml's time horizon
    sources=["fred", "nyt_archive", "yahoo_market"],  # optional preference
)
if not ev.available:      # both tiers down -> WebSearch fallback (tier 3)
    ...
```

Rules:
- The evidence lands in `environment/external_events.json` (provenance: `provider`
  tells which tier served it). Keep it next to `environment.json`.
- **Author broadcasts from `ev.results` (typed per-source data), NEVER from `ev.summary`**
  (`summary`/`web_evidence` are legacy fields — the production MCP returns structured data
  only; service prose would contaminate B=f(P,E) attribution). Quote numbers with their
  source + month.
- A study pins the service by NAME in `resources.json` (`McpServerDecl(name="event_tool",
  transport="http")`; url + key stay in env/`.env` — set `url=` only for a genuinely public
  endpoint, never a private/dev IP in a committed artifact). `ExternalEventsClient.from_manifest(manifest)`
  uses a pinned url when present and falls back to the environment otherwise; pass it via
  `materialize_external_events(..., client=client)`.
- The runtime loop never fetches: if E must change mid-run, schedule it as
  `ScheduledEvent`/`Broadcast` built from this materialized file.
- Event-service numbers that E consumes directly (layer baselines, provider_args) also
  get a grounding fact (`via="event_service"`) — see the grounding-first section above.

### Chicago example (reuses the study's bundle factory)
```python
from socioverse.validation import validate_handoff, write_artifact
from socioverse.schemas import StudySpec, ScheduledEvent, Broadcast
from skills.sv_workspace import study_paths
from studies.chicago_schelling.adapter import make_chicago_bundles

study = validate_handoff(study_paths("studies/chicago_schelling")["study"], StudySpec)
events = [ScheduledEvent(at_step=2, target_layer="tract_local", op="add",
            property_name="cta_stations", value=2, selector={"geoid_list": ["17031842600"]},
            note="open CTA station at step 2")]
broadcasts = [
    Broadcast(message_id="A", channel="news", content="Citywide transit expansion", at_step=2, ttl=3, audience="all"),
    Broadcast(message_id="B", channel="ward_notice", content="New rail station in your tract",
              at_step=2, ttl=3, audience={"geoid_list": ["17031842600"]}),
]
_, env_bundle, _ = make_chicago_bundles(scale="small", scheduled_events=events, broadcasts=broadcasts)
write_artifact(study_paths("studies/chicago_schelling")["environment"], env_bundle)
```

### Generic example (from-scratch / Path B — no Chicago)
For a `sv-build-model`-authored study, take the bundle from its factory (or construct `EnvironmentBundle` directly with `provider_ref="<id>.env"` + your `layers`/`scheduled_events`/`information_program`). The `provider_ref` must match a class the study registered.
```python
from socioverse.validation import write_artifact
from skills.sv_workspace import study_paths
from studies.opinion_diffusion.model import make_opinion_bundles   # the from-scratch template

_, env_bundle, _, _ = make_opinion_bundles(n_agents=12, campaign_step=2)
write_artifact(study_paths("studies/opinion_diffusion")["environment"], env_bundle)
```

## Re-entry (iteration mode — arriving from /sv-iterate)
A post-run change routed here means the version gate already ran (new version / branch / in-place):
- **E is affected** → author + `write_artifact` as above, overwriting the live `environment.json`
  (when the user chose a new version, the previous one is safe in its `versions/vN/` snapshot).
  New real-world context (rates, indices, news) still goes through the retrieval chain above,
  and the grounding entries the change touches get refreshed (`sv_grounding.merge` by id —
  leave the untouched entries alone).
- **E is NOT affected** → `validate_handoff(path, EnvironmentBundle)` only, report
  "environment.json unchanged, validation passed, rebuild skipped" (+ narrative), and do **not** rewrite the file —
  an identical rewrite bumps its mtime and corrupts the dashboard's carried/stale detection.
Then hand back to the iteration flow (next affected stage, or `/sv-run`'s spend gate).

## Pause & confirm (default: stop between stages)
Pausing here is the **default**, and it yields to an explicit user instruction about the flow.
1. **By default, stop and wait.** After writing `environment.json`, don't invoke `/sv-build-population` (or any later skill) on your own. Advance only on an explicit go-ahead; silence or an ambiguous reply is not confirmation.
2. **Follow the user's flow when they've set one.** If the user said how far to run or where to pause (e.g. "run straight through to /sv-run", "do the whole pipeline"), honor it — chain the stages to the point they named instead of stopping here.
3. **Either way, surface the intermediate product:** the layers / scheduled events / broadcasts in one line, plus the artifact path.
4. **Be rollback-ready.** If the user asks for changes — including ones that revise an earlier step's decision — revise `environment.json` in place, re-summarize, and stop again. Treat a change request as "redo this step", never "skip ahead".

## Hand-off
`sv-build-population` reads `study.yaml`; `sv-run` reads this `environment.json` via `validate_handoff(path, EnvironmentBundle)`.

## Dashboard narrative (optional, non-blocking — L2)
If the local run dashboard is up, after writing the artifact post a 1–2 sentence
plain-language summary of *what you decided and why*. It shows on this stage's card
+ event feed (files already carry the state; this is the reasoning):
```bash
python3 dashboard/hooks/sv_emit.py narrative --study <study_id> --stage sv-build-environment \
  --text "E = 3 layers (information × macro/local); scheduled a fact-check broadcast to all at step 3."
```
Exits 0 / prints nothing, so it never blocks; skip in headless or offline runs.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.