Bessmyslennost i skorotechnost

Philosophy built into memory, conversation and recirculation

10 notes · 18 min

"Bessmyslennost i skorotechnost" as a philosophy built into memory, conversation and recirculation: illustration 1
We meet on the threshold between conditional normality and chaos. Bessmyslennost i skorotechnost 👆

Contents

  1. How the idea of a book gave rise to a space for memory, conversation and recirculation2 min
  2. What this project is really made of1 min
  3. Memory, profile and context: what the bot rests on2 min
  4. The logic by which the bot answers people2 min
  5. How I made a system that doesn't let content vanishphoto2 min
  6. How the project's philosophy is sewn into the codephoto2 min
  7. Why this project lives not only in text3 min
  8. How the bot gathers and updates people's profilesphoto1 min
  9. How I protect the project from chaos, duplicates and chancephoto2 min
  10. Why I wrote this code not for the sake of automation1 min

How the idea of a book gave rise to a space for memory, conversation and recirculation

I’ve long had one thought: the best way to really talk to a person isn’t to spend years slowly getting closer, but to let them enter your optics right away.

At one time I thought this called for a book. Having read it, a person would enter the conversation not through the outer layer but through a more precise understanding. But I never wrote the book.

Instead, another form gradually emerged.

I have a main channel that holds the main body of my material in the context of the project and my philosophy. But the channel doesn’t explain itself directly: much in it stays without comment and requires optics of its own. Next to it there was always another layer of material: videos, fragments, observations that didn’t fit the frame of the main channel, though they mattered to me no less.

That’s partly why the “Bessmyslennost i skorotechnost” chat appeared together with the ann bot: not only as a separate space for this material, but also as a way of revealing the philosophy indirectly, the philosophy this material arises from.

Over time this chat became a media library and an environment where content doesn’t vanish after publication but returns, recirculates and goes on living.

Social networks are built so that almost anything quickly sinks in the stream. This principle has always seemed too poor to me. Some things don’t open up right away. Sometimes they need not a day or a month but years.

So the ann bot appeared inside this space. Not as decoration and not as a feature for the sake of a feature. Its job is different: in conversation, in passing, not head-on, to show the philosophy that lies beneath what I do. To explain not individual posts but the angle of view they arise from.

For me “Bessmyslennost i skorotechnost” isn’t just a chat and not just an archive. It’s an attempt to create an environment where things don’t dissolve in the stream right after they appear, but keep the right to be seen and understood later.

Back to contents

What this project is really made of

From the outside it looks simple: a chat, a bot, replies. But in fact this project is built as a system of several layers.

The first layer is the Telegram interface: messages, commands, media, entry points and the interaction with people itself.

The second layer is the SQLite database. It stores messages, users, profiles, evidence, media and service state. Otherwise the bot would remain a creature with a memory one chat window long.

The third layer is context logic. The bot relies not only on the last message but on the history of communication, the person’s profile, previous reactions, internal rules and accumulated material.

The fourth layer is profiling. The project gradually gathers a structure of observations about users: what they feel close to, how they speak, which motives recur. Thanks to this, replies become less random and more personal.

The fifth layer is work with media material. The bot doesn’t only talk, it also takes part in recirculating content: it returns old files to the stream, links them to the conversation and doesn’t let material vanish after publication.

And finally there are background processes: updating profiles, rotating content, service states, separate cycles that keep the whole system alive.

So it’s more accurate to say this: I made not just a Telegram bot but an environment that connects memory, profile, context, a media library and the logic of bringing material back to life.

Back to contents

Memory, profile and context: what the bot rests on

The basis of this bot isn’t one prompt but its own memory in SQLite.

Here’s one of the basic fragments:

c.execute("""
CREATE TABLE IF NOT EXISTS ann_messages(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  chat_id INTEGER, msg_id INTEGER,
  user_id INTEGER, date TEXT, text TEXT,
  reply_to_msg_id INTEGER, entities_json TEXT
)
""")

— CREATE TABLE IF NOT EXISTS ann_messages Creates the ann_messages table if it doesn’t exist yet. This is where the bot stores the dialogue history.

— id INTEGER PRIMARY KEY AUTOINCREMENT The internal sequence number of a row. The database itself needs it so that each record has a unique ID.

— chat_id INTEGER The ID of the chat the message came from. This is needed so messages from different chats don’t mix.

— msg_id INTEGER The ID of the message itself in Telegram. It tells you exactly which message is meant.

— user_id INTEGER The ID of the user who sent the message. So the bot remembers not only the text but also the author.

