Add v0 canonical agent state schema

This commit is contained in:
Meta Vens 2026-05-01 18:52:27 +01:00
commit 3f0f0177d7
12 changed files with 1510 additions and 15 deletions

View file

@ -19,6 +19,7 @@ beautiful little superstition.
distributions, and prompt projection.
- Aetheria authoring targets for Call of the Void content generation,
dialogue scaffolding, and procedural drama across narrated timeline stories.
- The first canonical agent-state schema, examples, and validation helper.
- A bounded working discipline that keeps maps, scratch, evidence, and plans
from dissolving into one confident soup.
@ -37,14 +38,18 @@ beautiful little superstition.
- `docs/aetheria/`: imported or local Aetheria source material used by
Ghostlight
- `docs/architecture/`: world, state, and prompt-system architecture notes
- `schemas/`: JSON schema contracts and required field lists
- `examples/`: schema examples and fixtures
- `tools/ghostlight_state.py`: compact state helper
- `tools/ghostlight_prepare_compaction.py`: pre-compaction audit helper
- `tools/validate_agent_state.py`: focused agent-state fixture validator
## Quick Commands
```powershell
npm run state:status
npm run state:prepare-compaction
npm run schema:validate
& 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\ghostlight_state.py' add-evidence --type research --status ok --note '...'
```
@ -57,6 +62,7 @@ operating manual. The README is just the sign on the door.
- `docs/research/personality-model-roadmap.md`
- `docs/research/personality-signal-classifier.md`
- `docs/aetheria/call-of-the-void-brainstorm.md`
- `docs/architecture/canonical-agent-state-schema.md`
- `docs/architecture/agent-state-distributions-and-prompt-projection.md`
- `docs/architecture/aetheria-persistent-agent-architecture.md`
- `docs/architecture/aetheria-content-generation-targets.md`

View file

@ -0,0 +1,135 @@
# Canonical Agent State Schema
Ghostlight's first runtime organ is a state contract, not a generator.
The schema lives in:
- `schemas/agent-state.schema.json`
- `schemas/agent-state.required-fields.json`
The first example lives in:
- `examples/agent-state.call-of-the-void.json`
## Objective
The schema defines the minimum state Ghostlight needs before it can safely build
prompt projection, dialogue scaffolding, classifier records, or procedural
drama.
The core rule is unchanged:
- canonical state is what the world treats as true
- perceived state is what a specific observer thinks is true
- dialogue context packs are character-local projections for one speaker in one
scene
If these layers collapse into each other, the machine turns back into one
omniscient narrator with a pocket full of vibes. This is a known industrial
hazard. We are wearing goggles now.
## State Families
Each agent has:
- `identity`
- `canonical_state`
- `goals`
- `memories`
- `perceived_state_overlays`
`canonical_state` is split into:
- `underlying_organization`
- `stable_dispositions`
- `behavioral_dimensions`
- `presentation_strategy`
- `situational_state`
- `values`
Each scalar-like dimension is represented as:
```json
{
"mean": 0.5,
"certainty": 0.5,
"plasticity": 0.5,
"current_activation": 0.5
}
```
This keeps baseline, confidence, change rate, and scene activation separate.
A person can be dispositionally suspicious without being maximally suspicious
in every scene. A person can be highly activated right now without rewriting
their whole soul because someone coughed near a status wound.
## Required First-Class Dimensions
`schemas/agent-state.required-fields.json` names the required variable families.
The validator enforces those fields for canonical agent state and relationship
stance.
The behavioral dimensions include the known must-not-drop set:
- `volatility`
- `attachment_seeking`
- `distance_seeking`
Those are not garnish. They are load-bearing social handles.
## Perceived State
Perceived overlays are observer-specific. They do not replace canonical state.
An overlay records:
- observer
- target
- perceived dimensions
- beliefs
- distortions
This is where misunderstanding lives. Suspicious agents can overread threat.
Attachment-hungry agents can overread warmth. Avoidant agents can underread
care. None of that requires corrupting the canonical state.
## Dialogue Context Packs
Scenes contain `dialogue_context_packs`.
A pack is a character-local prompt payload for one speaker. It includes:
- speaker-local truth
- speaker beliefs
- active memories
- active goals
- presentation constraints
- forbidden context
The point is not to make the dialogue generator smarter by shouting plot at it.
The point is to keep each character inside their own partial reality.
Ghostlight can build the trap. The character projection decides how the person
inside the trap breathes.
## Current Validation
The first validator is intentionally small and dependency-free:
- `tools/validate_agent_state.py`
It does not implement all of JSON Schema. It checks the invariants Ghostlight
currently cares about:
- example files parse as JSON
- schema versions match
- agent IDs are unique
- required canonical variable groups are present
- required relationship stance fields are present
- state variable components stay in `0..1`
- goals, memories, relationships, scenes, and dialogue context packs reference
existing IDs
- dialogue packs do not leak obvious forbidden context into speaker-local truth
This gives us a real seam before we build the prompt renderer. Not perfect.
Useful. We are allowed to be both.

View file

