20 People = 20 Permanent Agents
Each person gets a continuously running agent process. Memory, CPU, and inference costs scale linearly with population.
Persistent people. Evolving relationships. Social circles. One bounded inference per scene. Population scales to thousands at near-zero cost.
Five concepts, deliberately separate. Understanding this changes how you think about AI societies.
Durable canonical identity — biography, personality, interests, social graph
Reusable behavior/capability set — assigned at zero cost
Ephemeral per-scene persona — materialized from Person + Roles + Relationships + Memory
Capability class — social.mock → nano → standard → deep
Optional premium inference route — a route, not an actor
Durable state builds up. Intelligence only activates when interaction requires it.
kernel.createPerson({
name: "Emma Reyes",
biography: "Product designer...",
identity: { core: "warm, honest..." },
interests: ["photography", "hiking"],
currentState: { mood: 0.4 }
})The industry builds agent swarms. We built a society kernel. The difference is everything.
Each person gets a continuously running agent process. Memory, CPU, and inference costs scale linearly with population.
Agents 'think' even when nobody's talking. Idle time burns inference budget on manufactured activity.
Every agent remembers everything forever. Context windows explode, token costs compound exponentially.
A 3-person group chat = 3 separate model calls. No shared context, no efficiency, 3x the latency and cost.
Failed calls retry automatically. A flaky provider silently multiplies your bill while you sleep.
Identity tied to model. Switching from GPT-4 to Claude means rebuilding every persona from scratch.
Naive agent swarms. Every person, every relationship, every second costs money.
GrokBot Society. Only selected speakers in a scene cost inference. Dormant = free.
This is not optimization — it is a different architecture. The runtime enforces it at every layer.
29 reusable behavior definitions. Assign at zero cost. One person, many facets — never multiple agents.
Tendencies
Capabilities
Tendencies
Capabilities
Tendencies
Capabilities
✓ Active in Emma's Actor when context-relevant
Tendencies
Capabilities
Tendencies
Capabilities
Tendencies
Capabilities
Tendencies
Capabilities
Tendencies
Capabilities
Tendencies
Capabilities
Tendencies
Capabilities
Tendencies
Capabilities
Product designer with a soft spot for analog cameras.
5 roles assigned. Zero inference cost. At scene time, only context-relevant roles compose into the Actor.
A group chat is ONE structured generation returning multiple messages — never one call per character.
Every event flows through this deterministic pipeline. Intelligence is the last rung — most scenes stop before it.
USER_MESSAGE, GROUP_INTERACTION, PROACTIVE_REACH, SESSION_MESSAGE
Every interaction enters as a typed event with actor, participants, circle, payload
Content-hash keyed on shape + modelClass + context sections
Hit → return $0 instantly. Miss → proceed. Dependencies = selected person IDs for invalidation
Person, roles, relationships, circles, timeline, memory — all zero-inference
Only selected participants' relevant data retrieved. Privacy scopes enforced at read time
Deterministic scoring: intensity + circle + relationship + recency + topic
Hard cap: ≤3 speakers. Silent is valid (no eligible → no inference, no fake chatter)
Top-K scoring with recency penalty and intensity boost
Only selected speakers ever enter model context. Others remain completely dormant
BASELINE + ROLE + RELATIONSHIP + MEMORY sections, bounded by token budget
≤12k tokens, ≤30 sections. circuit_explosion breaker hard-trips on overflow
Kill switch → per-event cap → background cap → token ceilings → tier gate → hourly/daily spend
Fail closed: any refusal → deterministic fallback. blockedCalls incremented for audit
IntelligenceGateway → ModelRouter → Provider → structured SceneOutput
MockProvider default ($0). Real providers only when budget allows. Validation before persistence
Timeline, memory, relationships, followups, serendipity, telemetry — all applied
Event status: persisted | deterministic_fallback | silent | no_action. Full audit trail
maxCallsPerEvent = 1 enforced by BudgetGovernor. A 3-person scene = 1 call.
Kill switch, budget ceilings, provider unavailability all block before inference → deterministic fallback
backgroundModelCalls = 0 by default. Scheduled followups resolve deterministically at $0
Switch models without touching a Person. Identity survives provider changes — routes are pure configuration.
Deterministic structured output. Exercises full protocol. Default for all development.
Pure template/code cognition. No model at all. Still routed through gateway, subject to budget.
Fast, inexpensive real model. RemoteStubProvider becomes available when SOCIETY_NANO_API_KEY is set.
Requires environment variable:
SOCIETY_NANO_API_KEYBalanced capability. RemoteStubProvider available when OPENAI_API_KEY is set.
Requires environment variable:
OPENAI_API_KEYHighest capability. RemoteStubProvider available when GROK_API_KEY is set.
Requires environment variable:
GROK_API_KEYAll routes point to MockProvider by default ($0). Change routes in config to enable paid tiers.
| Capability Class | Provider | Model | Est. Cost |
|---|---|---|---|
| social.deterministic | deterministic | template-v1 | $0 |
| social.mock | mock | mock-v1 | $0 |
| social.nano | mock | mock-v1 | $0 (stub) |
| social.standard | mock | mock-v1 | $0 (stub) |
| social.deep | mock | mock-v1 | $0 (stub) |
Emma exists in SQLite with biography, roles, relationships, memories
social.mock → social.standard via config. Zero Person migration.
Same UUID, same relationships, same memories. Only inference backend changed.
Population scales. Inference does not. Only active interactions cost money.
$0.0275 per scene
3,500 input + 500 output tokens at $0.005/$0.020 per 1k. This is the core simulator profile, not a live provider quote.
Only active people (those in circles, with relationships, recently interacted) ever enter model context. 485 dormant people = $0 inference.
20 messages
16 calls @ $0.0275/scene
15 messages
12 calls @ $0.0275/scene
30 messages
24 calls @ $0.0275/scene
40 messages
32 calls @ $0.0275/scene
0 messages
No inference needed
Core simulator profile — 500 people (15 active, 485 dormant)
Scenario totals use the core-verified token and rate profile. Population changes the dormant count, not these example workloads.
Identical scene context → instant return, $0
Person, roles, relationships, circles — all zero inference
Relevance, selection, compilation, validation — pure code
Remote provider classes are scaffolds. No real inference is connected.
Disabled bridge contract. No ChatGPT inference or credits are connected.
Disabled optional route contract. No GrokBot inference or credits are connected.
ParticipantSelector picks ≤3 relevant speakers from circles. At population 5,000, 4,985 people never enter context.
≤12k tokens, ≤30 sections per scene. Circuit breaker hard-trips on overflow. Context = constant.
maxCallsPerEvent = 1. A 3-person group chat = 1 structured generation, not 3 separate calls.
Every claim backed by code. Every guarantee tested. Run it yourself at $0.
Install dependencies
pnpm installStrict TypeScript, zero errors
pnpm typecheck42/42 tests passing
pnpm testLive society on MockProvider ($0)
pnpm devStart, chat, proactive, sessions, usage
pnpm society start && pnpm society chat ...assertGatewalledCall() in every provider
maxCallsPerEvent = 1 enforced by BudgetGovernor
backgroundModelCalls = 0 by default
≤3 speakers, ≤12k tokens, circuit breaker
Kill switch, budget, availability all block
Switch routes without touching Person
git clone https://github.com/M4G3LL4N0/grokbot-society.gitcd grokbot-society && pnpm installpnpm typecheck && pnpm testpnpm devpnpm society start --db ./society.dbLicensed under Apache 2.0 — permissive, business-friendly, patent grant included.
Independent project. GrokBot Society is not affiliated with, endorsed by, or connected to xAI, Grok, Cursor, or any other company.
"GrokBot" in this project refers to an optional internal inference route name only.
Working code in 60 seconds. MockProvider by default. Zero paid inference required.
Get the code and install dependencies
git clone https://github.com/M4G3LL4N0/grokbot-society.gitcd grokbot-societypnpm installVerification:
pnpm typecheck && pnpm testBoot a live society on MockProvider ($0)
pnpm devVerification:
Output shows 5 persons, 29 roles, 11 circles, 1 model call, $0 costPersistent database with full operator control
pnpm society start --db ./my-society.dbpnpm society status --db ./my-society.dbpnpm society people --db ./my-society.dbpnpm society chat "Hello everyone!" --circle <INNER_CIRCLE_ID> --db ./my-society.dbVerification:
Interactive REPL with 3-speaker bounded scenes at $0Inspect persons, relationships, circles, landmarks, sessions
pnpm society person Emma --db ./my-society.dbpnpm society circles --db ./my-society.dbpnpm society landmarks Emma --db ./my-society.dbpnpm society session start evening_social --db ./my-society.dbpnpm society simulate --db ./my-society.dbVerification:
Identity cards, relationship dims, landmarks, cost simulator outputZero inference — pure structured state
const kernel = createKernel(
{ maxPersons: 1000 },
{ clock: new SimulatedClock() }
);
const person = kernel.createPerson({
name: "Aria Chen",
biography: "Urban explorer and street photographer.",
identity: { core: "curious, observant, quietly intense" },
personality: { summary: "notices what others miss" },
interests: ["photography", "urban exploration", "coffee"],
currentState: { mood: 0.3, availability: "available", socialIntensity: "normal" },
});
console.log(person.id); // p_...Composable behavior at zero cost
kernel.assignRole(person.id, "friend");
kernel.assignRole(person.id, "creative_friend");
kernel.assignRole(person.id, "storyteller");
kernel.assignRole(person.id, "listener");
// At scene time, only context-relevant roles compose into the Actor
const actor = kernel.composeActorContext(
person.id,
"event:123",
[person.id, otherPerson.id],
circleId
);
// actor.roles = [friend, creative_friend, storyteller, listener]One inference, multiple speakers, bounded context
const innerCircle = kernel.circles.list()
.find(c => c.name === "Inner Circle");
const event = await kernel.tell(
"Anyone up for a spontaneous road trip this weekend?",
{ circleId: innerCircle.id }
);
// event.payload.sceneResult.output.messages = [
// { personId: "p_...", text: "Sounds good — tell me more...", person: "Sam" },
// { personId: "p_...", text: "Ha, that's a vibe. I'm in.", person: "Emma" },
// { personId: "p_...", text: "I've been thinking about exactly that...", person: "Leo" }
// ];
console.log(`Model calls: ${kernel.stats().modelCalls}`); // 1
console.log(`Est. spend: $${kernel.stats().totalCost.toFixed(6)}`); // 0.000000Full introspection at zero cost
// Person identity card
const card = kernel.identityCard(person.id);
console.log(card.name, card.archetype, card.essence, card.voice);
// Relationships
const rels = kernel.relationship.relationshipsFor(person.id);
rels.forEach(r => console.log(
r.otherName, r.status, r.interactions,
`${r.dims.familiarity.toFixed(2)}/${r.dims.trust.toFixed(2)}/${r.dims.affection.toFixed(2)}`
));
// Landmarks (shared history)
const landmarks = kernel.memory.landmarksFor(person.id);
landmarks.forEach(l => console.log("•", l.content));
// Cost simulator
const sim = kernel.cost.simulate({ population: 5000 });
sim.forEach(s => console.log(
s.name, s.modelCalls, `$${s.estimatedSpend.toFixed(4)}`, s.note
));The entire system boots and runs through MockProvider with no paid model configured. It is a deterministic, zero-cost stand-in that still exercises the full structured single-inference scene protocol: one call in, one SceneOutput (multi-message) out.
Main interaction entry. Returns SocietyEvent with sceneResult.
Idempotent seed (5 persons, 29 roles, 11 circles, 10 rels, 5 landmarks).
Explicit proactive pass. Default off → 0 calls. Configurable relevance/cooldown/budget.
Compact frozen Person snapshot (name, archetype, essence, voice, interests, goals).
Bounded Actor for one scene. Person + Roles + Relationships + Memory + SceneRef.
Telemetry: calls, tokens, spend per person/circle/provider, blockedCalls, escalations, trips.
Fail-closed kill switch. All inference blocked → deterministic fallback. blockedCalls increments.
Long-session abstraction. Rolling window (6 msgs) + participant cards (4).