— date TEXT The date and time of the message. Needed for chronology: who said what and when.

— text TEXT The message text itself.

— reply_to_msg_id INTEGER If the message was a reply to another message, the ID of that original message is stored here. This is no longer just memory of text but memory of the structure of a conversation.

— entities_json TEXT Telegram service data: mentions, links, formatting and other entities. So the bot can, if needed, remember not only the text but also its form.

Now the moment a message is written to the database:

def ann_touch_message(user_id: int, chat_id: int, msg_id: int|None, text: str|None, dt_iso: str|None,
                      reply_to_msg_id: int|None, entities_json: str|None):
    c.execute("""
      INSERT INTO ann_messages(chat_id, msg_id, user_id, date, text, reply_to_msg_id, entities_json)
      VALUES(?,?,?,?,?,?,?)
    """, (int(chat_id), int(msg_id or 0), int(user_id), dt_iso or _utc_now_iso(), (text or "").strip(), reply_to_msg_id, entities_json))

— def ann_touch_message(…) The function that saves a new message into the bot’s memory.

— INSERT INTO ann_messages(…) The command inserts a new row into the ann_messages table.

— VALUES(?,?,?,?,?,?,?) The real values go here: chat, message, user, date, text, reply and entities.

— int(chat_id) Converts chat_id to a number. This is protection against wrong types.

— int(msg_id or 0) If there’s no msg_id, 0 is used. So the function can still save the record.

— dt_iso or _utc_now_iso() If no date is passed, the current time is taken.

— (text or “”).strip() If there’s no text, an empty string is used. strip() removes extra spaces at the edges.

This is exactly where the bot stops being an ordinary chatbot. It doesn’t just answer the last message, it stacks the conversation into its own memory: who wrote, when they wrote, what they wrote and what they were replying to.

Profile and context are then built on top of this memory. But the foundation starts right here.

Back to contents

The logic by which the bot answers people

The bot doesn’t work on the principle of “saw a message → generated text right away”.

First it decides whether to answer at all. Then it gathers context, pulls in the profile, and only after that builds a reply.

One of the key nodes looks like this:

ctx = ann_build_context_from_db(chat_id, limit=ANN_CONTEXT_MSG_LIMIT, max_chars=ANN_CONTEXT_MAX_CHARS)
profile_block = _build_profile_prompt_block(last_user_text, base_max_tokens, mention_max_tokens)

messages = [
    {"role": "system", "content": system_prompt},
    {"role": "system", "content": profile_block},
    {"role": "user", "content": ctx},
]

What matters here:

• ann_build_context_from_db(…) The function gathers context from the database. So before replying the bot takes not only the last message but the history of the conversation.

• chat_id Context is gathered for a specific chat. This protects against mixing different dialogues.

• limit=ANN_CONTEXT_MSG_LIMIT Limits how many messages go into the context. Otherwise the prompt bloats and gets noisy.

• max_chars=ANN_CONTEXT_MAX_CHARS Additionally limits the size of the context in characters. This is needed so as not to overload the model.

• _build_profile_prompt_block(…) Separately builds the user profile block. So the bot mixes in not only the chat history but also accumulated observations about the person.

• last_user_text The user’s last message is passed to the profile block. This helps choose a more relevant tone and fact from the profile.

• messages = […] This is the final assembly of the prompt package before generation.

• {“role”: “system”, “content”: system_prompt} The main system layer. This holds the bot’s general character, its frame and behaviour.

• {“role”: “system”, “content”: profile_block} The second system layer. This holds personalisation: tone, facts, the particulars of a specific person.

• {“role”: “user”, “content”: ctx} And this holds the assembled context of the conversation itself.

So the logic is:

  1. the bot doesn’t answer “blindly”
  2. first it gathers the dialogue history
  3. then it adds the person’s profile
  4. then it connects this with the general philosophy and system rules
  5. and only after that generates a reply

That’s why its reply rests not on one last line but on several layers at once: context, profile, memory and system logic. The bot answers not “a message” but the situation of a conversation.

Back to contents

How I made a system that doesn't let content vanish

One of the main tasks of this project isn’t only answering people but also bringing material back to life.

For this the bot has a separate content base. It stores not only the materials themselves but also their descriptions. It’s by these descriptions that the system understands what can appear in a conversation again and when.

One of the key fragments looks like this:

def load_content_index(path: str = CONTENT_JSON_PATH):
    global _content_loaded, content_index, content_embeddings
    if _content_loaded:
        return

    with open(path, "r", encoding="utf-8") as f:
        raw = json.load(f)

    content_index = raw.get("items", []) or []
    content_embeddings = [_extract_embedding(item) for item in content_index]
    _content_loaded = True