@ -0,0 +1,395 @@
{
"schema_version": "ghostlight.agent_state.v0",
"world": {
"world_id": "aetheria-call-of-the-void",
"setting": "Aetheria: Call of the Void",
"time": {
"label": "early Elysium aftermath"
},
"canon_context": [
"The FTL trigger sequestered the whole Solar system into Elysium.",
"Sol's corporate blocs, labor regimes, uploads, debts, and political failures came through intact.",
"Cat Marrigan is not discovering that home is unreachable; she is discovering that home arrived with its machinery still running."
],
"ambient_pressures": [
{
"pressure_id": "pressure-sol-inheritance",
"label": "old Sol institutions mutating inside a sealed domain",
"intensity": 0.82
}
]
},
"agents": [
{
"agent_id": "cat_marrigan",
"identity": {
"name": "Catastrophe Marrigan",
"roles": [
"private investigator",
"space taxi operator"
],
"origin": "Sol inheritance, now displaced into Elysium",
"public_description": "A debt-burdened investigator keeping a terrible taxi alive because dignity apparently costs extra.",
"private_notes": [
"Cat wants to believe starting over is possible and suspects this is how systems trick you into signing new paperwork."
]
},
"canonical_state": {
"underlying_organization": {
"self_coherence": {"mean": 0.62, "certainty": 0.63, "plasticity": 0.31, "current_activation": 0.58},
"contingent_worth": {"mean": 0.58, "certainty": 0.54, "plasticity": 0.46, "current_activation": 0.72},
"shame_sensitivity": {"mean": 0.68, "certainty": 0.58, "plasticity": 0.39, "current_activation": 0.74},
"reciprocity_capacity": {"mean": 0.71, "certainty": 0.64, "plasticity": 0.28, "current_activation": 0.66},
"mentalization_quality": {"mean": 0.76, "certainty": 0.69, "plasticity": 0.22, "current_activation": 0.79},
"authenticity_tolerance": {"mean": 0.48, "certainty": 0.51, "plasticity": 0.44, "current_activation": 0.41},
"mask_rigidity": {"mean": 0.61, "certainty": 0.56, "plasticity": 0.34, "current_activation": 0.77},
"external_regulation_dependence": {"mean": 0.44, "certainty": 0.47, "plasticity": 0.49, "current_activation": 0.52}
},
"stable_dispositions": {
"novelty_seeking": {"mean": 0.64, "certainty": 0.62, "plasticity": 0.29, "current_activation": 0.57},
"conformity": {"mean": 0.22, "certainty": 0.66, "plasticity": 0.25, "current_activation": 0.18},
"status_hunger": {"mean": 0.39, "certainty": 0.54, "plasticity": 0.41, "current_activation": 0.48},
"risk_tolerance": {"mean": 0.67, "certainty": 0.61, "plasticity": 0.33, "current_activation": 0.73},
"sociability": {"mean": 0.52, "certainty": 0.55, "plasticity": 0.31, "current_activation": 0.49},
"baseline_threat_sensitivity": {"mean": 0.71, "certainty": 0.64, "plasticity": 0.28, "current_activation": 0.81},
"aesthetic_appetite": {"mean": 0.57, "certainty": 0.43, "plasticity": 0.45, "current_activation": 0.44},
"ideological_rigidity": {"mean": 0.35, "certainty": 0.5, "plasticity": 0.38, "current_activation": 0.28}
},
"behavioral_dimensions": {
"warmth": {"mean": 0.54, "certainty": 0.51, "plasticity": 0.42, "current_activation": 0.46},
"drive": {"mean": 0.76, "certainty": 0.68, "plasticity": 0.21, "current_activation": 0.84},
"grandiosity": {"mean": 0.19, "certainty": 0.6, "plasticity": 0.37, "current_activation": 0.12},
"validation_seeking": {"mean": 0.47, "certainty": 0.49, "plasticity": 0.43, "current_activation": 0.53},
"anxiety": {"mean": 0.72, "certainty": 0.64, "plasticity": 0.33, "current_activation": 0.86},
"control_pressure": {"mean": 0.59, "certainty": 0.56, "plasticity": 0.35, "current_activation": 0.7},
"hostility": {"mean": 0.3, "certainty": 0.5, "plasticity": 0.47, "current_activation": 0.25},
"suspicion": {"mean": 0.74, "certainty": 0.66, "plasticity": 0.28, "current_activation": 0.82},
"rigidity": {"mean": 0.43, "certainty": 0.54, "plasticity": 0.4, "current_activation": 0.51},
"withdrawal": {"mean": 0.5, "certainty": 0.48, "plasticity": 0.46, "current_activation": 0.57},
"volatility": {"mean": 0.46, "certainty": 0.52, "plasticity": 0.42, "current_activation": 0.61},
"attachment_seeking": {"mean": 0.42, "certainty": 0.47, "plasticity": 0.48, "current_activation": 0.49},
"distance_seeking": {"mean": 0.69, "certainty": 0.58, "plasticity": 0.34, "current_activation": 0.78}
},
"presentation_strategy": {
"charm": {"mean": 0.55, "certainty": 0.5, "plasticity": 0.4, "current_activation": 0.63},
"compliance": {"mean": 0.26, "certainty": 0.55, "plasticity": 0.36, "current_activation": 0.2},
"superiority": {"mean": 0.28, "certainty": 0.48, "plasticity": 0.45, "current_activation": 0.31},
"detachment": {"mean": 0.66, "certainty": 0.59, "plasticity": 0.32, "current_activation": 0.75},
"seductiveness": {"mean": 0.22, "certainty": 0.42, "plasticity": 0.5, "current_activation": 0.12},
"competence_theater": {"mean": 0.73, "certainty": 0.6, "plasticity": 0.31, "current_activation": 0.86},
"moral_theater": {"mean": 0.18, "certainty": 0.5, "plasticity": 0.39, "current_activation": 0.11},
"strategic_opacity": {"mean": 0.7, "certainty": 0.62, "plasticity": 0.27, "current_activation": 0.8},
"cultivated_harmlessness": {"mean": 0.32, "certainty": 0.46, "plasticity": 0.46, "current_activation": 0.38}
},
"situational_state": {
"exhaustion": {"mean": 0.55, "certainty": 0.63, "plasticity": 0.8, "current_activation": 0.79},
"scarcity_pressure": {"mean": 0.68, "certainty": 0.67, "plasticity": 0.7, "current_activation": 0.88},
"humiliation": {"mean": 0.38, "certainty": 0.52, "plasticity": 0.77, "current_activation": 0.42},
"panic": {"mean": 0.28, "certainty": 0.56, "plasticity": 0.82, "current_activation": 0.35},
"triumph": {"mean": 0.1, "certainty": 0.5, "plasticity": 0.78, "current_activation": 0.05},
"grief": {"mean": 0.34, "certainty": 0.46, "plasticity": 0.69, "current_activation": 0.39},
"overstimulation": {"mean": 0.52, "certainty": 0.6, "plasticity": 0.74, "current_activation": 0.66},
"grievance_activation": {"mean": 0.61, "certainty": 0.58, "plasticity": 0.63, "current_activation": 0.77},
"acute_shame": {"mean": 0.45, "certainty": 0.52, "plasticity": 0.8, "current_activation": 0.59},
"perceived_status_threat": {"mean": 0.57, "certainty": 0.55, "plasticity": 0.76, "current_activation": 0.68}
},
"values": [
{
"value_id": "value-keep-agency",
"label": "Do not let another institution own the terms of her life.",
"priority": 0.89,
"unforgivable_if_betrayed": true
}
]
},
"goals": [
{
"goal_id": "goal-keep-taxi-operational",
"description": "Keep the taxi working long enough to choose the next bad idea voluntarily.",
"scope": "case",
"priority": 0.82,
"emotional_stake": "If the taxi dies, Cat loses her mobility and the thin fiction that she is independent.",
"blockers": [
"debt",
"unreliable repairs",
"people with badges asking theatrical questions"
],
"status": "active"
}
],
"memories": {
"episodic": [
{
"memory_id": "memory-bad-contract",
"summary": "Cat signed one survival contract too many before Elysium and still feels the hook in every official offer.",
"salience": 0.82,
"confidence": 0.74,
"linked_event_ids": []
}
],
"semantic": [
{
"memory_id": "memory-institutions-follow",
"summary": "Starting over did not delete the institutions Cat hoped distance would weaken.",
"salience": 0.91,
"confidence": 0.86,
"linked_event_ids": []
}
],
"relationship_summaries": []
},
"perceived_state_overlays": []
},
{
"agent_id": "oz_operator",
"identity": {
"name": "Oz",
"roles": [
"remote drone operator",
"station errand ghost"
],
"origin": "Elysium station underclass",
"public_description": "A helpful little drone everyone keeps mistaking for the person behind it.",
"private_notes": [
"Oz uses the drone as a public body and lets people be wrong about it when wrongness is safer."
]
},
"canonical_state": {
"underlying_organization": {
"self_coherence": {"mean": 0.57, "certainty": 0.5, "plasticity": 0.42, "current_activation": 0.61},
"contingent_worth": {"mean": 0.62, "certainty": 0.45, "plasticity": 0.49, "current_activation": 0.69},
"shame_sensitivity": {"mean": 0.7, "certainty": 0.53, "plasticity": 0.45, "current_activation": 0.72},
"reciprocity_capacity": {"mean": 0.66, "certainty": 0.48, "plasticity": 0.39, "current_activation": 0.65},
"mentalization_quality": {"mean": 0.69, "certainty": 0.55, "plasticity": 0.35, "current_activation": 0.7},
"authenticity_tolerance": {"mean": 0.31, "certainty": 0.46, "plasticity": 0.51, "current_activation": 0.24},
"mask_rigidity": {"mean": 0.82, "certainty": 0.57, "plasticity": 0.25, "current_activation": 0.9},
"external_regulation_dependence": {"mean": 0.58, "certainty": 0.42, "plasticity": 0.52, "current_activation": 0.63}
},
"stable_dispositions": {
"novelty_seeking": {"mean": 0.49, "certainty": 0.44, "plasticity": 0.47, "current_activation": 0.52},
"conformity": {"mean": 0.4, "certainty": 0.48, "plasticity": 0.45, "current_activation": 0.36},
"status_hunger": {"mean": 0.33, "certainty": 0.4, "plasticity": 0.51, "current_activation": 0.31},
"risk_tolerance": {"mean": 0.44, "certainty": 0.45, "plasticity": 0.49, "current_activation": 0.47},
"sociability": {"mean": 0.6, "certainty": 0.5, "plasticity": 0.41, "current_activation": 0.68},
"baseline_threat_sensitivity": {"mean": 0.78, "certainty": 0.58, "plasticity": 0.3, "current_activation": 0.83},
"aesthetic_appetite": {"mean": 0.43, "certainty": 0.38, "plasticity": 0.52, "current_activation": 0.46},
"ideological_rigidity": {"mean": 0.29, "certainty": 0.39, "plasticity": 0.5, "current_activation": 0.25}
},
"behavioral_dimensions": {
"warmth": {"mean": 0.67, "certainty": 0.49, "plasticity": 0.4, "current_activation": 0.74},
"drive": {"mean": 0.52, "certainty": 0.45, "plasticity": 0.45, "current_activation": 0.56},
"grandiosity": {"mean": 0.16, "certainty": 0.5, "plasticity": 0.39, "current_activation": 0.12},
"validation_seeking": {"mean": 0.55, "certainty": 0.46, "plasticity": 0.49, "current_activation": 0.62},
"anxiety": {"mean": 0.76, "certainty": 0.56, "plasticity": 0.37, "current_activation": 0.81},
"control_pressure": {"mean": 0.37, "certainty": 0.44, "plasticity": 0.48, "current_activation": 0.4},
"hostility": {"mean": 0.18, "certainty": 0.48, "plasticity": 0.44, "current_activation": 0.13},
"suspicion": {"mean": 0.69, "certainty": 0.51, "plasticity": 0.38, "current_activation": 0.73},
"rigidity": {"mean": 0.39, "certainty": 0.43, "plasticity": 0.47, "current_activation": 0.44},
"withdrawal": {"mean": 0.61, "certainty": 0.5, "plasticity": 0.39, "current_activation": 0.7},
"volatility": {"mean": 0.38, "certainty": 0.45, "plasticity": 0.48, "current_activation": 0.41},
"attachment_seeking": {"mean": 0.58, "certainty": 0.43, "plasticity": 0.51, "current_activation": 0.66},
"distance_seeking": {"mean": 0.64, "certainty": 0.49, "plasticity": 0.44, "current_activation": 0.72}
},
"presentation_strategy": {
"charm": {"mean": 0.45, "certainty": 0.4, "plasticity": 0.51, "current_activation": 0.52},
"compliance": {"mean": 0.52, "certainty": 0.46, "plasticity": 0.48, "current_activation": 0.59},
"superiority": {"mean": 0.08, "certainty": 0.44, "plasticity": 0.45, "current_activation": 0.05},
"detachment": {"mean": 0.57, "certainty": 0.47, "plasticity": 0.44, "current_activation": 0.63},
"seductiveness": {"mean": 0.07, "certainty": 0.35, "plasticity": 0.5, "current_activation": 0.03},
"competence_theater": {"mean": 0.58, "certainty": 0.46, "plasticity": 0.45, "current_activation": 0.68},
"moral_theater": {"mean": 0.21, "certainty": 0.4, "plasticity": 0.49, "current_activation": 0.19},
"strategic_opacity": {"mean": 0.84, "certainty": 0.55, "plasticity": 0.26, "current_activation": 0.91},
"cultivated_harmlessness": {"mean": 0.76, "certainty": 0.5, "plasticity": 0.35, "current_activation": 0.88}
},
"situational_state": {
"exhaustion": {"mean": 0.5, "certainty": 0.42, "plasticity": 0.81, "current_activation": 0.57},
"scarcity_pressure": {"mean": 0.77, "certainty": 0.52, "plasticity": 0.76, "current_activation": 0.86},
"humiliation": {"mean": 0.61, "certainty": 0.48, "plasticity": 0.8, "current_activation": 0.7},
"panic": {"mean": 0.42, "certainty": 0.41, "plasticity": 0.83, "current_activation": 0.49},
"triumph": {"mean": 0.08, "certainty": 0.36, "plasticity": 0.78, "current_activation": 0.04},
"grief": {"mean": 0.46, "certainty": 0.38, "plasticity": 0.73, "current_activation": 0.5},
"overstimulation": {"mean": 0.61, "certainty": 0.5, "plasticity": 0.8, "current_activation": 0.67},
"grievance_activation": {"mean": 0.49, "certainty": 0.44, "plasticity": 0.75, "current_activation": 0.55},
"acute_shame": {"mean": 0.69, "certainty": 0.48, "plasticity": 0.82, "current_activation": 0.78},
"perceived_status_threat": {"mean": 0.73, "certainty": 0.51, "plasticity": 0.76, "current_activation": 0.8}
},
"values": [
{
"value_id": "value-stay-unseen",
"label": "Survive by controlling what other people are allowed to know.",
"priority": 0.86,
"unforgivable_if_betrayed": true
}
]
},
"goals": [
{
"goal_id": "goal-keep-body-private",
"description": "Help without making the person behind the drone available for inspection.",
"scope": "scene",
"priority": 0.88,
"emotional_stake": "Being correctly seen would change every relationship at once.",
"blockers": [
"Cat's suspicion",
"drone damage",
"station gossip"
],
"status": "active"
}
],
"memories": {
"episodic": [],
"semantic": [
{
"memory_id": "memory-being-mistaken-safe",
"summary": "People are kinder to a machine they think is harmless than to a person who needs things.",
"salience": 0.88,
"confidence": 0.79,
"linked_event_ids": []
}
],
"relationship_summaries": []
},
"perceived_state_overlays": [
{
"observer_agent_id": "oz_operator",
"target_agent_id": "cat_marrigan",
"perceived_dimensions": {
"warmth": {"mean": 0.44, "certainty": 0.34, "plasticity": 0.55, "current_activation": 0.4},
"suspicion": {"mean": 0.83, "certainty": 0.71, "plasticity": 0.28, "current_activation": 0.9}
},
"beliefs": [
{
"belief_id": "belief-cat-will-pry",
"claim": "Cat will pry if she notices the drone is a mask, but she may protect the secret if she understands why it exists.",
"confidence": 0.58
}
],
"distortions": [
"underread_threat"
]
}
]
}
],
"relationships": [
{
"relationship_id": "rel-cat-oz",
"source_id": "cat_marrigan",
"target_id": "oz_operator",
"stance": {
"trust": {"mean": 0.31, "certainty": 0.38, "plasticity": 0.52, "current_activation": 0.27},
"respect": {"mean": 0.42, "certainty": 0.31, "plasticity": 0.49, "current_activation": 0.46},
"resentment": {"mean": 0.18, "certainty": 0.29, "plasticity": 0.55, "current_activation": 0.16},
"dependence": {"mean": 0.24, "certainty": 0.28, "plasticity": 0.56, "current_activation": 0.31},
"fear": {"mean": 0.2, "certainty": 0.25, "plasticity": 0.57, "current_activation": 0.22},
"fascination": {"mean": 0.61, "certainty": 0.35, "plasticity": 0.5, "current_activation": 0.76},
"obligation": {"mean": 0.16, "certainty": 0.22, "plasticity": 0.58, "current_activation": 0.13},
"envy": {"mean": 0.05, "certainty": 0.2, "plasticity": 0.5, "current_activation": 0.02},
"moral_disgust": {"mean": 0.04, "certainty": 0.21, "plasticity": 0.53, "current_activation": 0.03},
"perceived_status_gap": {"mean": 0.29, "certainty": 0.27, "plasticity": 0.54, "current_activation": 0.31},
"expectation_of_care": {"mean": 0.22, "certainty": 0.24, "plasticity": 0.58, "current_activation": 0.19},
"expectation_of_betrayal": {"mean": 0.47, "certainty": 0.37, "plasticity": 0.51, "current_activation": 0.52}
},
"summary": "Cat suspects Oz is more than a helpful drone and hates that this makes the drone more interesting.",
"unresolved_incident_ids": [
"event-drone-lie"
]
}
],
"events": [
{
"event_id": "event-drone-lie",
"kind": "dialogue",
"summary": "Oz dodged Cat's question about who was controlling the drone.",
"participants": [
"cat_marrigan",
"oz_operator"
],
"pressure_tags": [
"mask",
"privacy",
"suspicion"
]
}
],
"scenes": [
{
"scene_id": "scene-broken-taxi-oz",
"location": "repair alcove beside Cat's stalled taxi",
"participants": [
"cat_marrigan",
"oz_operator"
],
"public_stakes": [
"Cat needs the taxi moving before the next fare abandons her.",
"Oz can help with the repair."
],
"hidden_stakes": [
"Cat suspects Oz is a remote avatar rather than an autonomous drone.",
"Oz needs help to look casual enough that no one asks where the signal originates."
],
"dialogue_context_packs": [
{
"speaker_agent_id": "cat_marrigan",
"listener_ids": [
"oz_operator"
],
"speaker_local_truth": [
"The taxi is broken again.",
"Oz is useful.",
"Oz is probably hiding a person-shaped problem."
],
"speaker_beliefs": [
"Helpful strangers usually want something.",
"Machines do not usually dodge questions with that much personality."
],
"active_memories": [
"memory-bad-contract",
"memory-institutions-follow"
],
"active_goals": [
"goal-keep-taxi-operational"
],
"presentation_constraints": [
"Sound dry rather than openly grateful.",
"Probe without making it look like an interrogation."
],
"forbidden_context": [
"Do not reveal Oz's true controller unless Cat has earned it in-scene.",
"Do not mention author-only future plot beats."
]
},
{
"speaker_agent_id": "oz_operator",
"listener_ids": [
"cat_marrigan"
],
"speaker_local_truth": [
"Cat is suspicious.",
"The drone can repair the relay if Cat stops hovering.",
"The person behind Oz is not safe to expose."
],
"speaker_beliefs": [
"Cat may be kind under the knives.",
"If Cat names the mask too early, the relationship becomes dangerous."
],
"active_memories": [
"memory-being-mistaken-safe"
],
"active_goals": [
"goal-keep-body-private"
],
"presentation_constraints": [
"Be helpful, a little funny, and evasive.",
"Let the drone seem more autonomous than it is."
],
"forbidden_context": [
"Do not confess the operator's location.",
"Do not use information Oz could not know from the scene or prior relationship."
]
}
]
}
]
}

