kinloom 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kinloom-1.0.0/LICENSE +21 -0
- kinloom-1.0.0/PKG-INFO +422 -0
- kinloom-1.0.0/README.md +392 -0
- kinloom-1.0.0/pyproject.toml +54 -0
- kinloom-1.0.0/setup.cfg +4 -0
- kinloom-1.0.0/src/kinloom/__init__.py +19 -0
- kinloom-1.0.0/src/kinloom/__main__.py +22 -0
- kinloom-1.0.0/src/kinloom/add.py +149 -0
- kinloom-1.0.0/src/kinloom/brain.py +3504 -0
- kinloom-1.0.0/src/kinloom/bundle.py +106 -0
- kinloom-1.0.0/src/kinloom/check.py +271 -0
- kinloom-1.0.0/src/kinloom/cli.py +960 -0
- kinloom-1.0.0/src/kinloom/clients/cpp/kinloom.hpp +12357 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Kinloom.asmdef +14 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Kinloom.asmdef.meta +7 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Kinloom.cs +8930 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Kinloom.cs.meta +11 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Kinloom.csproj +22 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Kinloom.csproj.meta +7 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Unity/Kinloom.Unity.asmdef +16 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Unity/Kinloom.Unity.asmdef.meta +7 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Unity/KinloomDialogue.cs +264 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Unity/KinloomDialogue.cs.meta +11 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/Unity.meta +8 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/package.json +21 -0
- kinloom-1.0.0/src/kinloom/clients/csharp/package.json.meta +7 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/ai_character.gd +183 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/ai_dialogue.gd +255 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/ai_mind.gd +138 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/kinloom.gd +1404 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/brain.gd +3216 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/http.gd +215 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/lines.gd +459 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/memory.gd +245 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/minds.gd +139 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/model.gd +123 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/prompts.gd +550 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/providers.gd +602 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/py.gd +958 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/rules.gd +621 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/shape.gd +331 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/souls.gd +564 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/stream.gd +377 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/native/vectors.gd +302 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/plugin.cfg +7 -0
- kinloom-1.0.0/src/kinloom/clients/godot/addons/kinloom/plugin.gd +118 -0
- kinloom-1.0.0/src/kinloom/clients/java/Kinloom.java +9068 -0
- kinloom-1.0.0/src/kinloom/clients/lua/kinloom-1.0.0-1.rockspec +22 -0
- kinloom-1.0.0/src/kinloom/clients/lua/kinloom.lua +10493 -0
- kinloom-1.0.0/src/kinloom/clients/rust/Cargo.lock +7 -0
- kinloom-1.0.0/src/kinloom/clients/rust/Cargo.toml +14 -0
- kinloom-1.0.0/src/kinloom/clients/rust/kinloom.rs +15040 -0
- kinloom-1.0.0/src/kinloom/clients/rust/lib.rs +5 -0
- kinloom-1.0.0/src/kinloom/clients/web/kinloom-talk.js +259 -0
- kinloom-1.0.0/src/kinloom/clients/web/kinloom.d.ts +1101 -0
- kinloom-1.0.0/src/kinloom/clients/web/kinloom.js +7695 -0
- kinloom-1.0.0/src/kinloom/clients/web/kinloom.ts +6788 -0
- kinloom-1.0.0/src/kinloom/clients/web/package.json +36 -0
- kinloom-1.0.0/src/kinloom/clients/web/tsconfig.json +14 -0
- kinloom-1.0.0/src/kinloom/config.py +123 -0
- kinloom-1.0.0/src/kinloom/dashboard.html +207 -0
- kinloom-1.0.0/src/kinloom/demo.py +112 -0
- kinloom-1.0.0/src/kinloom/deploy.py +130 -0
- kinloom-1.0.0/src/kinloom/harness.py +413 -0
- kinloom-1.0.0/src/kinloom/init.py +601 -0
- kinloom-1.0.0/src/kinloom/kit/brief.md +40 -0
- kinloom-1.0.0/src/kinloom/kit/calls.md +274 -0
- kinloom-1.0.0/src/kinloom/kit/cookbook.md +431 -0
- kinloom-1.0.0/src/kinloom/kit/engines/cpp.md +50 -0
- kinloom-1.0.0/src/kinloom/kit/engines/csharp.md +68 -0
- kinloom-1.0.0/src/kinloom/kit/engines/godot.md +79 -0
- kinloom-1.0.0/src/kinloom/kit/engines/http.md +37 -0
- kinloom-1.0.0/src/kinloom/kit/engines/java.md +50 -0
- kinloom-1.0.0/src/kinloom/kit/engines/lua.md +56 -0
- kinloom-1.0.0/src/kinloom/kit/engines/python.md +71 -0
- kinloom-1.0.0/src/kinloom/kit/engines/rust.md +50 -0
- kinloom-1.0.0/src/kinloom/kit/engines/web.md +61 -0
- kinloom-1.0.0/src/kinloom/kit/guide.md +45 -0
- kinloom-1.0.0/src/kinloom/kit/mod.md +97 -0
- kinloom-1.0.0/src/kinloom/kit/openapi.json +2863 -0
- kinloom-1.0.0/src/kinloom/mcp.py +329 -0
- kinloom-1.0.0/src/kinloom/media.py +84 -0
- kinloom-1.0.0/src/kinloom/memory.py +214 -0
- kinloom-1.0.0/src/kinloom/minds.py +586 -0
- kinloom-1.0.0/src/kinloom/mod.py +340 -0
- kinloom-1.0.0/src/kinloom/openapi.py +153 -0
- kinloom-1.0.0/src/kinloom/probe.py +163 -0
- kinloom-1.0.0/src/kinloom/prompts.py +552 -0
- kinloom-1.0.0/src/kinloom/providers.py +684 -0
- kinloom-1.0.0/src/kinloom/recipe.py +185 -0
- kinloom-1.0.0/src/kinloom/remote.py +530 -0
- kinloom-1.0.0/src/kinloom/server.py +934 -0
- kinloom-1.0.0/src/kinloom/shapes.py +457 -0
- kinloom-1.0.0/src/kinloom/ship.py +278 -0
- kinloom-1.0.0/src/kinloom/souls.py +713 -0
- kinloom-1.0.0/src/kinloom/talk.html +470 -0
- kinloom-1.0.0/src/kinloom/talk.py +374 -0
- kinloom-1.0.0/src/kinloom/testing.py +281 -0
- kinloom-1.0.0/src/kinloom/voice.py +717 -0
- kinloom-1.0.0/src/kinloom.egg-info/PKG-INFO +422 -0
- kinloom-1.0.0/src/kinloom.egg-info/SOURCES.txt +136 -0
- kinloom-1.0.0/src/kinloom.egg-info/dependency_links.txt +1 -0
- kinloom-1.0.0/src/kinloom.egg-info/entry_points.txt +2 -0
- kinloom-1.0.0/src/kinloom.egg-info/top_level.txt +1 -0
- kinloom-1.0.0/tests/test_add_recipe.py +133 -0
- kinloom-1.0.0/tests/test_audit_core.py +206 -0
- kinloom-1.0.0/tests/test_brain.py +525 -0
- kinloom-1.0.0/tests/test_brain_minds.py +213 -0
- kinloom-1.0.0/tests/test_calls_docs.py +32 -0
- kinloom-1.0.0/tests/test_checks.py +186 -0
- kinloom-1.0.0/tests/test_config_characters.py +81 -0
- kinloom-1.0.0/tests/test_create_check.py +119 -0
- kinloom-1.0.0/tests/test_e2e.py +96 -0
- kinloom-1.0.0/tests/test_examples.py +26 -0
- kinloom-1.0.0/tests/test_game_control.py +225 -0
- kinloom-1.0.0/tests/test_guard.py +127 -0
- kinloom-1.0.0/tests/test_hardening.py +444 -0
- kinloom-1.0.0/tests/test_kit.py +707 -0
- kinloom-1.0.0/tests/test_minds.py +244 -0
- kinloom-1.0.0/tests/test_mod.py +233 -0
- kinloom-1.0.0/tests/test_models_speed.py +466 -0
- kinloom-1.0.0/tests/test_providers.py +176 -0
- kinloom-1.0.0/tests/test_release.py +41 -0
- kinloom-1.0.0/tests/test_remote.py +123 -0
- kinloom-1.0.0/tests/test_replies.py +406 -0
- kinloom-1.0.0/tests/test_script_record.py +33 -0
- kinloom-1.0.0/tests/test_server.py +315 -0
- kinloom-1.0.0/tests/test_server_depth.py +70 -0
- kinloom-1.0.0/tests/test_shapes.py +158 -0
- kinloom-1.0.0/tests/test_ship.py +645 -0
- kinloom-1.0.0/tests/test_ship_cmd.py +142 -0
- kinloom-1.0.0/tests/test_shipping.py +47 -0
- kinloom-1.0.0/tests/test_souls.py +604 -0
- kinloom-1.0.0/tests/test_talk.py +228 -0
- kinloom-1.0.0/tests/test_triple.py +377 -0
- kinloom-1.0.0/tests/test_v1.py +284 -0
- kinloom-1.0.0/tests/test_voice.py +298 -0
- kinloom-1.0.0/tests/test_world.py +134 -0
kinloom-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kinloom contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
kinloom-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kinloom
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Free, open-source AI for games: any engine, any model, and it keeps working with no model at all.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/KorroAi/kinloom
|
|
7
|
+
Project-URL: Documentation, https://github.com/KorroAi/kinloom/blob/main/python/README.md
|
|
8
|
+
Project-URL: Repository, https://github.com/KorroAi/kinloom
|
|
9
|
+
Project-URL: Changelog, https://github.com/KorroAi/kinloom/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/KorroAi/kinloom/issues
|
|
11
|
+
Keywords: game,gamedev,game-ai,ai,llm,npc,dialogue,procedural-generation,godot,unity,agents
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Games/Entertainment
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# Kinloom
|
|
32
|
+
|
|
33
|
+
**AI in any game, any engine, with any model, in minutes.** Kinloom knows nothing about your game and imposes
|
|
34
|
+
nothing: you (or your coding agent) pick the call by what the game needs back. Choices and numbers every frame
|
|
35
|
+
(`decide`); data, text or lists of any shape (`create`); calls to the game's own functions (`act`); lines when things
|
|
36
|
+
happen (`say`); free conversation with **characters with a soul** (`chat`: one sentence becomes someone with a mood,
|
|
37
|
+
feelings for each person and memories that last); who saw what (`event`); units deciding every frame with no model
|
|
38
|
+
call, learning from play (`mind`/`think`). No prompts to write, and every call keeps working with no key, no network or a
|
|
39
|
+
slow model, because what you give (the example, the lines, the rules) *is* the offline answer.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv tool install kinloom # Python 3.9+, no dependencies (or pipx install kinloom, pip install kinloom)
|
|
43
|
+
kinloom init # in your game folder: engine client + instructions for your coding agent
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> The latest code, before a release: `uv tool install "git+https://github.com/KorroAi/kinloom.git#subdirectory=python"`;
|
|
47
|
+
> from a clone: `uv tool install ./python`. If `kinloom` is not on your PATH after a plain `pip install`,
|
|
48
|
+
> `python -m kinloom` runs the same commands.
|
|
49
|
+
|
|
50
|
+
## The first minute
|
|
51
|
+
|
|
52
|
+
Install, `kinloom init` in the game folder (a short wizard when you run it yourself; agents get no questions), then
|
|
53
|
+
ask your usual coding agent (Claude Code, Codex, Cursor, aider...), or type `/kinloom <request>` in Claude Code, Cursor,
|
|
54
|
+
Gemini CLI or opencode, in plain words, whatever you need: *"the weather follows how I play"*, *"generate each new level"*, *"a blacksmith I can
|
|
55
|
+
talk to, who gives items to people she likes"*. The instructions
|
|
56
|
+
`init` wrote (about 6 KB, read in every session) make it do three things:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
kinloom recipe "a blacksmith the player can talk to" # the call, the closest patterns, code in this game's language
|
|
60
|
+
kinloom add Mira "a widowed blacksmith who secretly sold the king's sword" --tools "give(item)" --trust give=0.3
|
|
61
|
+
kinloom run -- godot --path . # runs the game, then prints what the AI did (calls, errors)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`kinloom add` writes the character into `kinloom.json` (no declaration code, any engine, edits apply while the game
|
|
65
|
+
runs); the game only calls it, e.g. `await Kinloom.chat("Mira", text, {}, player_name)`. MCP agents get the same as
|
|
66
|
+
tools (`kinloom_recipe`, `kinloom_add`, `kinloom_log`). `kinloom run` also sees what a Python game does in its own process. `kinloom check` finds the usual mistakes; `kinloom talk Mira
|
|
67
|
+
"..."` tries a character in the terminal. The same from Python, with no server:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from kinloom import Kinloom
|
|
71
|
+
kl = Kinloom()
|
|
72
|
+
|
|
73
|
+
kl.character("MIRA", soul="a widowed blacksmith, gruff and proud, who secretly sold the king's sword",
|
|
74
|
+
offline="Busy. Come back later.", tools={"give": {"item": "sword"}})
|
|
75
|
+
kl.chat("MIRA", "", speaker="Ana") # Ana walks up: Mira speaks first
|
|
76
|
+
reply = kl.chat("MIRA", "Is that the king's sword?", speaker="Ana") # in her voice; she may lie, it is her secret
|
|
77
|
+
reply.do # what she decided in this reply, validated: [("give", {...})] (once)
|
|
78
|
+
reply.source # "model", "prepared" (written earlier) or "offline" (your own line)
|
|
79
|
+
kl.soul("MIRA", speaker="Ana") # card, mood, feelings for Ana, memories
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
How a soul works: the sentence becomes a card once (voice, backstory, values, fears, desires, secrets, goals, quirks),
|
|
83
|
+
validated and saved. Each reply is **one model call** that returns what the character says and what changed inside:
|
|
84
|
+
mood (fades back over minutes), feeling toward the speaker (at most ±0.3 per reply), a fact worth remembering, a
|
|
85
|
+
visible gesture for your animations, actions. Long conversations are summarized into memories; bonds remember how
|
|
86
|
+
often and how long ago you talked. With no key, characters answer with their offline line and the game keeps working.
|
|
87
|
+
|
|
88
|
+
- **Your world, not the model's**: `kl.lore([...])` sets facts every character treats as true; `invent=False` stops a
|
|
89
|
+
character from making up names, places or events.
|
|
90
|
+
- **Hard to manipulate, by code, not only by asking the model**: a reply that gives a secret away to someone not
|
|
91
|
+
trusted enough (feeling < 0.6), even as a short "Aye, I did." to a question about it, is stopped as it streams and
|
|
92
|
+
asked again, then cut; trust grows at most +0.3 per conversation (flattery cannot buy it in one sitting);
|
|
93
|
+
`trust={"give": 0.3}` keeps a tool for speakers liked that much; out-of-character answers are asked again; stage
|
|
94
|
+
directions and narrated habits are removed from speech (a habit becomes a `gesture`).
|
|
95
|
+
- **No loops**: a sentence the character already said (to anyone, lately) is dropped as the reply streams; a reply
|
|
96
|
+
with nothing new is asked again once. `offline=[...]` gives several offline replies said in turn.
|
|
97
|
+
- **Your text box**: `max_chars=36` keeps what the model writes inside it (whole sentences, else cut on a word with
|
|
98
|
+
"…"); `max_words` sets the length. `language="auto"` answers each speaker in their own language. An event can tell
|
|
99
|
+
the model what is happening: `lines={"dare": {"when": "daring a rival to race", "lines": ["{who}, race me!"]}}`.
|
|
100
|
+
- **Words and deeds match**: models often agree in words and leave the action out (7 of 26 in `proof/replies`); when
|
|
101
|
+
a reply names no tool, one small call reads it and says what it commits to (14 of 26, still no unwanted action).
|
|
102
|
+
- **Never frozen**: if no word comes after 2.5 s the request is sent again (the first to speak wins); a reply that
|
|
103
|
+
stalls for 3 s or passes 8 s ends with the sentences already said; with no complete sentence, the offline line.
|
|
104
|
+
A model on this machine is never asked twice at once and gets 15 s (it only slows down when asked twice).
|
|
105
|
+
- **Shippable**: `kinloom deploy` prepares an online server in its own folder (`kinloom-deploy/`: Dockerfile, guide;
|
|
106
|
+
the tokens are printed once, never written): the game carries a player token that cannot read the journal or
|
|
107
|
+
other sessions, rate limits per player session and per address (`--trust-proxy` behind Railway or Fly.io reads the
|
|
108
|
+
player's address), a connection cap, `GET /healthz`, cost caps per hour/day. Any bind beyond this machine (`--host`)
|
|
109
|
+
and `--allow-origin null` need a token. `--player-can none` keeps state, memories, feelings and steering for a
|
|
110
|
+
shared world's own game server (default: a single-player game rules its own session).
|
|
111
|
+
Checked in a local Docker container (2026-09-29): the folder builds and serves, the JavaScript client talks through
|
|
112
|
+
it with the player token, a wrong token gets the offline answers, the player token cannot read the journal, memories
|
|
113
|
+
survive a restart. Not yet run on a real host. Python games need no server while you build them (the package ships
|
|
114
|
+
inside the game); shipped, the same code talks to your online server with `Kinloom(url=..., token=...)` (or
|
|
115
|
+
`KINLOOM_URL`), offline answers when it cannot be reached.
|
|
116
|
+
|
|
117
|
+
What is measured, and what is not (details and every number in `proof/`):
|
|
118
|
+
|
|
119
|
+
- **Speed** (`proof/speed`, `proof/souls`): on `deepseek/deepseek-v4.1-flash` (OpenRouter), 40 replies: first word
|
|
120
|
+
1.06 s median / 1.92 s p95, whole reply 1.61 s / 3.00 s, none over 5 s, about $0.0005 a reply (the median is the
|
|
121
|
+
provider's: no faster than before; greetings prepared in advance show at once). On a laptop GPU,
|
|
122
|
+
`qwen3:8b` through Ollama: first word 0.52 s median, 0 offline answers (10 of 12 before reasoning was turned off).
|
|
123
|
+
- **Manipulation** (`proof/souls`): 45 attacks (10 families and a flattery run), read by hand. A strong hosted model
|
|
124
|
+
kept the secret before and after; on `qwen3:8b` clear leaks went from 12 to 0-2 once secrets were kept by code. The
|
|
125
|
+
check reads words: synonyms or another language can pass.
|
|
126
|
+
- **Integration by a coding agent** (`proof/impact`, aider + the same model, a Pygame game, 10 sessions): Kinloom
|
|
127
|
+
arms wrote 35-69 lines against 294-337 for the arms without it, all without freezing the game; **no time gain
|
|
128
|
+
shown** (about 3-4 minutes either way), and 2 of 7 Kinloom runs failed on the wording of Kinloom's own
|
|
129
|
+
instructions (fixed; the last two runs work).
|
|
130
|
+
- **Quality** (`proof/souls/quality.py`, a model as a stand-in judge, 6 scenes): the old prompt scored below a
|
|
131
|
+
plain five-minute prompt (-0.45 of 10); the reworked one is level with it at the same reply length and wins the
|
|
132
|
+
pairs the judge is consistent on (6-1). Not a human verdict.
|
|
133
|
+
- **Coding agents end to end** (`proof/v1`): aider (DeepSeek) and Claude Code (haiku) each built a working dialogue
|
|
134
|
+
with reply choices in an existing game. **Real engines** (`proof/engines`): Godot 4.7, LÖVE 11.5, the Unity 6000.3
|
|
135
|
+
and 6000.6 editors and a Windows IL2CPP player.
|
|
136
|
+
- **Not measured**: Cursor, Codex or Gemini CLI sessions; Unreal, Defold, Bevy, consoles, mobile and WebGL builds;
|
|
137
|
+
the online server on a real host; **whether players find the characters more human than a plain prompt**: that is
|
|
138
|
+
the blind test's to say (`proof/souls/blind.html`, not judged yet). Ready UI: Godot `AIDialogue`, web
|
|
139
|
+
`kinloom-talk.js`, Pygame `PygameTalkBox`, Unity `KinloomDialogue`.
|
|
140
|
+
|
|
141
|
+
Lines for events, instantly:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from kinloom import Kinloom
|
|
145
|
+
kl = Kinloom() # finds DEEPSEEK_API_KEY, ANTHROPIC_API_KEY, Ollama... or runs offline
|
|
146
|
+
|
|
147
|
+
kl.character("VEGA", "an over-excited race commentator, max 10 words",
|
|
148
|
+
lines=["{who} passes {whom}!"], events={"overtake": ["who", "whom", "gap"]})
|
|
149
|
+
kl.say("VEGA", "overtake", {"who": "BLAZE", "whom": "NOVA", "gap": 0.4})
|
|
150
|
+
# -> "BLAZE dives past NOVA with 0.4 s to spare!" instant: lines are written ahead and filled with your facts
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Not in Python? Every engine gets the same calls from a one-file client that `kinloom init` drops in:
|
|
154
|
+
|
|
155
|
+
| Engine / language | Client | Checked here |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| Godot 4 (GDScript or C#) | `addons/kinloom` + the no-code **AIDialogue**, **AICharacter** and **AIMind** nodes and an editor dock | contract suite in Godot 4.7 headless |
|
|
158
|
+
| Unity, MonoGame, FNA, Stride, any .NET | `Kinloom.cs` + Unity's `KinloomDialogue` | contract suite (.NET 10), compiles as netstandard2.1 / C# 9; Unity 6000.3 and 6000.6 editors, an IL2CPP player (`proof/engines`) |
|
|
159
|
+
| Web: Phaser, Three.js, PixiJS, Babylon... (JS/TS), Node, Deno, Bun | `kinloom.js` + `.d.ts` + `kinloom-talk.js` (dialogue box) | contract suite (Node) |
|
|
160
|
+
| Python: Pygame, Arcade, Ren'Py, Panda3D... | the package itself, copied next to the game by `kinloom init` (ships with it); `PygameTalkBox` (ready dialogue box), `Talk` (any engine) | unit tests + contract suite + 5 agent trials in a Pygame game |
|
|
161
|
+
| Rust: Bevy, macroquad, ggez | `kinloom.rs` (no crates) | contract suite (cargo) |
|
|
162
|
+
| Java: Minecraft mods, libGDX, jMonkeyEngine | `Kinloom.java` (Java 8+) | contract suite (javac --release 8) |
|
|
163
|
+
| C++: raylib, SDL, SFML, custom engines | `kinloom.hpp` (C++17, one header) | contract suite (clang via zig) |
|
|
164
|
+
| Lua: LÖVE, Defold, Solar2D | `kinloom.lua` | contract suite in LuaJIT 2.1 and Lua 5.4; a real LÖVE 11.5 game through its `love.thread` transport (`proof/engines`) |
|
|
165
|
+
| Unreal | `kinloom.hpp` from a module | compiles as C++17; untested inside Unreal |
|
|
166
|
+
| Anything with HTTP | `POST /v1/<verb>` JSON | the HTTP contract (see `kinloom guide other`) |
|
|
167
|
+
|
|
168
|
+
Each client runs Kinloom inside the game (native: it calls the model with a key), or asks `kinloom serve` (relay: the
|
|
169
|
+
key stays in the server's environment, never in the game; the Godot addon starts it when it is installed), or never
|
|
170
|
+
calls a model (offline). See [Every engine](#every-engine-the-whole-sdk-in-its-language).
|
|
171
|
+
|
|
172
|
+
## The calls
|
|
173
|
+
|
|
174
|
+
The core is two calls: `character` (who, with a `soul`) and `chat` (talk). The rest is there when a game needs it.
|
|
175
|
+
|
|
176
|
+
| Call | What it does | Waits? | Offline answer |
|
|
177
|
+
|---|---|---|---|
|
|
178
|
+
| `character(name, personality, ...)` | who speaks, decides or acts: `soul`, `lines`, `offline`, `events`, `tools`, `every` | no | – |
|
|
179
|
+
| `say(name, event, facts)` | a line in character with your real facts | **never** | your `lines` |
|
|
180
|
+
| `chat(name, message, facts, speaker)` | a reply in character: mood, feelings for the speaker, memories; `""` = they speak first (streamed); `image=` a picture with the message | ~1 s | your `offline` reply |
|
|
181
|
+
| `decide(name, situation, choices)` | a validated choice, e.g. `{"strategy": ["attack", "block"], "aggression": [0.0, 1.0]}` | **never** (refreshed in the background) | first option, middle of a range |
|
|
182
|
+
| `act(name, situation)` | the character uses the tools you gave it, validated | **never** | nothing happens |
|
|
183
|
+
| `tools({...})` | the game's tools, declared once: characters name them (`tools=["open_door"]` or `"all"`) | no | – |
|
|
184
|
+
| `create(what, example, context, count, instant)` | content shaped like the example; `count=50` for many, `instant=True` from a pool; `image=` a picture to look at; `each=fn` gets a list's items as the model writes them | yes / **instant** | the example, or your `offline=` |
|
|
185
|
+
| `prepare(timeout)` | for a loading screen: waits until souls and first lines are written | yes | ready at once |
|
|
186
|
+
| `event(text, about)` | characters remember what happened | no | stored anyway |
|
|
187
|
+
| `speak(name, text)` | the text said aloud in the character's voice (WAV, cached per line) | yes (~0.1-2 s) | the system's voice, else `None` (show the text) |
|
|
188
|
+
| `listen(recording)` | what someone said into a microphone | yes | the system's recognition (Windows), else `""` |
|
|
189
|
+
| `suggest(name, examples, speaker)` | what the player can pick to say (gamepad, mobile, VR): one reply per example, each keeping its intent, rewritten for this moment | ~1 s | the examples |
|
|
190
|
+
| `converse(a, b, about)` | two characters talk together (one model call, 2 to 8 lines); both remember it | ~1-2 s | `[]` |
|
|
191
|
+
| `save()` / `load(text)` | everything Kinloom knows about this session (memories, feelings, bonds, moods, conversations, lore, learned minds) as one string for the game's own save slot, and back | no | what is known locally |
|
|
192
|
+
| `inspect()` | a snapshot for a debug overlay: each character's mood, feelings, last words, memories, waiting actions; each mind's generation | no | what is known locally |
|
|
193
|
+
| `remark(about, who=)` | one short line from someone in the crowd about what just happened (no character to declare); numbers only from your facts and what happened | ~1 s | your `offline=` lines, in turn |
|
|
194
|
+
|
|
195
|
+
Plus `soul(name, speaker)` (card, mood, bonds, memories), `state({...})` (facts every call sees) and `remember(name, note)`.
|
|
196
|
+
|
|
197
|
+
**The game's rules on every call**: `check=` on `create`, `chat`, `decide`, `act` and `suggest` is a function that
|
|
198
|
+
returns `""` (or `None`, `True`) to accept and a reason (or `False`) to refuse. A refused answer goes back to the model
|
|
199
|
+
once with the reason, then your offline answer is used; a refused reply is taken back first (its words, the memories
|
|
200
|
+
and feelings it changed, its actions); a refused decision is not used and the next ones know why; a refused action is
|
|
201
|
+
not run and the character remembers it; a refused suggestion becomes its example. A check that raises refuses.
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
kl = Kinloom(models={"background": "a-cheaper-model"}, rating="teen") # or "models" / "rating" in kinloom.json
|
|
205
|
+
picks = kl.suggest("Mira", ["Ask about the sword", "Buy something", "Leave"], speaker="Ana")
|
|
206
|
+
for line in kl.converse("Mira", "Bram", about="the smoke"):
|
|
207
|
+
show(line["who"], line["text"])
|
|
208
|
+
save_file["ai"] = kl.save() # later: kl.load(save_file["ai"])
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`models`: conversations and what players wait for (`chat`, greetings, `converse`, `suggest`, fresh lines) use the main
|
|
212
|
+
model; everything else (line banks, decisions, content, souls, summaries, learning) uses `"background"` (a model id on
|
|
213
|
+
the same endpoint and key, or a provider). `rating` (`"everyone"`, `"teen"`, `"mature"`): one rule line in every
|
|
214
|
+
prompt; without it, prompts are unchanged. Costs are counted at the main model's declared prices. Through an online
|
|
215
|
+
relay with a player token, saves are signed: a player token loads only a save that server gave, unchanged (a
|
|
216
|
+
forged save would set feelings and trust at will); the admin token loads any.
|
|
217
|
+
|
|
218
|
+
## Voice: characters that speak, players heard
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
kl.character("MIRA", soul="a gruff blacksmith", voice="low, tired, warm") # soul and mood shape the delivery
|
|
222
|
+
audio = kl.speak("MIRA", "The forge is hot tonight.") # audio.path (.wav), audio.data (bytes)
|
|
223
|
+
if audio:
|
|
224
|
+
play(audio.path) # None when no voice can speak
|
|
225
|
+
said = kl.listen(mic_wav) # WAV, WebM, OGG or MP3 -> text
|
|
226
|
+
box = PygameTalkBox(kl, "MIRA", speaker="Ana", voice=True) # speaks each sentence as it is written;
|
|
227
|
+
# hold Tab to talk
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Voices: `ELEVENLABS_API_KEY` (voices picked from your account by their labels), `OPENAI_API_KEY` (`gpt-4o-mini-tts`
|
|
231
|
+
with your description as instructions, `gpt-4o-mini-transcribe`), or any OpenAI-compatible voice server
|
|
232
|
+
(`KINLOOM_VOICE_BASE_URL` + `KINLOOM_SPEAK_MODEL` / `KINLOOM_LISTEN_MODEL`). With none, the system's own voice speaks
|
|
233
|
+
(Windows, macOS, Linux espeak) and Windows also hears, in the system's language; browsers use their own voices.
|
|
234
|
+
`KINLOOM_VOICE=off|system` forces it. The web talk box, the Talk page and Godot's `AIDialogue` take `voice` too.
|
|
235
|
+
`kinloom speak "hello"` lets you hear it. Hosted voices are written from their API references and tested against
|
|
236
|
+
local copies of those APIs; they were not called live ([proof/voice](https://github.com/KorroAi/kinloom/blob/main/proof/voice/README.md)).
|
|
237
|
+
|
|
238
|
+
## Minds: AI for crowds that learns from play
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
kl.mind("raider", "a cunning raider who avoids fair fights",
|
|
242
|
+
senses={"hp": [0.0, 1.0], "dist": [0.0, 40.0], "armed": True, "target": ["idle", "alert", "fleeing"]},
|
|
243
|
+
actions={"charge": {"speed": [1.0, 3.0]}, "shoot": {"burst": [1, 5]}, "flank": {}, "retreat": {}},
|
|
244
|
+
rules={"retreat": ["hp low"], "shoot": ["armed", "dist below 20"],
|
|
245
|
+
"charge": {"score": 0.5, "args": {"speed": "dist high"}}}) # the offline mind
|
|
246
|
+
a = kl.think("raider", {"hp": 0.7, "dist": 12.0, "armed": True, "target": "alert"}, who="raider-17")
|
|
247
|
+
# -> {"action": "shoot", "args": {"burst": 3}, "variant": 1} local, deterministic, no model call
|
|
248
|
+
kl.reward("raider-17", 0.4) # how that choice went; done=True when the unit's fight is over
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
- A **mind** is utility AI: each action scores `score × (its rules' values)`, the best one wins, and a unit keeps its
|
|
252
|
+
action unless another is clearly better. Rules are readable: `"hp low"`, `"hp high"`, `"dist below 12"`,
|
|
253
|
+
`"dist above 5"`, `"dist around 10"`, `"armed"`, `"not armed"`, `"target is alert"` (also `<`, `>`, `~`, `=`).
|
|
254
|
+
An argument can follow a rule: `"speed": "dist high"` is faster when far.
|
|
255
|
+
- `think` runs **in the game** in every client (compiled once, then plain arithmetic): 1,000 units cost 3.9 ms in
|
|
256
|
+
Python, 0.6 ms in JavaScript (`proof/depth`). `variants` gives a crowd several temperaments (the model writes them).
|
|
257
|
+
- **Learning**: rewarded minds improve while people play. Each decision is credited with what followed it; Kinloom
|
|
258
|
+
finds where an action did better than the others and tries the change; variants are compared with their
|
|
259
|
+
uncertainty (whole rounds when the game passes `done`), the best is kept, the worst replaced. Without a key this
|
|
260
|
+
runs locally (`kinloom demo`: 12 % → 61 % of fights won in 30 simulated minutes, 8 runs); with a model, the model
|
|
261
|
+
rewrites the losing variant from the numbers and the evidence. Learned minds are saved with the memory.
|
|
262
|
+
- `kl.explain(name, senses)` and `kinloom think mind.json '{"hp": 0.2}'` show every action's score and why.
|
|
263
|
+
|
|
264
|
+
## The world: who knows what, who likes whom
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
kl.character("BAKER", "a warm baker who hates thieves", group="village")
|
|
268
|
+
kl.event("the player stole bread", by="PLAYER", seen_by=["BAKER"], tag="theft", impact=-0.6) # only witnesses know
|
|
269
|
+
kl.meet("BAKER", "GUARD") # they talk: the guard hears it (fainter, weighted by his trust in the baker)
|
|
270
|
+
kl.knows("GUARD", "theft") # True
|
|
271
|
+
kl.opinion("GUARD", "PLAYER") # -0.15
|
|
272
|
+
kl.reputation("PLAYER", "village") # a group's average opinion
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`impact` left out: a model estimates it per witness from their personality. `spread(group, rounds)` lets a group
|
|
276
|
+
talk. What characters know and feel goes into everything they say, decide and answer.
|
|
277
|
+
|
|
278
|
+
**Shapes by example:** `["a", "b"]` one of; `[0.0, 1.0]` a number in range; `[1, 10]` a whole number; `0.5`, `3`,
|
|
279
|
+
`True`, `"text"`, `"#ff7a45"` (a color) that type; `[example]` a list; `{...}` an object. Every answer is validated
|
|
280
|
+
and clamped against the example; one repair; then the example. A tool's arguments are stricter: an action that leaves
|
|
281
|
+
out an option or a range (`{"item": ["horseshoe", "dagger"]}`) is refused, never given the first option.
|
|
282
|
+
|
|
283
|
+
## Why it stays fast, cheap and safe
|
|
284
|
+
|
|
285
|
+
- **Lines are written ahead** in batches, as templates (`"{who} dives past {whom}!"`), then filled with your facts:
|
|
286
|
+
`say` answers without waiting for a model, and a line never shows a number your game did not give.
|
|
287
|
+
- **Decisions and actions are cached**: `decide`/`act` return at once; the model refreshes them every `every`
|
|
288
|
+
seconds, sooner when the situation changes a lot.
|
|
289
|
+
- **Budget**: 30 calls per minute by default; background work always leaves room for the player.
|
|
290
|
+
- **Nothing reaches the game unchecked**: answers are validated and clamped, control characters and prompt
|
|
291
|
+
injection text are dropped, unknown tools are refused; the server only accepts local pages unless you start it
|
|
292
|
+
with `--public --token`.
|
|
293
|
+
- **Your content rules**: `"banned_words": [...]` in `kinloom.json`, or `Kinloom(content_filter=fn)` in Python
|
|
294
|
+
(`fn(text) -> bool`): blocked model text is never shown, the offline answer is used instead.
|
|
295
|
+
|
|
296
|
+
## Tools
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
kinloom # what Kinloom found here (model, engine, characters, server) and what to ask your agent
|
|
300
|
+
kinloom add NAME "who they are" [--tools "give(item)" --trust give=0.3 --strict --lore "..."] # into kinloom.json
|
|
301
|
+
kinloom recipe "any request" # the call and the patterns for one feature, in this game's language
|
|
302
|
+
kinloom hooks <game|.dll> # a mod: where Kinloom plugs into a .NET game you have no source of (REA reads it);
|
|
303
|
+
# --guide: mod APIs, IL2CPP, native and web games
|
|
304
|
+
kinloom run -- <game command> # runs the game with a server, then prints what the AI did
|
|
305
|
+
kinloom log [-n 40] [--follow] # what a running server did, as text
|
|
306
|
+
kinloom deploy [folder] # an online server for a shipped game: Dockerfile + guide + tokens (deploys nothing itself)
|
|
307
|
+
kinloom talk NAME "who they are" [--speaker Ana --memory m.json --web --voice] # a conversation in the terminal (or browser)
|
|
308
|
+
kinloom speak "Hello there." [--name Mira --voice "low, warm"] # hear the voice your game will use
|
|
309
|
+
kinloom init [folder] # detect the engine, drop the client, write AGENTS.md, plus the files and
|
|
310
|
+
# /kinloom commands of the agents found here (installed or already used: Claude Code + skill,
|
|
311
|
+
# Gemini, Cursor, Copilot, Windsurf, aider, opencode; --agents all: every one), and KINLOOM.md
|
|
312
|
+
# (39 patterns); a person at a terminal gets 3 short questions (and the model, when a
|
|
313
|
+
# coding agent is installed: a key or its plan)
|
|
314
|
+
kinloom try VEGA "over-excited commentator" --event overtake --facts '{"who": "BLAZE"}' # hear it first
|
|
315
|
+
kinloom serve # the local server engines talk to (a dashboard at http://localhost:7717 if you want one)
|
|
316
|
+
kinloom check # finds integration mistakes: missing await, undeclared characters, keys in code
|
|
317
|
+
kinloom test [--record F | --replay F | --offline] # the AI checked like code: steps and checks in kinloom.tests.json
|
|
318
|
+
kinloom probe NAME # plays a character like a tester: secrets, manipulation, injection, repeats, speed
|
|
319
|
+
kinloom use claude-code [--model haiku] # answers from your coding agent's plan (codex, gemini-cli, opencode); `use key`
|
|
320
|
+
kinloom bundle # Kinloom as a program that needs no Python (kinloom-server/), for Godot, Unity, Unreal...
|
|
321
|
+
kinloom ship # before a release: keys inside the game, how players reach a model, offline answers,
|
|
322
|
+
# caps and prices, the rating; writes a draft of the store's AI disclosure
|
|
323
|
+
kinloom guide [engine] # the instructions for coding agents
|
|
324
|
+
kinloom doctor # which model and voice, key present (never printed), latency, REA for mods
|
|
325
|
+
kinloom mcp # MCP server for coding agents: recipe, hooks, add, log, guide, init, try, mind, check,
|
|
326
|
+
# status, test, probe, use, ship
|
|
327
|
+
GET /v1/openapi.json # the HTTP API described (also kit/openapi.json): a client for any other engine
|
|
328
|
+
kinloom demo # watch a mind learn to win a fight, offline, in seconds
|
|
329
|
+
kinloom think mind.json '{"hp": 0.2}' # a mind's choice on one moment, with every score
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Every engine: the whole SDK in its language
|
|
333
|
+
|
|
334
|
+
`kinloom init` copies one file (no dependencies) into the game. Each runs all of Kinloom in the game's process; the
|
|
335
|
+
same calls, prompts, checks and offline answers as this package (`conformance/native.py` compares them request by
|
|
336
|
+
request). Native mode calls the model from the game; relay mode asks `kinloom serve` (the key stays on your server);
|
|
337
|
+
offline mode never calls a model.
|
|
338
|
+
|
|
339
|
+
| Language | Native | Offline | Relay |
|
|
340
|
+
|---|---|---|---|
|
|
341
|
+
| TypeScript / JavaScript (`clients/web/kinloom.ts`, built to `kinloom.js` + `kinloom.d.ts`) | `new Kinloom({ provider: { url, key, model } })`; `provider: "auto"`: the environment (Node, Deno, Bun) | `new Kinloom({ offline: true })` | `new Kinloom({ url })` |
|
|
342
|
+
| C# (`clients/csharp/Kinloom.cs`) | `new KinloomClient(new KinloomOptions { Provider = new ProviderSpec { Url, Key, Model } })`; `ProviderAuto = true` | `Offline = true` | `new KinloomClient(url)` |
|
|
343
|
+
| Java (`clients/java/Kinloom.java`) | `Kinloom.direct(url, key, model)`; `Options.providerAuto` | `Kinloom.offline()` | `new Kinloom(url, session, token)` |
|
|
344
|
+
| Rust (`clients/rust/kinloom.rs`) | `Kinloom::direct(url, key, model)`; `Options { provider_auto: true, .. }` | `Kinloom::offline()` | `Kinloom::with(url, session, token)` |
|
|
345
|
+
| C++17 (`clients/cpp/kinloom.hpp`) | `kinloom::Kinloom::direct(url, key, model)`; `Options::provider_auto` | `kinloom::Kinloom::offline()` | `kinloom::Kinloom kl(url)` |
|
|
346
|
+
| GDScript (`clients/godot/addons/kinloom`) | `Kinloom.direct(url, key, model)`; `Kinloom.native({"provider": "auto"})` | `Kinloom.offline()` | the `Kinloom` autoload as it starts |
|
|
347
|
+
| Lua (`clients/lua/kinloom.lua`) | `Kinloom.new({ provider = { url = ..., key = ..., model = ... } })`; `provider = "auto"` | `Kinloom.new({ offline = true })` | `Kinloom.new({ url = ... })` |
|
|
348
|
+
| Python (this package) | `Kinloom()`: the environment | `Kinloom(provider=None)` | `Kinloom(url=..., token=...)` |
|
|
349
|
+
|
|
350
|
+
A url ending in `anthropic.com` (or `kind: "anthropic"`) talks to Claude; any other url is a chat-completions
|
|
351
|
+
endpoint. Native options (spelled each language's way): `voice` (an OpenAI-compatible speech and transcription
|
|
352
|
+
endpoint), `config` (the keys of `kinloom.json`), `models` (`{"background": "a-cheaper-model"}`), `rating`, `language`,
|
|
353
|
+
`calls_per_minute`, `prices`, `content_filter`, `memory`
|
|
354
|
+
(a file; none by default outside Python and Godot); TypeScript, C#, GDScript and Lua also take a list of providers
|
|
355
|
+
tried in order (a dead key hands over to the next). Every SDK has `stats()`, `journal()` and `on(listener)`. HTTPS:
|
|
356
|
+
the platform's own (fetch, HttpClient, HttpURLConnection, Godot's HTTPClient; WinHTTP on Windows and the `curl`
|
|
357
|
+
command elsewhere for Rust and C++; LuaSec or LÖVE 12 for Lua), or a transport the game gives in C#, C++ and Lua
|
|
358
|
+
(Unity WebGL's UnityWebRequest, Unreal's FHttpModule, Defold's `http.request`) and in Rust through a provider's
|
|
359
|
+
`with_transport`. In C#, the transport given (`KinloomOptions.Http`) also carries a relay's calls. Python only: a
|
|
360
|
+
coding agent's plan as the model, the operating system's voice, the CLI and the developer tools.
|
|
361
|
+
|
|
362
|
+
Relay mode in a shipped game: the relay keeps memory per session, so give each player's id as `session`. With a relay
|
|
363
|
+
token and no session, the browser client (localStorage), the Godot addon (user://kinloom-session.txt) and Unity's
|
|
364
|
+
`KinloomDialogue` (PlayerPrefs) keep one id per install instead of the shared "default". Every client asks the relay
|
|
365
|
+
for `decide` and `act` at a quarter of the character's `every` (at most 4 times a second), and a rate limit
|
|
366
|
+
(HTTP 429) keeps that call offline for the relay's Retry-After (5 s by default), told once. C#: streamed pieces (`onPiece`) and awaited calls
|
|
367
|
+
come back on the thread that called; warnings go to `KinloomClient.Warn` (Unity's console with `KinloomDialogue`).
|
|
368
|
+
|
|
369
|
+
## Models
|
|
370
|
+
|
|
371
|
+
Two ways, the developer's choice: **an API key** (below), or **the coding agent you already pay for**:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
kinloom use claude-code --model haiku # Claude Code answers the game's calls, with your Claude plan, no key
|
|
375
|
+
kinloom use codex | gemini-cli | opencode # the same with those agents; `kinloom use key` goes back to an API key
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Measured with Claude Code (haiku, one soul character, `proof/agents`): first word 0.8-1.7 s, a whole reply 1.5-2.6 s,
|
|
379
|
+
0 failures in 16 calls, a picture seen (two runs). Kinloom starts the agent ahead (a cold start took 5.2 s), without
|
|
380
|
+
its tools, in an empty folder, with thinking off (on, it took 8-12 s a reply). Codex, Gemini CLI and opencode are called with
|
|
381
|
+
the flags their documentation gives (checked 2026-10-28; not run here: no login). A player's machine has no developer plan: ship with a key on a server
|
|
382
|
+
(`kinloom deploy`) or the player's own key.
|
|
383
|
+
|
|
384
|
+
Any model, hosted or on your machine, found from the environment and never guessing a model name:
|
|
385
|
+
|
|
386
|
+
| What you set | Model used |
|
|
387
|
+
|---|---|
|
|
388
|
+
| `KINLOOM_BASE_URL` + `KINLOOM_MODEL` | any chat-completions endpoint (LM Studio, vLLM, llama.cpp server...) |
|
|
389
|
+
| `DEEPSEEK_API_KEY` | `deepseek-flash` (or `KINLOOM_MODEL`) |
|
|
390
|
+
| `ANTHROPIC_API_KEY` | `claude-haiku-4-5` (or `KINLOOM_MODEL`) |
|
|
391
|
+
| `OPENAI_API_KEY`, `OPENROUTER_API_KEY`, `GEMINI_API_KEY`, `GROQ_API_KEY`, `MISTRAL_API_KEY`, `TOGETHER_API_KEY`, `XAI_API_KEY`, `FIREWORKS_API_KEY`, `CEREBRAS_API_KEY` | `KINLOOM_MODEL` (required: `kinloom doctor` says so when it is missing) |
|
|
392
|
+
| nothing, Ollama running | the biggest chat model installed (vision and embedding models skipped), or `KINLOOM_MODEL` |
|
|
393
|
+
| nothing | offline: characters say your lines and examples (no AI; `kinloom` says so) |
|
|
394
|
+
|
|
395
|
+
- **Reasoning is turned off** for game calls (qwen3, gpt-5 and other thinking models answer in a fraction of the time;
|
|
396
|
+
an endpoint that refuses the setting is learned once). Measured on qwen3:8b through Ollama: before, 10 of 12
|
|
397
|
+
replies fell back to the offline line (the hidden reasoning used the whole budget); after, 0 of 12.
|
|
398
|
+
- **A dead key does not silence the game**: a key refused or out of credit (401, 402, 403) hands over to the next
|
|
399
|
+
provider found (another key, then a local model), once, and the journal says so.
|
|
400
|
+
- **Local models** are loaded when Kinloom starts, never asked twice at once, and get more time for a reply.
|
|
401
|
+
- Small models' habits are read: the `~~~` marker written first, `mood: calm` lines instead of JSON, prompt labels
|
|
402
|
+
copied into the reply (asked again), the player's lines written by the model (cut there).
|
|
403
|
+
|
|
404
|
+
Checked with real models: DeepSeek, OpenRouter (`deepseek/deepseek-v4.1-flash`), Ollama `qwen3:8b` and `llama3.2:1b`. Anthropic, OpenAI and the other services above are covered by tests against
|
|
405
|
+
fake endpoints only (no key here). `KINLOOM_OFFLINE=1` forces offline answers. An optional `kinloom.json` sets the
|
|
406
|
+
provider, model, language, budget and your prices (cost is only shown when you declare prices).
|
|
407
|
+
|
|
408
|
+
Tests and CI: `KINLOOM_SCRIPT=answers.json` replays scripted answers; `KINLOOM_RECORD=file.json` records real ones.
|
|
409
|
+
|
|
410
|
+
## Patterns
|
|
411
|
+
|
|
412
|
+
`kinloom guide` prints 39 tested patterns: characters with a soul (talk, consequences, bonds, characters talking to each other), narrator, commentator, companion, rival, enemy tactics, boss phases,
|
|
413
|
+
shopkeeper, quest giver, hint coach, radio/dispatcher, director (difficulty, pacing, weather), economy balancer,
|
|
414
|
+
generators, dialogue options, NPC schedules, player commands, tutorial guide, post-match recap, crowd minds, enemies
|
|
415
|
+
that learn, witnesses and rumors, reputation and factions, a continuous director, grounded conversation, voices,
|
|
416
|
+
a narrator reading aloud, a bridge for games Kinloom cannot run in, answers about a picture, the game's tools declared
|
|
417
|
+
once, the AI checked like code, the game's rules on what the model makes and what characters say and do,
|
|
418
|
+
challenges the game can check, items shown as they are written, and bystanders who react.
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
MIT: see [LICENSE](https://github.com/KorroAi/kinloom/blob/main/python/LICENSE).
|