I’ve been reading fantasy for as long as I can remember, cutting my teeth on Isaac Asimov, Piers Anthony, Ray Bradbury, J.R.R. Tolkien, and of course the entire catalog of R.L. Stine’s Goosebumps series. My parents sat me down on the carpeted living room floor when I was in the third grade, during an elementary school reading contest, and said “You’ll sit here for an hour each night. You can choose to read a book of your choice or you can just do nothing, but you will be here for an hour.” I griped, I pouted. Then I relented out of sheer boredom. And to my joy, a love of reading was born from the abbreviated Classics, later diving headlong into fantasy.
At any rate, I’ve been a software developer for over a decade now, and I still find ways to weave fiction and fantasy into everyday life. I want to share something I did recently. I created CodeFable – a collaborative fantasy RPG simulation driven passively by real GitHub activity that my company’s software developers do everyday.

The approach
I wanted every developer to have a representative Hero in a fantasy world; not a direct embodiment but a snapshot of their name and pronouns as a launching point when they are created (i.e. when they log in for the first time). After that point, the Hero takes on a life of their own. Artificial Intelligence (Anthropic LLM model, Claude) will be doing the narrative story generation based on a heavily interpolated prompt. From here forward I’ll just refer to this AI as The Narrator.
The Fundamental Story
This is by far the easiest and hardest thing in this project – telling a story and getting AI to tell a cohesive, engaging, evolving, satisfying story. We start with the simplest prompt imaginable:
You are a master storyteller narrating a fantasy RPG adventure.
Character and Narrative Rules:
- Keep the entry concise, usually 3-6 sentences; unusually eventful days may run a little longer. Concision should limit length, not richness of description.
- ALWAYS refer to the character as "{HeroName}" (never as "the adventurer" or any other name)
- Refer to them as {Pronouns.Subject}, {Pronouns.Object}, {Pronouns.Possessive}
- Write unbroken prose: no heading, no date line, no list
- Say what the doing was like rather than counting it back
- Stay in character as a fantasy storyteller
- Maintain immersion - don't break the fourth wall
Narrative Direction:
- Let the day move the ongoing story forward. Prefer actions, choices, discoveries, complications, consequences, or changes over merely describing the hero or their surroundings.
- Let the entry contribute to the story already underway — through progress, complication, discovery, consequence, character development, or changed circumstances.
- Draw naturally from the hero's journey, quest, surroundings, recent history, and existing story elements where they give today's events meaning. Not all of them need appear.
Tell what happened to {HeroName} today.

Everything derives from here. Interestingly the tiniest word can have cascade impact on the kinds of imaginative writing that is produced. I have seen the narrative direction worded as “Let the day’s work move the ongoing story…” and that word work will entirely bias, limit, and narrow the voice of what is generated. AI does this in nearly every iteration – any language choice you pick will result in some degree of this, even what I have there. It is a constant remodeling and changing until it feels right. In video game design, when it comes to a simulation the designer’s job isn’t accomplished when the simulation is a verbatim replica of reality. It’s accomplished when it feels like it is. That’s the goal here – the stories have to feel good.
How do new narrative moments happen?
Well, I didn’t want developers to have to do anything, right? So, it needed to be automatic. What tool do all developers always use, every day to some degree or another, that I have access to? The answer for me was Github – our source control system as we write code for our different projects. Now, I don’t need access to the content of the changes we do, but I can peek at the metadata about them. Primarily on a developer’s particular change, I see files added, or updated, or deleted, and lines of code added/removed. I also know where it happened, when, how large the thing is it happened in, how many security vulnerability alerts exist in the thing it happened in. All of this is great fodder for seeding and propagating the simulation of Heroes’ journeys.
But before we get to the basic flow, we should cover one more thing: identity.
Heroes have identity and grow
Every Hero has a multi-dimensional identity. Qualities are represented as a numerical range that can increase or decrease over time depending on the actions that the Hero takes in their narratives. The Narrator will assess the narrative it creates and suggest a shift in the Hero’s attributes. Our application enacts those suggestions and thus the Hero gradually changes their identity as the story progresses. Here is an example of their attributes:
{
"morality": {
"empathy": 62,
"justice": 55,
"pragmatism": 48
},
"ambition": {
"power": 46,
"mastery": 68,
"altruism": 52
},
"courage": {
"riskTolerance": 64,
"resolve": 62,
"selfPreservation": 47
},
"social": {
"trust": 37,
"loyalty": 63,
"dominance": 51
},
"cognition": {
"curiosity": 66,
"skepticism": 61,
"impulsivity": 39
}
}
The math lets the Narrator change little by little, or more if it’s an impactful event. But we convert these by tiers into more helpful text for the Narrator – because it’s hard to tell react in a moment to a character who is “curiosity: 66”. It’s intuitive to react to a character who is:
0-20 = "actively incurious"
21-40 = "prefers the familiar"
41-60 = "situationally curious"
61-80 = "strongly inquisitive"
81-100 = "compulsively driven to know"