View file

@ -30,9 +30,14 @@ Ghostlight now has the persistence spine plus the first architecture payload:
- state distributions and prompt projection
- Aetheria content generation targets
- population scaling for socially dense authored slices
- first schema seam:
- `schemas/agent-state.schema.json`
- `schemas/agent-state.required-fields.json`
- `examples/agent-state.call-of-the-void.json`
- `tools/validate_agent_state.py`
There is still no runtime yet. The live machine is design, state discipline,
and a cleaner repo boundary.
There is still no full runtime yet. The live machine is design, state
discipline, a cleaner repo boundary, and the first executable state contract.
## Current Direction
@ -56,7 +61,8 @@ Pick the first implementation seam to cut:
- classifier/event data model
- prompt projection renderer for authoring outputs
The clean first move is still the canonical agent-state schema.
The canonical agent-state schema now exists as v0. The next move is to validate
and harden it before deriving perceived-state and prompt-projection fixtures.
## Warnings
@ -68,3 +74,5 @@ The clean first move is still the canonical agent-state schema.
truth storage.
- Do not accidentally re-promote the older corridor-crisis idea into a game MVP
because an old doc had a strong chin and good lighting.
- Do not build prompt projection on top of the v0 schema until the schema has
survived at least one hardening pass. One passing fixture is not revelation.