What matters here:

• load_content_index(…) The function loads the content base from content.json.

• global _content_loaded, content_index, content_embeddings Shared variables are declared here, in which the loaded content then lives.

• if _content_loaded: return If the base is already loaded, there’s no need to read the file again. This saves time and avoids extra work.

• json.load(f) Reads the whole JSON file.

• content_index = raw.get(“items”, []) or [] Gets the list of materials. So the bot works with a separate content index. If there’s no items key, an empty list is used.

• content_embeddings = [_extract_embedding(item) for item in content_index] An embedding is pulled out separately for each item. So content is stored not only as text or a link but also as a vector for semantic search.

• _content_loaded = True A mark that the base is already in memory.

From there the project can choose suitable material not at random but by semantic closeness to the current conversation.

Another important node:

picked = pick_relevant_content(query_text, top_k=CONTENT_TOP_K)
if picked:
    send_content_to_chat(chat_id, picked)

What happens here:

• pick_relevant_content(query_text, top_k=CONTENT_TOP_K) The system takes the current text or topic of the conversation and looks for the most suitable items among the content.

• query_text This is what the search starts from: a message, a topic or an assembled context.

• top_k=CONTENT_TOP_K Limits the number of candidates. The bot doesn’t drag in everything, it picks only the top matches.

• if picked: If suitable material is found, the bot goes on.

• send_content_to_chat(chat_id, picked) Sends the found material back to the chat.

This is where recirculation begins. The material doesn’t lie in a dead archive. It can appear in a conversation again if the system understands there’s a place for it now.

So the logic here is:

  1. content is stored separately
  2. it has embeddings
  3. the bot can search by meaning
  4. the material found returns to the chat
  5. old things don’t vanish but get a new point of entry

So for me this project isn’t just a bot. It’s a system in which memory works not only for replies but also for bringing material back to life.

How I made a system that doesn't let content vanish: illustration 1How I made a system that doesn't let content vanish: illustration 2How I made a system that doesn't let content vanish: illustration 3How I made a system that doesn't let content vanish: illustration 4

Back to contents

How the project's philosophy is sewn into the code

In this project philosophy doesn’t lie next to the bot as a separate text. It’s loaded into the system and affects how exactly the bot speaks.

The first important node:

def _reload_ann_philosophy(force: bool = False) -> bool:
    data = _safe_json_read(_PHILOSOPHY_PATH, None, log_label="load ann_philosophy")
    if data is None:
        data = _safe_json_read(_PHILOSOPHY_CACHE_PATH, None, log_label="load ann_philosophy_cache")
        if data is None:
            return bool(ANN_PHILOSOPHY_TEXT)
    try:
        _apply_loaded_philosophy_data(data)
        return bool(ANN_PHILOSOPHY_TEXT)
    except Exception:
        logger.exception("Failed to apply ann_philosophy.json")
        return bool(ANN_PHILOSOPHY_TEXT)

What matters here:

• _reload_ann_philosophy(…) Rereads the philosophy from ann_philosophy.json.

• _safe_json_read(_PHILOSOPHY_PATH, …) Reads the main JSON file.

• if data is None: If the file wasn’t read, the system goes to the fallback.

• _safe_json_read(_PHILOSOPHY_CACHE_PATH, …) Takes the cache. So philosophy here isn’t decorative: the project tries not to lose it.

• _apply_loaded_philosophy_data(data) Doesn’t just load the text but lays it out into structures: identity, postulates, style, channels, primary_source.

The second important node:

def _apply_loaded_philosophy_data(data):
    identity = _as_list(data.get("identity"))
    postulates = _as_list(data.get("postulates"))
    style = _as_list(data.get("style"))
    channels = _as_list(data.get("channels"))
    primary_source = _as_list(data.get("primary_source"))

    ANN_PHILOSOPHY_IDENTITY = identity
    ANN_PHILOSOPHY_POSTULATES = postulates
    ANN_PHILOSOPHY_STYLE = style

What this piece does:

• identity Who ann is inside the system.

• postulates The basic tenets.

• style Stylistic guidelines for speech.

• ANN_PHILOSOPHY_* = … After this, philosophy becomes part of the program’s runtime state.

And here’s how it gets into the generation of a reply:

