B: a desktop companion built around one event bus
An autonomous, context-aware AI desktop companion. Built with Python, featuring real-time screen vision, custom ONNX voice synthesis, active window tracking, and a dynamic floating UI with reactive facial expressions.
At a glance
- What is it?
- B is a Python desktop companion that watches the active window, decides on its own whether to speak, and renders that decision as voice, expression and movement. The interesting part is not the character but the strict event bus that keeps every module ignorant of every other module.
- Who is it for?
- B fits engineers on Windows who want an ambient desktop assistant and are willing to accept a window-reading privacy tradeoff; it does not fit macOS or Linux users, teams that need shared or audited context, or anyone who needs a pinned, released version, since the repository publishes no GitHub releases and the last push was on 2026-07-07.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 108 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
B watches the screen instead of waiting for a command
B is a desktop companion rather than a command driven assistant, and that choice drives everything else in the codebase. The README describes it as an autonomous, context-aware companion that keeps running while you work, tracks which window has focus, reads that window semantically, and decides on its own whether it has something worth saying. Three promises shape the product. It stays silent by default so deep work is not interrupted, an internal state machine maps emotion onto facial expression and curiosity, and an autonomy loop lets it speak unprompted. Windows focus is the entry signal, so B is tied to a single desktop and to the applications running on it. The target is a person who wants ambient assistance from a local application rather than a chat window waiting for a prompt. The repository layout backs that up: main.py boots the process, while physics/, ui/, audio/ and vision/ sit next to brain/, and docs/ holds a demo video. The tradeoff arrives at once, since an assistant that watches everything you do also has to be trusted with everything you do.
The EventBus is the real API surface
Every module in B talks through one object, the EventBus, and the architectural diagram makes the rule strict: no module knows about any other module. core/bus.py holds a thread-safe publish and subscribe broker, and main.py instantiates the modules, starts a 60fps tick timer and registers global hotkeys. The event names carry the flow. WindowTracker publishes active_window_changed, SemanticSensor and VisionSensor publish context_updated, and the CognitiveEngine subscribes to context_updated, sends b_spoke for a spoken line, and sends b_move_request when the character should move. A tick event drives the StateMachine and the KinematicsEngine on the output side, while the AutonomyEngine publishes trigger_proactive_thought to ask the brain for an unprompted response. The benefit is that a new renderer can subscribe to b_spoke without touching the brain. The cost is that these event names become the de facto contract, and the diagrams in the README are the only place that contract is written down.
Perception falls back from UI Automation to OCR
Perception has two paths and the second one is a fallback. The WindowTracker is hook based and fires when you switch focus. The SemanticSensor then walks the UI Automation tree, extracts structured content, scores the quality of that extraction, and applies an adaptive cooldown that limits how often it retries. When extraction succeeds, the sensor publishes context_updated. When it fails or lands in cooldown, the VisionSensor takes over: it captures the screen through MSS and runs Tesseract OCR, then publishes context_updated from pixels rather than from the tree. This fallback exists because not every framework exposes a usable accessibility tree, so screen reading is a general safety net rather than an edge case. The tradeoff is precision. A tree walk can report which control holds focus, while OCR only reports the text that happened to be on screen, which means the context reaching the model is noisier exactly when the application is unusual. The pipeline shape is deterministic, which is what makes a 60fps tick and an asynchronous bus workable at all.
Local llama-cpp or Groq, chosen by one environment flag
The CognitiveEngine in brain/llm.py orchestrates inference, and the README names two backends, Groq in the cloud or a local llama-cpp runtime. The choice is not compiled in. It is a variable in a dotenv file that the repository ships as .env.example and that main.py reads through python-dotenv. Setting USE_LOCAL_MODEL to true selects local llama-cpp inference, leaving it at false keeps the cloud path and requires a Groq key. The file also hints at where the project might go next, with commented lines for OPENAI_API_KEY and ANTHROPIC_API_KEY, though neither is wired up anywhere in the tree. These lines come straight from .env.example and they are the entire configuration surface exposed for model selection:
# Model Configuration
# Set to 'true' to use local inference (llama-cpp), 'false' for cloud (Groq)
USE_LOCAL_MODEL=false
# Groq API Key (Required if USE_LOCAL_MODEL=false)
# Get yours at: https://console.groq.com/
GROQ_API_KEY=your_groq_api_key_here
# Optional: Other configuration if needed in the future
# OPENAI_API_KEY=...
# ANTHROPIC_API_KEY=...Fill in the key or flip the local flag, then start the process through main.py. Which model file llama-cpp loads, and what happens on a failed cloud call, are not documented, so a local setup forces its own decisions on the model choice.
Voice, face and motion all listen to the same b_spoke event
Speech, expression and movement are separate subscribers to one event. The VoiceEngine in audio/speaker.py performs text to speech with a DSP vocoder and raises speaking_start and speaking_end so the FaceRenderer can animate the character in step with the audio. The FaceRenderer draws the expressions, the ChatBubble shows text, and the KinematicsEngine handles the b_move_request that the brain issues. The StateMachine in brain/soul.py owns the emotional state, covering blinking, resting and conversing, and doubles as a real-time buffer that parses streaming model output into sentences. Voice generation is the part of the stack the project one line description calls custom ONNX synthesis, while the pinned dependency is piper-tts, a packaged voice runtime, and the voices/ directory at the top level is where voice assets sit. Adopting B means accepting that mismatch or reading the source to see which path the VoiceEngine actually takes.
The dependency list fixes the host platform
The dependency list is the clearest statement of what B needs from the host machine, and it is heavier than a chat script. PyQt6>=6.6.0 provides the floating overlay, pywin32>=306 provides the window hooks that feed active_window_changed, and that pairing pins the project to Windows even though the README badge advertises Python 3.10+ and PyQt6 6.6.0+. Audio and models add the rest, with llama-cpp-python>=0.2.0 for local inference, piper-tts>=1.2.0 for speech, sounddevice>=0.4.6 and pedalboard>=0.9.0 for capture and effects, scipy>=1.10.0 and numpy>=1.24.0 for numeric work, faster-whisper>=1.0.0 for speech to text, and python-dotenv>=1.0.0 for the dotenv file. Here is requirements.txt as the repository holds it:
PyQt6>=6.6.0
pywin32>=306
llama-cpp-python>=0.2.0
piper-tts>=1.2.0
sounddevice>=0.4.6
pedalboard>=0.9.0
scipy>=1.10.0
faster-whisper>=1.0.0
numpy>=1.24.0
python-dotenv>=1.0.0Note what is absent. There is no database client, no web framework and no HTTP server, which fits a local companion with no account system, and PRIVACY.md and SECURITY.md at the top level put the privacy posture in separate documents. Because the voice, vision and language stack is local except for the Groq call, the backend you choose decides how much of your desktop context leaves the machine.
The autonomy thresholds are the part nobody can tune
Two things stay under specified. The AutonomyEngine decides when to speak unprompted from context quality and timing, but the repository publishes no thresholds for either, so an install cannot predict how often the character interrupts you. The README frames proactive speech as something B does only when it has something valuable to contribute, and that judgment lives in brain/autonomy_loop.py with no configuration key exposed in .env.example. The second limit is platform scope. The perception path depends on UI Automation and win32 hooks, and the OCR fallback is a salvage path rather than an equal substitute, so running B on macOS or Linux would need work the project does not describe. The repository is not archived and the last push was on 2026-07-07, so this is a moving target rather than a fixed release, and with no GitHub releases published there is no version to pin against or to roll back to.
Editorial conclusion
B fits engineers on Windows who want an ambient desktop assistant and are willing to accept a window-reading privacy tradeoff; it does not fit macOS or Linux users, teams that need shared or audited context, or anyone who needs a pinned, released version, since the repository publishes no GitHub releases and the last push was on 2026-07-07. Before adopting it, verify the autonomy thresholds in brain/autonomy_loop.py, confirm that your Groq key or local llama-cpp model receives the screen context you expect, and read PRIVACY.md, because the context_updated payload carries whatever the OCR fallback managed to read.
Frequently asked questions
What is B and what does it actually do?
B is a Python desktop companion that runs on your desktop, tracks the active window, reads it semantically, and speaks and moves on its own instead of waiting for a command. It is aimed at people who want ambient help from a local application rather than a chat window.
Does B need an API key to run?
Only on the cloud path. The shipped .env.example sets USE_LOCAL_MODEL=false with a GROQ_API_KEY placeholder, and flipping USE_LOCAL_MODEL to true moves inference to a local llama-cpp runtime.
What happens when B cannot read the active window?
The SemanticSensor's UI Automation walk can fail or sit in its adaptive cooldown, and then the VisionSensor takes over with MSS screen capture and Tesseract OCR before publishing context_updated from the captured text.
Is B still being updated?
The repository is not archived and the last push was on 2026-07-07, with no GitHub releases published, so there is no tagged version to install against.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ahmadhassan-bted-b)Was this useful?