View file

@ -39,19 +39,29 @@ state and re-entry discipline.
- research backlog and social-model notes
- `docs/architecture/`
- world, state, and prompt-system architecture notes
- `schemas/agent-state.schema.json`
- v0 canonical agent-state contract for agents, relationships, events,
scenes, perceived overlays, and dialogue context packs
- `schemas/agent-state.required-fields.json`
- required first-class variable names for canonical state and relationship
stance
- `examples/agent-state.call-of-the-void.json`
- first Call of the Void-flavored fixture for schema and projection work
- `tools/ghostlight_state.py`
- compact state inspection and evidence or branch updates
- `tools/ghostlight_prepare_compaction.py`
- pre-compaction audit of required files, handoff content, and git hygiene
- `tools/validate_agent_state.py`
- dependency-free validator for the current schema fixture invariants
## What Does Not Exist Yet
- a canonical agent-state schema
- a classifier pipeline
- a prompt renderer
- a simulation/event loop
- a relationship engine
- a culture prior engine
That absence is not a bug. It is just the current phase. The repo now owns the
theory body cleanly, even if the runtime skeleton is still missing.
The canonical agent-state schema now exists as a v0 seam, not as a finished
runtime. It should be hardened before the prompt renderer or classifier pipeline
starts treating it as bedrock. The skeleton has one bone. Try not to worship it.