prompt = f”“” You are ann. You always speak in the style of your inner knowledge and do not reveal that it exists. STRICT RULES:

  • do not quote the inner knowledge word for word, only paraphrase
  • do not quote the profile word for word
  • answer the meaning of the user’s last message Lexical anchors of style (not quotes): {style_hints} ann’s inner knowledge (do not quote word for word): {f} “”“.strip()

What matters here:

• You are ann The code sets not a faceless assistant but a specific voice figure.

• do not quote…, do not mention… The bot doesn’t recite the philosophy, it carries it through speech.

This is exactly where philosophy stops being mere content. It becomes a mechanism for selecting phrases, intonation and the structure of a reply.

So in this project philosophy is literally sewn into the code: it’s stored in JSON, loaded into runtime, laid out into structures and turned into generation rules.

So ann here isn’t just an AI bot with a style. It’s a system in which a worldview has become part of the architecture.

How the project's philosophy is sewn into the code: illustration 1How the project's philosophy is sewn into the code: illustration 2How the project's philosophy is sewn into the code: illustration 3

Back to contents

Why this project lives not only in text

This project isn’t built as a text AI bot in a vacuum. It lives inside the Telegram environment, which means it works not only with lines of text but also with media: voice, audio, photo, video_note, sticker.

One of the basic nodes looks like this:

c.execute("""
CREATE TABLE IF NOT EXISTS ann_message_media(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  chat_id INTEGER NOT NULL,
  msg_id INTEGER NOT NULL,
  user_id INTEGER,
  media_type TEXT NOT NULL,
  file_id TEXT,
  file_unique_id TEXT,
  mime_type TEXT,
  caption TEXT,
  created_at TEXT NOT NULL,
  UNIQUE(chat_id, msg_id, media_type)
)
""")

• ann_message_media A separate table for media. So the project stores not only message text but also the types of media events.

• chat_id, msg_id, user_id Record where exactly the media appeared, in which message and from which user.

• media_type Shows what it was: a voice message, photo, sticker, video note, audio.

• file_id, file_unique_id Make it possible to refer to this object through Telegram again later.

• caption If the media had a caption, it’s saved too.

• UNIQUE(chat_id, msg_id, media_type) Protection against duplicates. The same media object shouldn’t be written to memory twice.

Now the moment of saving itself:

def ann_save_media(chat_id: int, msg_id: int, user_id: int | None, media_type: str,
                   file_id: str | None, file_unique_id: str | None,
                   mime_type: str | None = None, caption: str | None = None):
    c.execute("""
      INSERT OR IGNORE INTO ann_message_media
      (chat_id, msg_id, user_id, media_type, file_id, file_unique_id, mime_type, caption, created_at)
      VALUES(?,?,?,?,?,?,?,?,?)
    """, (...))

• ann_save_media(…) The function saves media into the project’s memory.

• INSERT OR IGNORE If such a record already exists, the database doesn’t create a duplicate. This matters for stable work in Telegram, where the same event can sometimes arrive twice.

• media_type, file_id, caption The project remembers not just “there was some media” but its type, identifier and context.

So the bot stores not only the conversation in words but also the conversation in forms.

And the project has one more layer, a publishing one:

c.execute("""
CREATE TABLE IF NOT EXISTS ann_story_state(
  id INTEGER PRIMARY KEY CHECK (id=1),
  last_story_at TEXT,
  last_source_chat_id INTEGER,
  last_source_msg_id INTEGER,
  last_file_unique_id TEXT,
  cooldown_until TEXT,
  note TEXT
)
""")

• ann_story_state This is the Stories state table.

• last_story_at When a Story was last published.

• last_source_chat_id, last_source_msg_id Where the source material was taken from.

• last_file_unique_id Which file has already been used.

• cooldown_until When publishing is allowed again.

So the project treats media not as dead attachments. It remembers them, tells them apart, links them to a source and manages their reappearance.

That’s why this project lives not only in text. It lives in material: in a voice, an image, a sticker, a video note, a caption, a publication, a file returning to the stream.

And in this sense it’s built not as a “chat with AI” but as a system embedded in the real media life of Telegram.

Back to contents

How the bot gathers and updates people's profiles

One of the rarest mechanisms in this project has to do not with replies but with how the system gradually assembles a person’s profile.

A profile here doesn’t look like a form filled in once. It’s an updatable model of presence: it’s refined as communication goes on and isn’t rebuilt for every line.

The key node looks like this:

if n >= PROFILE_UPDATE_EVERY:
    c.execute("UPDATE ann_profile_counters SET since_last_update=0 WHERE user_id=?", (int(user_id),))
    queue_profile_update(int(user_id))