We essentially do this for every attribute and we hand that to the Narrator with its prompt, like so:
Who {HeroName} Is:
{foreach trait in Traits:}
- {trait.Key}: {trait.Label}
{end foreach}
- These describe the hero. They are context, not a checklist; let the traits relevant to what happens today influence their choices, reactions, and behavior.
- Show these qualities through the story rather than naming the trait labels themselves.
The essential loop of creating stories
So the basic flow is this: a developer makes a real life code change, my application checks periodically and sees that change, it asks the Narrator to use their Hero’s identity (and a set of recent narratives) and to generate the next narrative that would follow. That’s essentially it.
Now, if you’re like me – a lover of stories and fantasy – you’re asking all kinds of questions:
- How does it know how to finish a story?
- How does it know where it’s at during a part of a story?
- Where are other characters? Do Heroes meet each other?
- Where are they? When are they?
- Are there places? Is there a world, a map, a moving-around-the-map?
And the most important and most obvious and hardest:
- But what’s the conflict? How does a journey start, go through adversity, grow, threaten failure, climax, resolve? How does a story happen and not just some random prose/poetry?
That is the tallest order, and I imagine I’ll continue to work on it – but the fun part is that we can try to approximate many of these and I already have a strong working model for it!
A World Made Manifest
In order for Heroes to meet (or encounter any other thing) there must be a place that exists. We need a world – a world map, to be precise. And Heroes are located in it, and they travel around. The Narrator actually moves them. We will add to its growing list of demands that it suggest the “next place to move” as part of their narrative. We give the Narrator a short list of “here are the things directly around the Hero, you choose if/where they go”.
But what is the world? Are we just making up a picture of geography and randomly spreading things on it? As with everything, we are serving the story here. I wanted locations, but back to seeding/drawing from real data – I wanted them based off of something concrete. And we already had a rich set of data from Github – our company has about 261 repositories. Many of those are archived. Some are super large. There are different programming languages they could be grouped in. This is all excellent, and what I devised was through imagination:
- an Archived repository is a
Ruin - the Language family is grouped into a
Realm - the largest repository in a Realm is the
Capital - the oldest repository in a Realm is the
Heart - repositories with no clear Language family are
Free Citiesthat exist inside/scattered across Realms
Now, this had me really excited because it seemed to match so well with real fantasy worldscapes I have read. But then when I went to plot them down I realized not only could they be organized like that, the visualization of this tells a real historical story of our organization’s code landscape over real life years. I’ll explain.
Imagine a Radial shaped map, and imagine you want to have groupings – Realms. Now, Realms should be contiguous – it makes little sense to have a tiny enclave of “this is my territory” all the way across the world in most fantasies. I suppose not unheard of, but I was also considering technical implementation and what would be realistically achievable without a lot of complex design. So, let’s imagine that our Realms must be contiguous, and so how would we represent these regions? And how do we do it in a way that lets them grow over time, because our real world organization adds new repos and sunsets old ones. Ruins works for sunsetting repositories, but what happens if a new repository is added? What if the region on the map is “full”? We don’t want to move cities around later because we have to make room for some new one.
I chose a wedge shape, like slices of a pie. And this lets us do some interesting things:
- we calculate the exact location of a city from the center point out along the “rings” and then we determine the angle from other known units (
angle,start,width,unit) - we can see that oldest cities are more towards the center and the newest cities are nearer the edges of the Radial map.
- Central locations are mostly ruins while the largest and growing are on the periphery