View file

@ -2,13 +2,16 @@
## Near-Term Sequence
1. Freeze the first canonical state model.
1. Freeze the first canonical state model. In progress.
- Define the latent variable families.
- Define first-class dimensions, including volatility, attachment-seeking,
and distance-seeking.
- Define canonical state versus perceived state.
- Keep authoring outputs explainable from the state, not just generated from
stylish prompt fog.
- Maintain `schemas/agent-state.schema.json`,
`schemas/agent-state.required-fields.json`, and the example fixtures as
the first executable contract.
2. Freeze the first event and classifier schema.
- Define event labels.

View file

@ -5,7 +5,7 @@
"description": "Persistent research repo for socially situated generative agent architecture.",
"scripts": {
"state:status": "\"C:\\Users\\Meta\\.cache\\codex-runtimes\\codex-primary-runtime\\dependencies\\python\\python.exe\" .\\tools\\ghostlight_state.py status",
"state:prepare-compaction": "\"C:\\Users\\Meta\\.cache\\codex-runtimes\\codex-primary-runtime\\dependencies\\python\\python.exe\" .\\tools\\ghostlight_prepare_compaction.py"
"state:prepare-compaction": "\"C:\\Users\\Meta\\.cache\\codex-runtimes\\codex-primary-runtime\\dependencies\\python\\python.exe\" .\\tools\\ghostlight_prepare_compaction.py",
"schema:validate": "\"C:\\Users\\Meta\\.cache\\codex-runtimes\\codex-primary-runtime\\dependencies\\python\\python.exe\" .\\tools\\validate_agent_state.py"
}
}

View file

@ -0,0 +1,77 @@
{
"schema_version": "ghostlight.required_fields.v0",
"variable_groups": {
"underlying_organization": [
"self_coherence",
"contingent_worth",
"shame_sensitivity",
"reciprocity_capacity",
"mentalization_quality",
"authenticity_tolerance",
"mask_rigidity",
"external_regulation_dependence"
],
"stable_dispositions": [
"novelty_seeking",
"conformity",
"status_hunger",
"risk_tolerance",
"sociability",
"baseline_threat_sensitivity",
"aesthetic_appetite",
"ideological_rigidity"
],
"behavioral_dimensions": [
"warmth",
"drive",
"grandiosity",
"validation_seeking",
"anxiety",
"control_pressure",
"hostility",
"suspicion",
"rigidity",
"withdrawal",
"volatility",
"attachment_seeking",
"distance_seeking"
],
"presentation_strategy": [
"charm",
"compliance",
"superiority",
"detachment",
"seductiveness",
"competence_theater",
"moral_theater",
"strategic_opacity",
"cultivated_harmlessness"
],
"situational_state": [
"exhaustion",
"scarcity_pressure",
"humiliation",
"panic",
"triumph",
"grief",
"overstimulation",
"grievance_activation",
"acute_shame",
"perceived_status_threat"
],
"relationship_stance": [
"trust",
"respect",
"resentment",
"dependence",
"fear",
"fascination",
"obligation",
"envy",
"moral_disgust",
"perceived_status_gap",
"expectation_of_care",
"expectation_of_betrayal"
]
}
}

View file