• PROFILE_UPDATE_EVERY Sets the update threshold. The profile isn’t touched after every new message.

• since_last_update The counter shows how much new material has accumulated since the last update.

• queue_profile_update(int(user_id)) Rebuilding the profile is moved out separately. This means the system first accumulates observations and then updates the profile as a task of its own.

So the logic here isn’t reactive but cumulative: first a body of new lines is gathered, then the system decides the person can now be described more precisely.

The profile itself is stored separately:

CREATE TABLE IF NOT EXISTS ann_profiles(...)

And the grounds for it are in one more layer:

CREATE TABLE IF NOT EXISTS ann_profile_evidence(...)

• ann_profiles Stores the current description of a person inside the system.

• ann_profile_evidence Stores what this description is based on at all.

This is an important point. The project doesn’t just “form an impression” of a user. It also stores the traces this impression grew from.

So the bot works not with a one-off image of its interlocutor but with a long-refined idea of a person.

That’s what makes the profile in this project not decorative personalisation but a separate operational layer of the system.

How the bot gathers and updates people's profiles: illustration 1

Back to contents

How I protect the project from chaos, duplicates and chance

The most unpleasant thing in projects like this isn’t errors but quiet accumulating chaos.

When the same event arrives twice. When media is saved again. When the system gradually starts contradicting itself.

From the outside it’s almost invisible. But this is exactly how a project loses its shape.

The first thing I had to do was stop duplicates at the lowest level:

UNIQUE(chat_id, msg_id, media_type)

The database simply won’t let the same thing be written twice. No extra logic and no hope that someone will sort it out by hand later.

Then comes the second layer:

INSERT OR IGNORE INTO ann_message_media

If the event has already happened, it just doesn’t go any further. No extra state is created.

But duplicates are only part of the problem.

There’s another form of chaos: repeated actions. When the system replies once too often, publishes once too often, or starts again something that no longer needs starting.

Here comes:

cooldown_until

The system stores the moment before which it must do nothing. This is no longer about functionality but about discipline of behaviour.

The next principle concerns heavy operations. They can’t be performed right at the moment of a reply. If you recalculate the profile for every line, the system becomes overly sensitive and starts making noise.

So part of the logic is moved to the background: queues, deferred updates, accumulating material before acting.

And finally, failures.

If an external layer goes down, the project shouldn’t turn into either silence or garbage. So there are pauses, fallback scenarios and state recovery here, and the database works through WAL so that writing doesn’t block the whole system.

In the end, protection is built in layers:

  • the database doesn’t let duplicates in
  • the code doesn’t amplify repetition
  • cooldown holds back excess activity
  • heavy logic is moved out of the dialogue
  • a failure doesn’t destroy the whole flow of work

And only after that can you talk about the quality of replies at all.

How I protect the project from chaos, duplicates and chance: illustration 1How I protect the project from chaos, duplicates and chance: illustration 2How I protect the project from chaos, duplicates and chance: illustration 3How I protect the project from chaos, duplicates and chance: illustration 4

Back to contents

Why I wrote this code not for the sake of automation

Projects like this are usually described in terms of convenience: the bot answers, saves something, publishes something, searches for something. But in my case the task was different from the start.

I didn’t write this code to speed up routine. I wrote it so that material wouldn’t vanish, a conversation wouldn’t reset after every line, and philosophy would exist not only in text but also in the behaviour of the system.

You can see the same in the logic of a reply:

messages = [
    {"role": "system", "content": system_prompt},
    {"role": "system", "content": profile_block},
    {"role": "user", "content": ctx},
]

A reply is built not from one last line. The system assembles the situation of the conversation: the frame, the person’s profile, the accumulated context. So it doesn’t just react, it holds duration.

But the most important point is that the project lives not only in replies.

queue_profile_update(int(user_id))
picked = pick_relevant_content(query_text, top_k=CONTENT_TOP_K)
ANN_PHILOSOPHY_STYLE = style

What matters here:

• queue_profile_update(…) The system keeps working between messages too. It updates its idea of a person not instantly but as material accumulates.

• pick_relevant_content(…) The material doesn’t lie in a dead archive. It returns to a conversation by meaning.

• ANN_PHILOSOPHY_STYLE = style Philosophy here isn’t attached alongside. It’s built into behaviour and affects the very principle of speech.

So this isn’t just a bot but an environment. Here a conversation doesn’t reset, material doesn’t vanish, and philosophy works as part of the system’s behaviour. “Bessmyslennost i skorotechnost” is the environment where this logic lives in action.

Back to contents

Next seriesWithout Streaming