Anukara · a simulation engine
A simulation engine for worlds of people who decide.
We built a grammar, a runtime and an SDK for worlds of people. A cartridge declares the people, the rules and the actions they may take. The engine gives each person turns on their own routine, records every decision as an event with an id and a cause, and branches from any checkpoint.
Recording · world 01a164f5 · arm A · 22 October 2026
- turns
- actions
- social
- host events
- other engine events
- crowd step
day 30 · 6,251 events · 364 host · 5,350 state changes · crowd step 10
- events
- 68,886
- in the event store
- turns
- 5,185
- 200 people, each on a routine
- prompt tokens
- 106.1M
- 29.5M cached · 27.8%
- model spend
- $11.74
- claude-haiku-5-5
- [1a3adc6c] [day 26] Sarah Ashford: inactive
- [84003907] [day 26] Derek Calloway: inactive
- [e7b81805] [day 30] the crowd: 200 replies, 40 likes on 5 posts
- [7132057d] [day 30] the host: cycle_closed → Matthew Stroud, downgrade
- [ebdff931] [day 30] the host: cycle_closed → Marcus Hale, downgrade
- [9a1d9848] [day 30] the host: cycle_closed → Donna Pickering, downgrade
- [a1f26ce6] [day 30] the host: cycle_closed → Chloe Bowman, full_renewal
One real run · 22 October 2026 · a breach-disclosure study run by a host application
- 200 + 300cast people and crowd accounts, per arm
- 30 daysof simulated time, each person on a routine
- 46 minof wall clock, both arms in parallel
- $11.70model spend per arm on Haiku 5.5, capped at $24
- 14,864model calls, each logged with its prompt
- 2 of 2arms reached the horizon, no fatal errors
Cartridges
A cartridge declares the world.
A cartridge is JSON checked against our grammar: 17 JSON schemas that also generate the engine's Go types. The gateway refuses anything it cannot run, and cartridges.dryRun says so before admission does.
- people
entities - Named people with state, persona traits, goals and the scopes they belong to.
- rules
rules - CEL conditions that fire on an event, on an interval, after a person acts or after someone acts against them.
- actions
action_registry - What a person may do, with typed parameters, a visibility and the effects it applies.
- scopes
scopes - Rooms with members or public visibility, named or anonymous attribution, and the visible_state a member can read.
- pacing
pacing - Routines at declared instants, profiles that bundle them, tempo shifts and pokes.
- termination
termination - The horizon in logical time, event and token ceilings, and exit conditions in CEL.
- effects
19 primitives - set
- add
- subtract
- transfer
- block
- unblock
- create_entity
- destroy_entity
- set_archetype
- set_profile
- emit
- corpus_write
- knowledge.attest
- attach_hook
- detach_hook
- create_scope
- destroy_scope
- add_to_scope
- remove_from_scope
{
"entities": [{
"id": "p_0017", "name": "Priya Natarajan",
"scope_ids": ["rowing_club"],
"state": { "member": true, "times_told": 0 },
"persona_traits": { "ad_trust": 0.29 },
"rules": [{
"id": "advocate", "name": "Told three friends",
"trigger": "after_i_act", "action_type": "tell_friend",
"condition": "agent.state.times_told >= 3",
"effects": [{ "primitive": "emit", "event_type": "study.advocate" }]
}]
}],
"action_registry": [{
"id": "tell_friend", "target": "entity", "visibility": "public",
"parameters": { "message": { "type": "string", "required": true } },
"effects": [{ "primitive": "add", "apply_to": "invoker", "path": "state.times_told", "value": 1 }]
}],
"scopes": [{ "id": "rowing_club", "visibility": "members", "visible_state": ["member"] }],
"pacing": { "routines": [{ "id": "evening", "every": 1440, "anchor": 1200, "turns": 1, "actions": ["tell_friend", "wait"] }] },
"termination": { "max_logical_time": 43200 }
}Grammar in, English out.
The host writes JSON the grammar validates. Nothing in a person's turn is JSON: state, events, the room and the crowd's reaction arrive as sentences, each event with an id the person can cite.
what the host sends
{
"id": "p_0017", "name": "Priya Natarajan",
"scope_ids": ["rowing_club"],
"state": { "member": true, "times_told": 3, "decision": "wait" },
"persona_traits": { "ad_trust": 0.29, "organiser": 0.71 }
}what the person reads
Your current state: - member: true - times_told: 3 - decision: wait - profile.ad_trust: 0.29 - profile.organiser: 0.71 Where you are: Rowing club, a Tuesday and Thursday crew that has rowed together for six years.
what the host sends
{ "event_type": "action.completed", "logical_time": 20760,
"source_entity_id": "p_0004", "target_entity_id": "p_0017",
"payload": { "action": "tell_friend", "message": "the rota thing works, try it" } }
{ "event_type": "period.advanced", "logical_time": 21600 }what the person reads
Since your last turn (Week 3, Monday, day 15): - [id: 6d2627f2] [Week 3, Monday, day 15] Hal Brennan: tell_friend → you, “the rota thing works, try it” - [id: 74146a32] [Week 3, Tuesday, day 16] the world: period.advanced What you did earlier, oldest first: - [id: a578f95a] [Week 1, Monday, day 1] You: wait - [id: 12a390b2] [Week 2, Wednesday, day 10] You: tell_friend → Oskar Lindqvist
what the host sends
{ "event_type": "chorus.crowd_advanced",
"payload": { "accounts": 300, "posts_total": 41,
"reply_groups": [{ "stance": "wary", "count": 17 }, { "stance": "keen", "count": 9 }],
"decisions": 184, "model_calls": 0 } }what the person reads
On the town board: - [post 2f81] Mireille Okafor in neighbours: “They want contacts access to send the invite. That is a no from me.” · the crowd: 26 replies, 17 wary and 9 keen, 118 likes - [post 9c0a] the service: “Gather is live in Columbus. Invite a friend, both get $5.” · the crowd: 41 read it, 4 replied, 2 of them hostile
Turns
What happens on a turn.
When a routine falls due, the engine builds the person's prompt from their context, their state, the events since their last turn and the memories recalled for this moment. The model answers with one tool call. Effects apply, the scribe rewrites the memory journal, and everything lands in an event-sourced history.
One person's 26 turns · Joshua Bolton · arm A of the Breach run
26 turns on 10 of 30 days. Prompts grew from 10,787 to 19,689 tokens as the month filled his history, 429,124 in all. 12 turns ran memory recall steps. He acted with check_account, review_terms, use_premium once each, chose inactive 15 times, and 8 evening turns recorded no action.
day 21 · 10:01 · chance_step_1 · 18,753 prompt tokens · 1,357 completion · 3 memory recall steps · use_premium
- an action
- chose inactive
- evening turn, no action recorded
- memory recall steps
- bar height: prompt tokens, up to 19,689
His day-16 turn in the event store
- id
- f05883b6
- type
- action.completed
- logical time
- 22,203 · day 16, 10:03
- source
- Joshua Bolton
- action
- inactive
Checkpoints and branches
Fork from any completed step. Keep both arms.
Every completed step is a checkpoint. A child world spawned from one with fork_tick carries the same people with the same memories and reads its parent's history up to that moment, then diverges. In the Breach study the two arms differ only in when and how the company tells its customers.
Day 1. The breach happens. Nobody outside the company knows yet.
Arm A kept 164 of 200: 24 downgrades, 12 switched provider, none cancelled.
Arm B kept 144 of 200: 36 downgrades, 18 switched, 2 cancelled. The tipped-off customers held back until the notice, then moved together.
End-of-run counts are from the run of 22 October 2026. The day-by-day shape between them is illustrative.
Metering, allowances and cost per decision
A ceiling before the first call. Cents after.
Casting admits the cartridge, pins a price table and plans every routine, so the gateway quotes a dollar ceiling before any model call. Nothing is spent until a job starts with an allowance. The wallet holds it, each batch reserves against it, every call settles to the cent, and the world stops at the cap.
- 01 · quote
A dollar ceiling for the cartridge, broken down by profile, before any call.
- 02 · allowance
The job starts with dollars in a wallet; top up mid-run if you want more.
- 03 · reserve
Each batch of turns reserves against the allowance before it runs.
- 04 · settle
Every model call settles to the cent and emits a usage event; the cap is a hard stop.
authorization gate Optional: the host's endpoint answers before each run, turn or model call, with an HMAC signature on every ask.
calibration point · the Breach run
estimated model spend, per arm
$11.70
two-arm fork $23.40 · same wall clock, arms run in parallel
- wall clock
- ≈ 46 min
- model calls
- ≈ 7,430 per arm
- agent-days
- 6,000
- per agent-day
- $0.00195
cast $10.80crowd $0.901.0× the Breach run
Measured basis: the Breach run averaged about $0.0016 per model call and $0.002 per agent-day, crowd and memory included.
Calibration · measured
The breach-disclosure run of 22 October 2026: 200 cast agents and a 300-account crowd per arm, 30 simulated days, on claude-haiku-5-5. $11.70 per arm, $23.39 for both, 14,864 model calls, 46 minutes wall clock with both arms in parallel. About $0.002 per agent-day, crowd and memory included.
The formula · estimate
spend ≈ agents × days × $0.0018 × model
+ crowd × days × $0.0001
$0.0018 is the Breach run's $11.70, less its crowd at $0.0001 per account-day, over its 6,000 agent-days. Wall clock assumes that run's pace of about 130 agent-days a minute per arm.
Per decision · measured
On 3–5k-token prompts, one decision costs about $0.0006 on Haiku and about $0.0011 on DeepSeek Flash, roughly 1.8× Haiku. The DeepSeek figures here apply that ratio to the Haiku run; no full DeepSeek run backs them.
Models
The model slot, as it stands today.
Every model call goes through one gateway with a price table pinned per job, so a world's cost does not move under it mid-run.
Claude Haiku 5.5
in usedecides and speaks for a person
Runs every person's turns today, including the Breach run.
DeepSeek Flash
pricedpriced, available through the same gateway
About $0.0011 per decision on 3 to 5k-token prompts, 1.8 times Haiku.
OpenAI models
pricedpriced, available through the same gateway
Same route, same metering, same price table pinned per job.
jev
oursour own non-LLM model: crowd reactions and memory ranking
Content-keyed and cached across snapshots and branches, so a replay makes zero LLM calls for them.
next
Our own model, in prototype, is built to decide and speak for a person, and to replace all three: the frontier model, the OpenAI models and jev. The SimBench evaluation is underway. No result exists yet, so we quote no number.
API surface
One gateway, typed end to end.
A host product drives the engine through 153 routes. The SDK is generated from the gateway's OpenAPI, so a misspelled field is a compile error rather than a failed run.
- 153HTTP routes on one gatewayCartridges, jobs, worlds, events, interviews, usage and archives.
- SDKTypeScript, generated from the OpenAPIEvery body, query and response typed. cartridges.dryRun checks a cartridge as admission will.
- WebhookssignedAn HMAC over the timestamp and the body on every delivery.
- MCPthe same routes, for agentsAn agent can admit a cartridge, start a job and read the record.
- A2Aan outside agent plays a personaA person in the world can be driven by an agent you run.
import { LaplaceClient } from "@anukara/engine-sdk"; const client = new LaplaceClient(config); const verdict = await client.cartridges.dryRun({ contract_definition: cartridge }); if (!verdict.ok) throw new Error(verdict.errors.map((e) => e.msg).join("\n")); const { cartridge_id } = await client.cartridges.admit({ contract_definition: cartridge }); const quote = await client.jobs.quote({ cartridge_id }); console.log(quote.total_usd, quote.pacing_preview.ceilings.decisions);
const job = await client.jobs.create({ cartridge_id }); const started = await client.jobs.start(job.id, { allowance_usd: 24 }); for await (const item of client.jobs.subscribe(job.id)) { if (item.kind === "event") console.log(item.event.logical_time, item.event.event_type); }
// same people, same memories, a different day-16 announcement const child = await client.jobs.spawn(job.id, { fork_tick: 21600, allowance_usd: 12 }, { idempotencyKey: "arm-b" }); await client.jobs.poke(child.id, { event_type: "notice.published", payload: { tone: "plain" } }, { idempotencyKey: "notice-b" }); await client.jobs.start(child.id, { allowance_usd: 12 });
const interview = await client.interviews.open({ world_id: started.world_id, entity_id: "p_0017", interview_grammar_version: 1, questions: ["You downgraded on day 19. What tipped it?"], }); const usage = await client.jobs.usage(job.id); const archive = await client.jobs.archive(job.id); // manifest.json first, sha256 checked
await client.jobs.start(job.id, { alowance_usd: 24, }); // error: 'alowance_usd' does not exist in the start body. // Did you mean 'allowance_usd'?
Scale · on the development gateway, 2 November 2026
What exists, counted.
One founder built all of it, starting in February 2026. The counts are read from the repository and the development gateway; the model calls and spend include smoke runs.
lines of code, thousands, rounded
- Go tests349k
- TypeScript221k
- Go source211k
- Python91k
- 7,594Go test functions
- 5,099TypeScript test cases
- 32wet-test scenarios against the full stack
- 17grammar schemas
- 34finished worlds
- 167,387model calls
- $105total model spend
the Breach run Two arms, each 200 people and a 300-account crowd, 30 simulated days in 46 minutes, $11.74 and $11.65 of model spend, 14,864 model calls.
Principles
Four rules the engine keeps.
When a shortcut breaks one of them, the shortcut goes.
- 01
People read English.
Nothing in a turn prompt is JSON: state, events, the room and the crowd's reaction all arrive as sentences, each with an id to cite.
- 02
The crowd is counted.
The crowd appears as counts and grouped replies, never quoted; the cast are the named people who matter, and only they speak in the record.
- 03
Correctness over model spend.
The host pays the bill, so the engine never trades a correct decision for fewer tokens; the allowance is the brake, not the prompt.
- 04
Every line has a receipt.
An id, a time and a cause on every event, and decisions cite the lines that drove them.
Applications
What the engine is for.
- 01
Lokara
Business decisions put to simulated people. The first application built on the engine.
- 02
Company digital twins
A company's people, teams and rules declared as a cartridge, to see how a change moves through them.
- 03
Agent evaluation and training
Worlds as replayable, scoreable environments, with an outside agent playing a persona over A2A.
- 04
RL environments for labs
Declared rules, recorded causes and branchable checkpoints give an environment with a full trace per episode.
- 05
Games
Characters who remember, keep routines and act on what they saw, under rules the designer declares.
The first application
Lokara
For research and product teams with a decision to make. Built on this engine through the same SDK.
Lokara rehearses a decision on simulated people before your customers see it. Build the audience, run each option, and read every decision with its reason.
Lokara, the Universe screen of the First 100Founder
Vighnesh Bheed
Saarbrücken, Germany
Ten years of engineering. Neural networks and digital twins for experiment design, computer-vision models for defect detection on industrial machines, and an agent-verification platform. Building Anukara and Lokara since February 2026.
Contact
hello@anukara.dev