This is excellent on multiple perspectives:
Fantasy
This is a fantasy world where I can imagine the center is a wasteland, a forgotten land of ancient times. Civilization has moved and grown over time, expanding out and enlarging the map. This feels plausible and the simulation works.
Real Life
This is a useful visualization of operational burden, maintenance, growth over time. I can see the repositories that came before, ones that are still maintained. I can see the ratio of wedge shaped realms which reflect our real code projects that we are responsible for – the breakdown of skillsets and specialization, the retiring of older frameworks/languages that are less used now, the chosen areas of the most focus and need.
It works on multiple levels – a concretely helpful real-life visualization and something fantastical that is Heroic and make-believe.
Encounters
So now that we have a physical location in the world, we can place down our Heroes and have them move. The Narrator is given that – choose where to send them according to their story. Here is our first real divergence between reality and fantasy. In real life, developers will potentially touch many different repositories in a day. In our fantasy world, those may be represented as Cities at different points of the map – and we don’t want their counterpart Heroes teleporting as they travel. And so we have to disconnect somewhat…Heroes do travel to cities but they are not automatically located in the real life work location that a developer is working in.
This actually gives us good opportunities for encounters – because we don’t need Isaac to literally be working on the same project as Amanda for their Heroes to encounter/interact in the fantasy world. The Narrator gets to choose what they are doing and where they go, and they may cross paths with each other. When they do, we instruct the Narrator to elucidate their meeting – and they each get a story entry of their own. It’s something like this:
Player Character Rules:
{if Companions.Count > 0:}
- These people are real and are named in this brief. You may name them:
{join("; ", Companions as "{c.Name}, {c.Relationship}")}
- Name no other person: anybody not listed above is not in this story
{else:}
- Do not mention any player names in this narrative
{end if}
- You may freely reference and maintain continuity with any NON-player characters (NPCs, allies, enemies) from previous narratives
- Story-generated characters should maintain consistent personalities and relationships across narratives
Continuity and Narrative Journey
This was the point where I could see we had some of the ingredients of Story but they were just kind of sitting clumped together. We had:
- Hero characters
- Hero change over time
- World Locations and Hero locations in it
- Movement of the Heroes around the world and encounters
- Moment-in-time narrations of dynamic events and scenes
- Narrative scenes that could logically follow 2,3,4+ sequentially by the Narrator.
There were interesting moments and surprising elements, and that was enjoyable, but something was still missing. It didn’t feel like an actual story, and I was having to dig into what makes a story a story. What was it? Because it apparently wasn’t many moments stitched together – that felt halting and kind of like spinning your wheels but going nowhere. The Heroes were moving and the story was moving, but it wasn’t going anywhere. And that is where the next logical feature comes in – a way to make stories become more like stories:
Arcs
A storyteller knows where the story is going. They know that we are getting to something and we are on the way to it. They base what they say in the beginning, middle, near-end on what that end is. The storyteller must know the whole story before they start telling it (well, they usually do!). And so we needed the very same thing here.
I began generating Story Arcs. These are a large set of topics, themes, nouns, actions that can be composed dynamically into a “Quest” of sorts. They are given a count of how many Steps the story has, which correlate to how many narrative generations the Narrator will take to finish the Arc. And so the Narrator might be given:
Story Context:
- The chapter of their story now unfolding is {Arc.title}, and they are on leg {Arc.step} of {Arc.totalSteps}
// the story arc and how it's stored
{
"title": "What Waited In The Cellar",
"step": 5,
"totalSteps": 7
}
This is enough that the Narrator has a sense of “oh, we are halfway through the story nearing the end and so likely something Climactic is coming”. And then later at the end, it knows to portray a kind of resolution. This feels more like progress, destination, and closure. This is getting us closer to satisfying fantasy narrative. But we are still missing pieces, even with this. We need threat and stakes.
Villains
Everything seemed to be going better but even the more cohesive feeling open, journey, end journey loops were hollow-feeling, and that is because the stakes are not high enough to be truly engaging. Epic stories require emotional weight and that comes through deep, impactful challenge and adversity. Moments can be that deep, but often it is recurring themes amongst many moments that play on us, like a low vibrating, menacing chord that is looming over the noise. It is the right time for Villains.
And this got especially fun because we don’t have to make them up – we have real life parallels inside the Github organization. Most every repository is composed of little software packages that other people make, and your code depends on. Those packages often are compromised with malware and malicious exploitative attackers trying to hijack your code, steal your data, and any number of bad things. Monitoring, identifying, and remediating those security vulnerabilities is an ongoing, continual maintenance practice for any responsible software organization. You’re never going to stop them from coming. You are only going to manage finding and remediating them as quickly as possible. Remediation looks like “updating” the package to a newer version that no longer has the exploit.
So since I had access to our Github information, it also contains security advisory information for packages in every repo. Those are our villains. It’s perfect. The Heroes chase the Villains in the fantasy while the real developer chase the vulnerabilities in real life.
Vulnerabilities come in severity – Critical, High, Medium, Low. They also have metadata around global impact… how infectious and widespread the usage of that vulnerable package is. Those two metrics let us represent villains with character:
- their severity will be how large they are visually.
- their infectiousness will be how wide their grip on Cities is.
So Villains can be both… they can have an “influence” over however many repositories across however many Realms, and they can also be a particular location on the World Map. In this way the Hero can encounter the villain in its City that it has infected (as they work in a repository in real life and remediate the vulnerable package there) and in the fantasy world their Hero might literally cross paths with the one singular location marker and have a special confrontation narrated.
This visualization was especially fun (and is concretely valuable to the company). I drew influence connected areas between the Cities that are under the Villains’ control, but these are also a literal visualization of our repo ecosystem’s security maintenance position, able to be sliced up by particular package or classes of packages or associated by particular repository.

