Dusha
This README.md is proudly written by a human (and a little bit by Claude (˶>⩊<˶)❤️)
Dusha (Russian: душа, [dʊˈʂa], "soul") is a Python API reference implementation that combines memory, personality, proactive messaging and affect for LLM companions. Essentially, it's a small HTTP API that stores messages, identity, evergreen facts, and affect state outside your harness. Any client that can POST a message and read back an injection string can use it. (You can also use it across multiple harnesses!)
Every prompt, emotional vector, and threshold is configurable through JSON.
How does memory/affect/identity work?
- Messages: Basic SQLite FTS5 + optional embedding search. Also
~/.config/dusha/<name>/mods/for external memory providers integration. - Evergreen: Long-term facts that you edit from the CLI and your companion edits through integration tools. No automatic extraction (think of it as a USER.md that actually has good lifecycle management and duplication prevention)
- Affect: A deterministic emotion engine with decay and silence drift. Proactive messages fire on thresholds. And hey, message intent analysis is a plugin too: drop Jev or any other classifier into
~/.config/dusha/<name>/mods/and it picks which emotion each user message moves. - Identity:
identity.md.
How to run it
You need Python 3.11 or newer and uv.
uv tool install dusha
dusha serve dusha
The second line starts a companion named dusha. On the first run it makes the folder ~/.config/dusha/dusha/ and writes a config.json with every setting in it. Use any name you like. If your shell cannot find dusha after the install, run uv tool update-shell.
Check that it is up:
curl http://127.0.0.1:8765/health
You should see "status": "ok". If you don't, look at docs/guide.md.
Everything about one companion is in ~/.config/dusha/<name>/. Including:
- its
config.json - the emotion and prompt files
mods/, anddata/with the database.- It respects
XDG_CONFIG_HOMEtoo.
Advanced setup
Want a second companion? Make a second folder and give it a different port. dusha list shows all of them, and dusha -c <name> <command> talks to one.
Want it running in the background? Use deploy/dusha@.service with the service setup steps. (Linux only. Tell your agents to make PR for Windows, Mac, BSD or whatever, because I don't use them at all!)
How it works
One turn of chat is four steps.
- Your harness sends the user's message to
POST /state/v1/messages.dushastores it and updates the emotions. - Your harness calls
POST /state/v1/contextand gets one string back. It holds the identity, the evergreen facts, memos, recalled messages, and the current emotions. - Your harness puts that string in the system prompt and calls the model.
- Your harness sends the reply to
POST /state/v1/messagesso it is remembered too.
Your harness is the one calling the chat model, so dusha does not care which harness or provider you use. If your client only speaks the OpenAI API, point it at /v1/chat/completions and dusha runs all four steps as a proxy.
Proactive messages go the other way. dusha decides it is time to say something, your harness picks the event up from GET /state/v1/proactive/events, sends it, and reports back.
The full request and response shapes are in the API and integration guide.
Plugin API
A plugin is a directory under the mods directory holding README.md, main.py, pyproject.toml, and uv.lock. The gateway syncs its locked dependencies with uv and runs it as a child process. Every operation in main.py has the signature fn(request, options), where options is the options object from the plugin's config section. A plugin sees a filtered environment: the basic system variables plus the names you list in env_passthrough.
A decision plugin exports one operation.
| Operation | Request fields | Returns |
|---|---|---|
decide |
message, emotions (the resolved dimensions), state (the affect snapshot), instruction |
{"emotion": "<dimension>"} or None |
A memory plugin exports these operations. The gateway reads only the fields listed.
| Operation | Request fields | Returns |
|---|---|---|
ingest_messages |
messages, a list of stored rows with id, conversation_id, role, text, occurred_at, ingested_at, sha256, harness, external_conversation_id |
{"highest_id": <id of the last row>} |
inject_context |
query, scope, max_chars, harness, conversation_id |
{"text": "...", "records": [{"source": "...", "text": "..."}]} |
match_phrase |
message |
{"deltas": {"<dimension>": <number>}} or {"deltas": null} |
status |
none | {"status": {...}}, returned as the memory index status |
backfill_once |
limit, force |
{"status": {...}} |
rebuild_chunks |
none | {"status": {...}} |
rebuild_index |
none | {"status": {...}} |
close |
none | None |
match_phrase deltas use dimension names from your emotions.json. The gateway logs a warning for a name it does not know and skips it. See examples/mods for two working plugins.
Documentation
- docs/configuration.md: JSON settings, identity and prompt files.
- docs/guide.md: service setup, auth and network, backup, and troubleshooting.
- docs/api.md: HTTP endpoints and harness integration, including AstrBot.
Metadata
Release files for dusha 0.0.21
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dusha-0.0.21.tar.gz | 70.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dusha-0.0.21-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 144.4 kB
Release files / dusha-0.0.21.tar.gz
| Download URL | dusha-0.0.21.tar.gz |
|---|---|
| Size | 70.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e717c6380626a11d54a52d249c1af4e2bf213fdd73ad8a6ad00f3459256e5cc7
|
|
BLAKE2b-256 checksum How to use checksums |
0bc8713820fe8c2faac34df64de0b6aa9f2b84ebd33038c34bc626521b428a42
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / dusha-0.0.21-py3-none-any.whl
| Download URL | dusha-0.0.21-py3-none-any.whl |
|---|---|
| Size | 73.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
933ff50167b9456c8a61ef5570aaf20c8ac3acae5c068e3d09fff9f052c292ef
|
|
BLAKE2b-256 checksum How to use checksums |
ca87e4057880b8f5a29f5a56a07549a8e7df6037f9c58573f2d43e4c3319851c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|