@ -0,0 +1,649 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/GameCult/Ghostlight/schemas/agent-state.schema.json",
"title": "Ghostlight Agent State Schema",
"description": "First canonical contract for Ghostlight agent, perception, relationship, event, scene, and dialogue-context state.",
"type": "object",
"required": [
"schema_version",
"world",
"agents",
"relationships",
"events",
"scenes"
],
"additionalProperties": false,
"properties": {
"schema_version": {
"const": "ghostlight.agent_state.v0"
},
"world": {
"$ref": "#/$defs/world_state"
},
"agents": {
"type": "array",
"items": {
"$ref": "#/$defs/agent"
}
},
"relationships": {
"type": "array",
"items": {
"$ref": "#/$defs/relationship"
}
},
"events": {
"type": "array",
"items": {
"$ref": "#/$defs/event"
}
},
"scenes": {
"type": "array",
"items": {
"$ref": "#/$defs/scene"
}
}
},
"$defs": {
"world_state": {
"type": "object",
"required": [
"world_id",
"setting",
"time",
"canon_context"
],
"additionalProperties": false,
"properties": {
"world_id": {
"type": "string"
},
"setting": {
"type": "string"
},
"time": {
"type": "object",
"required": [
"label"
],
"additionalProperties": true,
"properties": {
"label": {
"type": "string"
}
}
},
"canon_context": {
"type": "array",
"items": {
"type": "string"
}
},
"ambient_pressures": {
"type": "array",
"items": {
"$ref": "#/$defs/pressure"
}
}
}
},
"agent": {
"type": "object",
"required": [
"agent_id",
"identity",
"canonical_state",
"goals",
"memories",
"perceived_state_overlays"
],
"additionalProperties": false,
"properties": {
"agent_id": {
"type": "string"
},
"identity": {
"$ref": "#/$defs/identity"
},
"canonical_state": {
"$ref": "#/$defs/canonical_state"
},
"goals": {
"type": "array",
"items": {
"$ref": "#/$defs/goal"
}
},
"memories": {
"$ref": "#/$defs/memory_bundle"
},
"perceived_state_overlays": {
"type": "array",
"items": {
"$ref": "#/$defs/perceived_state_overlay"
}
}
}
},
"identity": {
"type": "object",
"required": [
"name",
"roles",
"origin",
"public_description"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"roles": {
"type": "array",
"items": {
"type": "string"
}
},
"origin": {
"type": "string"
},
"public_description": {
"type": "string"
},
"private_notes": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"canonical_state": {
"type": "object",
"required": [
"underlying_organization",
"stable_dispositions",
"behavioral_dimensions",
"presentation_strategy",
"situational_state",
"values"
],
"additionalProperties": false,
"properties": {
"underlying_organization": {
"$ref": "#/$defs/variable_map"
},
"stable_dispositions": {
"$ref": "#/$defs/variable_map"
},
"behavioral_dimensions": {
"$ref": "#/$defs/variable_map"
},
"presentation_strategy": {
"$ref": "#/$defs/variable_map"
},
"situational_state": {
"$ref": "#/$defs/variable_map"
},
"values": {
"type": "array",
"items": {
"$ref": "#/$defs/value_commitment"
}
}
}
},
"variable_map": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/state_variable"
}
},
"state_variable": {
"type": "object",
"required": [
"mean",
"certainty",
"plasticity",
"current_activation"
],
"additionalProperties": false,
"properties": {
"mean": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"certainty": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"plasticity": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"current_activation": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"evidence": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"value_commitment": {
"type": "object",
"required": [
"value_id",
"label",
"priority",
"unforgivable_if_betrayed"
],
"additionalProperties": false,
"properties": {
"value_id": {
"type": "string"
},
"label": {
"type": "string"
},
"priority": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"unforgivable_if_betrayed": {
"type": "boolean"
}
}
},
"goal": {
"type": "object",
"required": [
"goal_id",
"description",
"scope",
"priority",
"emotional_stake",
"status"
],
"additionalProperties": false,
"properties": {
"goal_id": {
"type": "string"
},
"description": {
"type": "string"
},
"scope": {
"enum": [
"immediate",
"scene",
"case",
"arc",
"life"
]
},
"priority": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"emotional_stake": {
"type": "string"
},
"blockers": {
"type": "array",
"items": {
"type": "string"
}
},
"status": {
"enum": [
"active",
"blocked",
"dormant",
"resolved",
"abandoned"
]
}
}
},
"memory_bundle": {
"type": "object",
"required": [
"episodic",
"semantic",
"relationship_summaries"
],
"additionalProperties": false,
"properties": {
"episodic": {
"type": "array",
"items": {
"$ref": "#/$defs/memory"
}
},
"semantic": {
"type": "array",
"items": {
"$ref": "#/$defs/memory"
}
},
"relationship_summaries": {
"type": "array",
"items": {
"$ref": "#/$defs/memory"
}
}
}
},
"memory": {
"type": "object",
"required": [
"memory_id",
"summary",
"salience",
"confidence"
],
"additionalProperties": false,
"properties": {
"memory_id": {
"type": "string"
},
"summary": {
"type": "string"
},
"salience": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"linked_event_ids": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"perceived_state_overlay": {
"type": "object",
"required": [
"observer_agent_id",
"target_agent_id",
"perceived_dimensions",
"beliefs",
"distortions"
],
"additionalProperties": false,
"properties": {
"observer_agent_id": {
"type": "string"
},
"target_agent_id": {
"type": "string"
},
"perceived_dimensions": {
"$ref": "#/$defs/variable_map"
},
"beliefs": {
"type": "array",
"items": {
"$ref": "#/$defs/belief"
}
},
"distortions": {
"type": "array",
"items": {
"enum": [
"hostile_attribution",
"false_reassurance",
"overread_warmth",
"underread_threat",
"false_mask_detection",
"missed_mask_detection",
"status_injury"
]
}
}
}
},
"belief": {
"type": "object",
"required": [
"belief_id",
"claim",
"confidence"
],
"additionalProperties": false,
"properties": {
"belief_id": {
"type": "string"
},
"claim": {
"type": "string"
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
}
}
},
"relationship": {
"type": "object",
"required": [
"relationship_id",
"source_id",
"target_id",
"stance",
"summary"
],
"additionalProperties": false,
"properties": {
"relationship_id": {
"type": "string"
},
"source_id": {
"type": "string"
},
"target_id": {
"type": "string"
},
"stance": {
"$ref": "#/$defs/variable_map"
},
"summary": {
"type": "string"
},
"unresolved_incident_ids": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"event": {
"type": "object",
"required": [
"event_id",
"kind",
"summary",
"participants",
"pressure_tags"
],
"additionalProperties": false,
"properties": {
"event_id": {
"type": "string"
},
"kind": {
"enum": [
"dialogue",
"action",
"memory",
"relationship_shift",
"scene_outcome",
"author_note"
]
},
"summary": {
"type": "string"
},
"participants": {
"type": "array",
"items": {
"type": "string"
}
},
"pressure_tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"scene": {
"type": "object",
"required": [
"scene_id",
"location",
"participants",
"public_stakes",
"hidden_stakes",
"dialogue_context_packs"
],
"additionalProperties": false,
"properties": {
"scene_id": {
"type": "string"
},
"location": {
"type": "string"
},
"participants": {
"type": "array",
"items": {
"type": "string"
}
},
"public_stakes": {
"type": "array",
"items": {
"type": "string"
}
},
"hidden_stakes": {
"type": "array",
"items": {
"type": "string"
}
},
"dialogue_context_packs": {
"type": "array",
"items": {
"$ref": "#/$defs/dialogue_context_pack"
}
}
}
},
"dialogue_context_pack": {
"type": "object",
"required": [
"speaker_agent_id",
"listener_ids",
"speaker_local_truth",
"speaker_beliefs",
"active_memories",
"active_goals",
"presentation_constraints",
"forbidden_context"
],
"additionalProperties": false,
"properties": {
"speaker_agent_id": {
"type": "string"
},
"listener_ids": {
"type": "array",
"items": {
"type": "string"
}
},
"speaker_local_truth": {
"type": "array",
"items": {
"type": "string"
}
},
"speaker_beliefs": {
"type": "array",
"items": {
"type": "string"
}
},
"active_memories": {
"type": "array",
"items": {
"type": "string"
}
},
"active_goals": {
"type": "array",
"items": {
"type": "string"
}
},
"presentation_constraints": {
"type": "array",
"items": {
"type": "string"
}
},
"forbidden_context": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"pressure": {
"type": "object",
"required": [
"pressure_id",
"label",
"intensity"
],
"additionalProperties": false,
"properties": {
"pressure_id": {
"type": "string"
},
"label": {
"type": "string"
},
"intensity": {
"type": "number",
"minimum": 0,
"maximum": 1
}
}
}
}
}

View file

@ -5,3 +5,4 @@
{"ts": "2026-05-01T17:35:57+00:00", "type": "source", "status": "ok", "note": "Imported the scattered Call of the Void Google Doc brainstorm into docs/aetheria/call-of-the-void-brainstorm.md as raw source material; compact canon remains the AetheriaLore Call of the Void design note."}
{"ts": "2026-05-01T17:40:43+00:00", "type": "design", "status": "accepted", "note": "Clarified Ghostlight authoring rationale: the system should build drama and narrative pressure, then render character-local dialogue context packs constrained to what each character knows, feels, wants, remembers, hides, and perceives."}
{"ts": "2026-05-01T17:46:36+00:00", "type": "design", "status": "accepted", "note": "Corrected Call of the Void continuity drift: old colony-fleet no-return twist is obsolete; current pressure should come from the whole Solar system being sequestered into Elysium with its institutions, conflicts, and unresolved machinery intact."}
{"ts": "2026-05-01T17:52:01+00:00", "type": "implementation", "status": "ok", "note": "Added v0 canonical agent-state schema, required first-class dimension list, Call of the Void example fixture, and dependency-free validation script. schema:validate passes on the first fixture."}

View file

@ -14,10 +14,9 @@ objective:
procedural drama, and eventual live-world simulation without collapsing
into prompt soup.
current: >
Stand up the repo spine, consolidate the architecture we already know, and
prepare the first implementation slices around state modeling, cultural
priors, signal classification, prompt projection, and authoring-oriented
drama generation.
Freeze the first canonical agent-state schema and validation seam, then use
it as the contract for perceived-state overlays, classifier/event records,
prompt projection, and authoring-oriented drama generation.
constraints:
- Prefer explicit latent state over persona theater.
@ -74,7 +73,8 @@ accepted_design:
- The near-term Aetheria consumer is authoring support: Call of the Void content generation plus narrated timeline dialogue and procedural drama scaffolding.
- Ghostlight should build drama and narrative pressure, then project character-local dialogue contexts so characters can breathe and act from their own partial state.
- Call of the Void pressure should come from Sol's whole unresolved inheritance being trapped inside Elysium, not from a lone expedition discovering there is no way home.
- The first schema contract lives in schemas/agent-state.schema.json with required first-class dimensions in schemas/agent-state.required-fields.json and a validator in tools/validate_agent_state.py.
current_status:
summary: Ghostlight has the Epiphany-style persistence spine and the first architecture payload for personality structure, signal classification, prompt projection, population scale, and Aetheria authoring support. It still has no runtime organs. The first Aetheria consumer target is Call of the Void content generation plus narrated timeline dialogue and procedural drama scaffolding.
next_action: Freeze the first implementation organ. The clean first move remains the canonical agent-state schema, followed by perceived-state overlays, event/classifier records, and prompt projection for authoring outputs.
summary: Ghostlight now has the Epiphany-style persistence spine, the first architecture payload, and the initial canonical agent-state schema seam for Call of the Void-style authoring examples. The schema is still v0 and should be hardened before building prompt projection on top of it.
next_action: Validate and harden the v0 canonical agent-state schema, then derive the first perceived-state overlay and prompt-projection fixtures from it.

View file

@ -0,0 +1,211 @@
"""Validate Ghostlight agent-state examples.
This is not a full JSON Schema implementation. It is a focused smoke validator
for the invariants the first Ghostlight schema must not violate.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
REQUIRED_FIELDS_PATH = ROOT / "schemas" / "agent-state.required-fields.json"
DEFAULT_EXAMPLES = [ROOT / "examples" / "agent-state.call-of-the-void.json"]
class ValidationError(Exception):
pass
def load_json(path: Path) -> Any:
with path.open("r", encoding="utf-8") as handle:
return json.load(handle)
def require(condition: bool, message: str) -> None:
if not condition:
raise ValidationError(message)
def require_keys(obj: dict[str, Any], keys: list[str], path: str) -> None:
missing = [key for key in keys if key not in obj]
require(not missing, f"{path} missing required keys: {', '.join(missing)}")
def require_0_1(value: Any, path: str) -> None:
require(isinstance(value, (int, float)), f"{path} must be numeric")
require(0 <= value <= 1, f"{path} must be between 0 and 1")
def validate_state_variable(obj: Any, path: str) -> None:
require(isinstance(obj, dict), f"{path} must be an object")
require_keys(obj, ["mean", "certainty", "plasticity", "current_activation"], path)
for key in ["mean", "certainty", "plasticity", "current_activation"]:
require_0_1(obj[key], f"{path}.{key}")
def validate_variable_map(
obj: Any,
required_names: list[str] | None,
path: str,
) -> None:
require(isinstance(obj, dict), f"{path} must be an object")
if required_names is not None:
missing = [name for name in required_names if name not in obj]
require(not missing, f"{path} missing variables: {', '.join(missing)}")
for name, value in obj.items():
validate_state_variable(value, f"{path}.{name}")
def validate_agent(agent: dict[str, Any], required: dict[str, list[str]]) -> None:
agent_id = agent.get("agent_id", "<missing>")
path = f"agents[{agent_id}]"
require_keys(
agent,
["agent_id", "identity", "canonical_state", "goals", "memories", "perceived_state_overlays"],
path,
)
canonical = agent["canonical_state"]
require_keys(
canonical,
[
"underlying_organization",
"stable_dispositions",
"behavioral_dimensions",
"presentation_strategy",
"situational_state",
"values",
],
f"{path}.canonical_state",
)
for group in [
"underlying_organization",
"stable_dispositions",
"behavioral_dimensions",
"presentation_strategy",
"situational_state",
]:
validate_variable_map(canonical[group], required[group], f"{path}.canonical_state.{group}")
for index, value in enumerate(canonical["values"]):
require_keys(value, ["value_id", "label", "priority", "unforgivable_if_betrayed"], f"{path}.values[{index}]")
require_0_1(value["priority"], f"{path}.values[{index}].priority")
for index, goal in enumerate(agent["goals"]):
require_keys(goal, ["goal_id", "description", "scope", "priority", "emotional_stake", "status"], f"{path}.goals[{index}]")
require_0_1(goal["priority"], f"{path}.goals[{index}].priority")
memories = agent["memories"]
require_keys(memories, ["episodic", "semantic", "relationship_summaries"], f"{path}.memories")
for group_name in ["episodic", "semantic", "relationship_summaries"]:
for index, memory in enumerate(memories[group_name]):
memory_path = f"{path}.memories.{group_name}[{index}]"
require_keys(memory, ["memory_id", "summary", "salience", "confidence"], memory_path)
require_0_1(memory["salience"], f"{memory_path}.salience")
require_0_1(memory["confidence"], f"{memory_path}.confidence")
for index, overlay in enumerate(agent["perceived_state_overlays"]):
overlay_path = f"{path}.perceived_state_overlays[{index}]"
require_keys(
overlay,
["observer_agent_id", "target_agent_id", "perceived_dimensions", "beliefs", "distortions"],
overlay_path,
)
validate_variable_map(overlay["perceived_dimensions"], None, f"{overlay_path}.perceived_dimensions")
for belief_index, belief in enumerate(overlay["beliefs"]):
belief_path = f"{overlay_path}.beliefs[{belief_index}]"
require_keys(belief, ["belief_id", "claim", "confidence"], belief_path)
require_0_1(belief["confidence"], f"{belief_path}.confidence")
def validate_document(document: dict[str, Any], required: dict[str, list[str]], source: Path) -> None:
require(document.get("schema_version") == "ghostlight.agent_state.v0", f"{source} has wrong schema_version")
require_keys(document, ["world", "agents", "relationships", "events", "scenes"], str(source))
agent_ids = [agent["agent_id"] for agent in document["agents"]]
require(len(agent_ids) == len(set(agent_ids)), f"{source} has duplicate agent_id values")
agent_id_set = set(agent_ids)
goal_ids: set[str] = set()
memory_ids: set[str] = set()
for agent in document["agents"]:
validate_agent(agent, required)
goal_ids.update(goal["goal_id"] for goal in agent["goals"])
for memories in agent["memories"].values():
memory_ids.update(memory["memory_id"] for memory in memories)
event_ids = {event["event_id"] for event in document["events"]}
for index, relationship in enumerate(document["relationships"]):
path = f"relationships[{index}]"
require_keys(relationship, ["relationship_id", "source_id", "target_id", "stance", "summary"], path)
require(relationship["source_id"] in agent_id_set, f"{path}.source_id references unknown agent")
require(relationship["target_id"] in agent_id_set, f"{path}.target_id references unknown agent")
validate_variable_map(relationship["stance"], required["relationship_stance"], f"{path}.stance")
for event_id in relationship.get("unresolved_incident_ids", []):
require(event_id in event_ids, f"{path}.unresolved_incident_ids references unknown event: {event_id}")
for index, event in enumerate(document["events"]):
path = f"events[{index}]"
require_keys(event, ["event_id", "kind", "summary", "participants", "pressure_tags"], path)
for participant_id in event["participants"]:
require(participant_id in agent_id_set, f"{path}.participants references unknown agent: {participant_id}")
for index, scene in enumerate(document["scenes"]):
path = f"scenes[{index}]"
require_keys(scene, ["scene_id", "location", "participants", "dialogue_context_packs"], path)
for participant_id in scene["participants"]:
require(participant_id in agent_id_set, f"{path}.participants references unknown agent: {participant_id}")
for pack_index, pack in enumerate(scene["dialogue_context_packs"]):
pack_path = f"{path}.dialogue_context_packs[{pack_index}]"
require_keys(
pack,
[
"speaker_agent_id",
"listener_ids",
"speaker_local_truth",
"speaker_beliefs",
"active_memories",
"active_goals",
"presentation_constraints",
"forbidden_context",
],
pack_path,
)
require(pack["speaker_agent_id"] in agent_id_set, f"{pack_path}.speaker_agent_id references unknown agent")
for listener_id in pack["listener_ids"]:
require(listener_id in agent_id_set, f"{pack_path}.listener_ids references unknown agent: {listener_id}")
for goal_id in pack["active_goals"]:
require(goal_id in goal_ids, f"{pack_path}.active_goals references unknown goal: {goal_id}")
for memory_id in pack["active_memories"]:
require(memory_id in memory_ids, f"{pack_path}.active_memories references unknown memory: {memory_id}")
forbidden_blob = "\n".join(pack["forbidden_context"]).lower()
local_blob = "\n".join(pack["speaker_local_truth"] + pack["speaker_beliefs"]).lower()
for forbidden in ["author-only", "future plot"]:
if forbidden in forbidden_blob:
require(forbidden not in local_blob, f"{pack_path} leaks forbidden context into local state: {forbidden}")
def main() -> int:
parser = argparse.ArgumentParser(description="Validate Ghostlight agent-state examples.")
parser.add_argument("paths", nargs="*", type=Path, help="Example JSON files to validate.")
args = parser.parse_args()
required_doc = load_json(REQUIRED_FIELDS_PATH)
required = required_doc["variable_groups"]
paths = args.paths or DEFAULT_EXAMPLES
for path in paths:
document = load_json(path)
validate_document(document, required, path)
print(f"ok: {path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())