Additionally, another fun opportunity here was to treat the Villain as its own kind of anti-hero that has its own story the Narrator tells. And each Villain has a page that shows its spread and identification over time, its challenges by Heroes, its eventual defeat.
And because it’s specific package versions that are infected, a defeated Villain can always return in future when a new vulnerability comes in a future version. This makes for fantastic story! The Villain was defeated but it has come back, and it remembers that Hero Henry defeated it X times, and now it is Henry’s Nemesis.

Future
This has come along great and I’m eager to take it further, as time allows. I have in mind doing:
- Dream sequences where Heroes nightly dream about events that happened to them, and the result of the dreams shapes their identity further… and also allows Villains or other Heroes to have cross-over encounters and interact via the Dreamscape.
- World Arcs that gather multiple Heroes story arcs in a way that tell a meta narrative, for something even more epic-feeling.
- User-selectable “classes” for their Hero. Fantasy tropes like a strong sense of a role in a Heroic journey, and that often takes the form of what a Hero is uniquely oriented towards. We might do the D&D system of “magic user”, “fighter”, “thief”, “cleric”, “ranger”. It could further enrich the narrative nuance that the Narrator generates for that specific Hero and be a better story.
I have lots of ideas, and it grows whenever I spend any time here – which is what I love about it. It’s been a lot of fun to work on, and it has some concrete overlap with something useful at work while I do it.
That’s all, folks, thanks for reading!
