Skip to content

feature: Graph-based story generation #1

Description

@ottlseo

Goal

Validate a flow where characters, relationships, and past events are defined as an ontology graph, then queried to generate new stories. The premise of a story is proposed by the graph rather than written by a human.

Why a graph instead of pasting a setting document into the prompt

  1. Inference. Relations that were never written down are derived by rules. A loves B + B loves C yields A rival_of C automatically. Structures the author of the setting did not notice become visible.
  2. Probes. Structural gaps in the graph are scored and ranked, so the machine proposes the story premise. No human instruction of the form "write a story about X".
  3. Writeback. A generated story emits new nodes and edges that merge back into the graph. The next round of probes finds new gaps and the world accumulates.

Pipeline

world.json --> reason() --> find_seeds() --> ContextPack --> LLM --> story
              inference     seed ranking    subgraph serialization    |
                  ^                                                  |
                  +---------------- apply_writeback() <--------------+

Scope

  • world.json schema: nodes, edges, ontology declarations, inference rules
  • In-memory triple store with a fixed-point inference engine (structural: inverse / symmetric / transitive; rule-based: SPARQL-style basic graph patterns declared in JSON)
  • 8 story seed probes with scoring
  • Subgraph to LLM context pack serialization (characters / relations / timeline / knowledge state)
  • Amazon Bedrock Converse integration and writeback payload parsing
  • Demo site: graph visualization plus branching story output
  • Migration to a graph database (Neo4j / Neptune)
  • Writeback validation against existing canon

Ontology schema

Everything is declared in the ontology block of world.json, so the world can be changed without touching code.

Structural inference:

"child_of":   { "inverse": "parent_of" },
"sibling_of": { "symmetric": true },
"located_in": { "transitive": true }

Rule inference:

{
  "id": "leverage_by_secret",
  "when": [["?a", "knows", "?s"],
           ["?s", "concerns", "?b"],
           ["?s", "hidden_from", "?b"]],
  "where": { "neq": [["?a", "?b"]] },
  "then": ["?a", "has_leverage_over", "?b"],
  "props": { "tension": 0.9 }
}

Probes

Probe What it looks for
secret_leverage A secret hidden from the person it concerns. A timed bomb
love_triangle Rivalry derived from affection. Shared form (a to c, b to c) and chain form (a to b, b to c)
unresolved_tension High-tension relations with no event node resolving them
chekhov_object Objects introduced but never paid off
strangers_shared_past Two people in the same event with no explicit relation
severed_bond Broken mentor relations with neither reconciliation nor parting recorded
dangling_consequence Events with no outgoing caused edge. A price not yet paid
contested_goal Multiple characters whose want points at the same target

Results so far

  • 27 nodes and 52 asserted edges produce 52 derived edges at the inference fixed point, and 19 ranked story seeds.
  • The demo renders 4 seeds, each expanding into 3 branches. Every branch writes a different set of edges back to the graph, so the next round of probes finds different gaps. Example: in the triangle seed, branch A breaks the jinu serves gwima edge while branch C keeps it and adds rumi drawn_to gwima instead.
  • Derived edges carry provenance (derived_by), so the demo can distinguish asserted relations from inferred ones.
Image

Known limitations

  • Inference is a full join, so it slows down as the node count grows. Rules like allied_by_faction and shared_past produce O(n^2) edges. On a real graph database, query-time expansion is the right call instead of materialization.
  • Probe score weights are hand-tuned. There is no feedback loop telling us which seeds actually turn into good stories.
  • contested_goal string-matches free-text want fields against node names. Modeling goals as edges (a desires predicate) would remove this.
  • Writeback payloads are validated for shape only, not for contradiction against existing facts.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions