Showing posts with label Go. Show all posts
Showing posts with label Go. Show all posts

Tuesday, 18 August 2026

Major Milestone: Skink Has Officially Bootstrapped!

Woohoo! It’s time for a long-overdue update on the language powering the future of DANI.

We just crossed a monumental threshold with Skink: it is now officially bootstrapped!

For anyone unfamiliar with compiler development, bootstrapping means that the Skink compiler can now compile its own source code. From here on out, all future versions of the Skink compiler will be written in Skink itself. It’s a huge rite of passage for any programming language project, and seeing it actually work after so much foundation work is insanely satisfying.

Where Go Fits In (For Now)

The handover
Now, does this mean I’m throwing Go out the window today? Not quite!

Go has a proven, rock-solid track record, and I’ll be keeping it around for prototyping work. Whenever I want to test out an idea quickly and I’m not sure if Skink is quite ready to handle the heavy lifting yet, Go gives me a reliable safety net.

However, the long-term vision hasn't changed: eventually, I plan to fully commit to Skink for almost all development across my projects, experiments, and research.

The Road to Skink 1.0

While bootstrapping is a massive step forward, we aren't quite at version 1.0 just yet. To earn that release tag, Skink needs to hit a few crucial targets:

  1. Cross-Platform Support: Clean cross-compilation across Windows, macOS, and Linux.
  2. Architecture Support: Full targeting for both x86 and ARM architectures.
  3. Embedded Target Transpilation: The ability to compile (or at least transpile down) to low-power platforms like the K210, ESP32, and standard Arduino C/C++.
  4. Direct Hardware I/O: First-class hardware abstraction libraries for lower-level protocols—specifically I2C, SPI, GPIO, and related interfaces.

Acceleration, Compute, and Graphics Backends

Beyond microcontrollers and standard CPUs, getting high-performance compute and neural inference support hooked up is high on the priority list.

  • CUDA Support: I still need to get CUDA fully enabled and tested. Because of my current local setup, this will likely require either acquiring new dedicated NVIDIA hardware or setting up a cloud-based VM for build and test pipelines.

  • Vulkan & AMD: My main local system currently relies on Vulkan to run LM Studio, so a Vulkan compute backend is high on the radar as a viable cross-vendor path. I'm also looking closely at AMD’s libraries (ROCm/HIP) to ensure broad hardware compatibility.

It's a lot of moving parts, but watching the architecture take shape piece by piece is incredibly rewarding.

Bringing It Back to DANI

So, why go through all the trouble of building a custom language from scratch?


It all comes down to DANI. My goal has always been to have a single, unified language capable of bridging every layer of DANI’s stack—from high-level logic and neural compute all the way down to real-time bare-metal sensor and actuator control. Skink is the key to making that happen without juggling three different language ecosystems.

Speaking of DANI, there have also been some fascinating developments regarding his neural network architecture recently... but I’ll leave you hanging on that for now and save the deep dive for the next post!

Stay tuned!


Friday, 19 June 2026

Introducing Skink-lang: Elegant Systems Programming for AI & Robotics (or, How to Stop DANI from Eating the Skirting Boards)


Over the last thirty years, I’ve worked across a sprawling landscape of technologies—from writing Go, C#, Pascal, Swift, and C, to orchestrating massive cloud pipelines, down to micro-managing registers on bare-metal microcontrollers.

But lately, my time has been dominated by a very specific, highly opinionated, and occasionally cooperative localized AI agent: DANI.

If you've been following my previous posts, you know DANI has transitioned from an LSTM-based cognitive core into a physical, moving entity. And that is where the real-world engineering headaches began. Every time you start a new robotics or physical AI project, you are forced to make a compromise that feels like choosing between a kick in the shins or a poke in the eye:

  • The C/C++ Extremity: You get deterministic, blazing-fast performance on microcontrollers, but you sacrifice developer ergonomics, modern package safety, and swift prototyping. Writing raw $C++$ register manipulation code sometimes feels like trying to perform laparoscopic surgery on yourself with a rusty spoon.
  • The Python Extremity: You get instant access to rich AI primitives, neural networks, and expressiveness. But you inherit bloated virtual environments that take up more disk space than the library of Alexandria, and unpredictable garbage collection (GC) latency. When DANI’s control loop is running at $100\text{ Hz}$ and the Python garbage collector decides to take a mandatory three-millisecond tea break, DANI doesn't stop. She gracefully, but deterministically, plows straight into the skirting board.

I got tired of choosing between developer speed and hardware execution safety. I wanted a compiled language built specifically for low-level systems hardware, high-throughput concurrent event streams, and native AI execution.

So, I built Skink-lang.

What is Skink?

Skink is an LLVM-backed, statically-typed systems programming language designed from the ground up for the modern era of intelligent automation. It blends the tight, expressive syntax of modern languages like Swift with the lightweight concurrency model of Go, leaving behind the runtime bloat.

To save you from the nightmare of modern package managers—where doing a pip install on a single helper utility somehow downloads half the internet and a bootleg copy of Doom—Skink comes with a rich, "batteries-included" standard library right out of the box.

As a Skinker (yes, that is our official title now), you get native access to:

  1. Automatic Reference Counting (ARC): Predictable, deterministic memory management without "stop-the-world" garbage collection pauses. This is a non-negotiable requirement when you are driving physical motors or processing high-frequency sensor telemetry.
  2. Lightweight Concurrency: A native spawn keyword that allows you to spin up millions of concurrent tasks with near-zero overhead, communicating cleanly via typed channels.
  3. The Rules Engine: A compiler-optimized reactive rules engine that lets you define declarative behavioral overrides that monitor variables in the background. It is perfect for saying, "I don't care what the neural net is thinking; if we are $15\text{ cm}$ away from a wall, hit the brakes."
  4. Native Tensors & ML Cores: Multi-dimensional arrays built directly into the type system via std/tensor, complete with matrix operations, activation functions, neural layers, and hooks for CUDA acceleration and llama.cpp (std/llm). DANI can run local inferences natively, at maximum speed, without needing a dedicated power plant or $16\text{ GB}$ of RAM.
  5. Model Context Protocol (MCP) & MQTT: Built-in support for turning any hardware endpoint into an instantly discoverable AI tool with SQLite (std/db) and edge-to-cloud messaging (std/mqtt) ready to go.

Under the Hood: Preventing a DANI Catastrophe

To see what "skinking" actually looks like, let's write a syntactically valid program based on the current Skink manual.

The following script concurrently polls a physical distance sensor via GPIO, pipes the measurements to our main execution block using channels, evaluates safety overrides in a background ruleset, and processes the inputs through a linear neural network layer to calculate motor outputs:

module main

import "std/gpio"
import "std/time"
import "std/tensor"

// 1. Declare global state variables (accessible by the ruleset)
var current_distance: float = 100.0
var motor_speed: float = 0.5

// 2. Define a reactive ruleset for safety overrides
ruleset SafetyOverride {
    rule collision_warning when current_distance < 15.0 {
        action: trigger_emergency_stop()
        priority: 1
    }
}

// Helper function called when the rule fires
fn trigger_emergency_stop() {
    motor_speed = 0.0
    print("SAFETY OVERRIDE: Obstacle detected! Distance: {current_distance}cm. Braking.")
}

// 3. Concurrently poll the hardware sensor in a background task
fn sensor_loop(ch: chan<float>) {
    err := gpio.Setup()
    if err.message != "" {
        print("Failed to initialize GPIO: " + err.message)
        return
    }

    // Bind to BCM Pin 17 (e.g., our sensor input pin)
    sensor_pin := gpio.PinFactory(17)
    sensor_pin.SetInput()

    while true {
        // Mocking a physical sensor read for this demo loop
        // In physical deployment, you'd calculate raw voltage pulses here
        measured := 12.5 
        ch <- measured
        time.SleepMs(10) // Poll at 100Hz
    }
}

// 4. Main Entry Point
fn main() -> int {
    sensor_chan := make(chan<float>)

    // Spawn our lightweight background polling loop
    spawn sensor_loop(sensor_chan)

    // Activate our safety ruleset background thread
    safety := SafetyOverride{}
    safety.start()
    defer safety.stop()

    // Initialize a linear neural network layer [input_features: 3, output_features: 1]
    layer := tensor.NewLinear(3, 1)

    // Run our control loop for 100 iterations
    for i := 0; i < 100; i = i + 1 {
        // Block until next sensor reading arrives
        current_distance = <-sensor_chan

        // If we are in a safe zone, let the tensor neural layer drive
        if current_distance >= 15.0 {
            input := tensor.Zeros([1, 3])
            input.Set([0, 0], current_distance)
           
            output := layer.Forward(input)
            motor_speed = output.Get([0, 0])
           
            print("Processing... Current motor velocity: {motor_speed}")
        }
    }
    return 0
}

Notice how neatly this handles the classic embedded AI problem. The concurrency model lets you poll hardware safely on separate execution threads without blocking the main loop, while the native ruleset watches the critical state variables and takes action within milliseconds if a threshold is breached—guaranteeing deterministic safety constraints before DANI can do any structural damage to the house.

Where Skink-lang Goes From Here

Currently, the compiler is bootstrapped in Go, using an LLVM backend to output highly optimized native machine binaries targeting both Linux and Windows corporate environments. Because there is no heavy runtime or garbage collector, a compiled Skink binary running an active inference loop can comfortably squeeze inside less than $128\text{ KB}$ of RAM.

But this is just the beginning. The roadmap ahead includes:

  • Direct Single Board Computer HAL: Expanding the standard library to map hardware registers and pins natively (such as on the Raspberry Pi 5) with zero external C-bindings.
  • Self-Hosting: Rewriting the Skink compiler entirely in Skink itself, proving the language's capabilities to handle massive, complex systems-level software natively.

Cullen the Skink
Cullen Skink

I’m incredibly excited about what this language makes possible. DANI is already running much cooler, much faster, and with a significantly lower skirting-board collision rate.

It is time to stop fighting the plumbing.

Let's get skinking!

Let me know your thoughts on the syntax, and what features you'd like to see added to the compiler next!


Tuesday, 31 March 2026

The Sledgehammer and the Hazelnut: Why DANI Isn't Using an LLM

I have a confession to make. I’ve broken my own rule.

When I set out to build and program DANI, one of my core principles was to ensure he learns primarily from experience. The goal has always been emergent behavior through his neural architecture, with as little "hard-coded" logic as possible. But as I’ve delved deeper into the complexities of human interaction, I’ve found one area where I feel a departure is justified: Natural Language Processing (NLP).

The LLM Dilemma

The LLM Sledgehammer
Early on, I toyed with the idea of giving DANI a dedicated board specifically to run a Large Language Model (LLM). It seemed like the modern solution, but the more I looked at it, the more the red flags started popping up.

First, there’s the cold, hard reality of the budget. I’ve managed to keep the total cost of DANI—parts, boards, and all—under £500. Adding a high-spec NPU or a secondary board capable of running something like Llama or Gemma would have blown that goal out of the water.

Then there’s the physical engineering. Space is at a premium inside DANI’s chassis. While I probably could have squeezed another board in there, I’m increasingly conscious of airflow. The last thing I want is for DANI’s "brain" to thermal throttle in the middle of a conversation.

But most importantly—and this was the dealbreaker—is the issue of personality. If I use a pre-trained model like Qwen or Llama, I’m essentially importing someone else’s bias and conversational style. These models are fine-tuned to be helpful assistants; I want DANI to be DANI. Using a massive, multi-billion parameter model just to parse a "hello" felt like using a sledgehammer to crack a hazelnut.

The Go-pher’s Path to Understanding

Instead of the LLM route, I’ve built a custom natural language module using standard NLP libraries for Go. It’s lightweight, it fits within our existing hardware constraints, and it gives me the control I need.

I’ve added two critical features that an off-the-shelf LLM wouldn't handle the way I want:

  1. Sentiment Assessment: By using established sentiment modules, DANI can now perceive whether he is being praised or scolded. This feeds directly into his hormone levels. If I’m happy with his performance and tell him he's done a good job, his "positive" hormones will rise, reinforcing that behaviour in his training.
  2. Simplified Context Awareness: I wanted conversations to feel natural. If I ask DANI, "Where is he?", he needs to understand that "he" refers to the last male person we discussed. Similarly, "Go there" should resolve "there" to the last location mentioned. This kind of stateful awareness is vital for a robot that exists in a physical space.

Commands, Questions, and the "Ignore" Factor

The module is now capable of distinguishing between commands, questions, and general statements. Each triggers a different internal processing path, but here is the kicker: everything is still influenced by his hormones.

Before DANI responds, every decision passes through his LSTM (Long Short-Term Memory) core. Because that LSTM is also being fed the current state of his effective hormones, there is no guarantee he will do what he's told. If he’s in a "bad mood" or his hormone levels are skewed by previous interactions, he might just decide to ignore me entirely.

It’s a bit of a gamble, breaking the "no-code" rule to build this framework, but I think it’s the only way to give DANI a voice that is truly his own. We’ll just have to wait and see if he actually listens to me.

Monday, 23 February 2026

Oops, I Gave My Robot Amnesia (And How I'm Fixing It)

Wow, it’s been a while. Apologies for the radio silence, but the pesky "real world" caught up with me, and I had to spend some time doing that whole "working for a living" thing.

Anyway, enough about the mundane. Let's get back to what is actually important: DANI.

As you might remember, my ultimate, beyond-my-wildest-dreams goal with this project is to cross that threshold and meet the definition of when a robot is actually alive, or at least close to it. But recently, while pondering DANI’s LSTM (the fancy Long Short-Term Memory neural network that acts as his brain), I realized I had made a fundamental—and slightly embarrassing—mistake.

It’s hard to achieve sentience when your robot has the memory retention of a goldfish.

The Problem: Scheduled Blackouts

As it stands right now, DANI "thinks" every 100 milliseconds, giving him 10 thought cycles a second. Every 10 seconds (100 cycles), backpropagation kicks in to train the network. To do this concurrently without stopping DANI in his tracks, I clone the LSTM at that exact moment, run the heavy backpropagation math on the clone, and then overwrite the active LSTM with the newly trained clone.

This backpropagation takes about 2 to 3 seconds. My initial thought was: Brilliant! The training happens in the background without interrupting his flow.

But there is a glaring flaw.

Because the process takes a snapshot, spends 3 seconds learning from it, and then violently overwrites the active brain... we lose those 2 to 3 seconds of short-term memory that DANI experienced while the training was happening. Every 10 seconds, DANI essentially blacks out and forgets the last few seconds of his existence. This is seriously hampering his learning capabilities.

How do we stop DANI from becoming a chronic amnesiac?

The Fix: A Neurological Hot-Swap

My solution is to ditch the cloning process entirely. Instead, each neuron will now have two sets of weights: one active, one inactive.

During backpropagation, the inactive weights will get the results of the calculation (using the active weights for the algorithm). This allows us to update the LSTM's underlying math without wiping out the actively evolving memory states (the cell states and hidden states) that DANI is currently using to understand the world. We just add a flag to each layer to indicate whether it should be reading from Weight Set 1 or Weight Set 2.

But wait, there’s more!

Reshaping the Brain

At present, DANI's model has about 300 neurons on each layer, with 5 layers (I don’t have the code right in front of me, so I'm relying on my own somewhat flawed, non-LSTM memory here).

If we increase the number of layers but reduce the neurons per layer, we can implement a rolling update. This means DANI can immediately benefit from the training layer-by-layer, even while the rest of the brain is still calculating.

What this entails is increasing the layer count to 7 (any higher and we start flirting with the dreaded vanishing gradient problem), but reducing the neuron count, per layer, to 128 (because who doesn't love a nice power of 2?).

This gives DANI a much more focused, "deep" thought process, allowing him to break down problems more efficiently. It also allows us to gracefully ‘flip the switch’ on each layer as we cycle through.

Here is how the rolling update will work:

As each feed-forward pass occurs (DANI thinking), a check is done to see if the next layer is ready to have its switch flipped to the newly trained weights. Because backpropagation is strictly sequential and works backwards, we start checking from the last layer and move towards the first.

If a layer is ready, we flip the weights to the newly trained set and mark it as done. On the next thought cycle, we check the next layer, and so on, until we reach the front of the brain. Then, we start the whole process over again.

What do we gain from this brain surgery?

Quite a bit, actually:

  1. Constant Learning: The LSTM is in a state of continuous, uninterrupted learning.
  2. Stable Learning Rate: No massive, sudden shifts in logic.
  3. Smoother Processing: No sudden CPU spikes from cloning and overwriting massive arrays.
  4. Deeper Thinking: The structural change to 7 layers gives DANI a more nuanced, layered way of processing information.
  5. Memory Retention: We actually retain the states of the memory gates within the LSTM. No more 3-second blackouts!

There are certainly other ways to create a continuous neural network, but I am aiming for the absolute simplest solution here. Remember, all of this is running on a Raspberry Pi!

This dual-weight method does increase the memory required to hold the LSTM, but because we are reducing the overall neuron count from ~1500 (5x300) to 896 (7x128), it's actually going to be lighter on the Pi overall. DANI had an oversized network anyway, so trimming the fat while adding depth is a win-win.

What do you guys think of this approach? Let me know in the comments if you see any potholes I'm about to step in!


Friday, 10 October 2025

3.5 Million Parameters and a Dream: DANI’s Cognitive Core

DANI’s Brain Is Online! Meet the LSTM That Thinks, Feels, and Remembers (Like a Champ)

Ladies and gentlemen, creators and dreamers—DANI has officially levelled up. He’s no longer just a bundle of sensors and hormones with a charming voice and a tendency to emotionally escalate when he sees a squirrel. He now has a brain. A real one. Well, a synthetic one. But it’s clever, emotional, and surprisingly good at remembering things. Meet his new cognitive core: the LSTM.

And yes—it’s all written in Go. Because if you’re going to build a synthetic mind, you might as well do it in a language that’s fast, clean, and built for concurrency. DANI’s brain doesn’t just think—it multitasks like a caffeinated octopus.

What’s an LSTM, and Why Is It Living in DANI’s Head?

LSTM stands for Long Short-Term Memory, which sounds like a contradiction until you realize it’s basically a neural network with a built-in diary, a forgetful uncle, and a very opinionated librarian. It’s designed to handle sequences—like remembering what just happened, what happened a while ago, and deciding whether any of it still matters.

Imagine DANI walking into a room. He sees a red ball, hears a dog bark, and feels a spike of adrenaline. A regular neural network might say, “Cool, red ball. Let’s chase it.” But an LSTM says, “Wait… last time I saw a red ball and heard barking, I got bumped into a wall. Maybe let’s not.”

Here’s how it works, in human-ish terms:

  • Input gate: Decides what new information to let in. Like a bouncer at a nightclub for thoughts.
  • Forget gate: Decides what old information to toss out. Like Marie Kondo for memory.
  • Output gate: Decides what to share with the rest of the brain. Like a PR manager for neurons.

These gates are controlled by tiny mathematical switches that learn over time what’s useful and what’s noise. The result? A brain that can remember patterns, anticipate outcomes, and adapt to emotional context—all without getting overwhelmed by the chaos of real-world data.

And because DANI’s LSTM is stacked—meaning multiple layers deep—it can learn complex, layered relationships. Not just “ball = chase,” but “ball + bark + adrenaline spike = maybe don’t chase unless serotonin is high.”

It’s like giving him a sense of narrative memory. He doesn’t just react—he remembers, feels, and learns.

What’s Feeding This Brain?

DANI’s LSTM is his main cognitive module—the part that thinks, plans, reacts, and occasionally dreams in metaphor. It takes in a rich cocktail of inputs:

  • Vision data: Objects, positions, shapes—what he sees.
  • Sensor data: Encoders, ultrasonic pings, bump sensors—what he feels.
  • Audio features: What he hears (and maybe mimics).
  • Emotional state: Dopamine, cortisol, serotonin, adrenaline—what he feels.
  • Spatial map: His mental layout of the world around him.
  • Short-term memory context: What just happened.
  • Associated long-term memories: Symbolic echoes from his main memory—what used to happen in similar situations.

This isn’t just reactive behaviour—it’s narrative cognition. DANI doesn’t just respond to stimuli; he builds a story from them. He’s learning to say, “Last time I saw a red ball and felt excited, I chased it. Let’s do that again.”

Trial by Raspberry Pi

We’ve successfully trialled DANI’s LSTM on a Raspberry Pi, running a 3.5 million parameter model. And guess what? It only used a quarter of the Pi’s CPU and 400 MB of memory. That’s like teaching Shakespeare to a potato and watching it recite sonnets without breaking a sweat.

We’ve throttled the inference rate to 10 decisions per second—not because he can’t go faster, but because we want him to think, not twitch. Emotional processing takes time, and we’re not building a caffeine-fuelled chatbot. We’re building a thoughtful, emotionally resonant robot who dreams in symbols and learns from experience.

Learning Without Losing His Mind

Training happens via reinforcement learning—DANI tries things, gets feedback, and adjusts. But here’s the clever bit: training is asynchronous. That means he can keep thinking, moving, and emoting while his brain quietly updates in the background. No interruptions. No existential hiccups mid-sentence.

And yes, we save the model periodically—because nothing kills a good mood like a power cut and a wiped memory. DANI’s brain is backed up like a paranoid novelist with a USB stick in every pocket.

Final Thoughts

This LSTM isn’t just a brain—it’s a story engine. It’s the part of DANI that turns raw data into decisions, decisions into memories, and memories into dreams. It’s the bridge between his sensors and his soul (okay, simulated soul). And it’s just getting started.

Next up: I plan to start the even more monumental task of getting the vector database working and linked up to DANI's brain in such a way that it will have a direct impact of DANI's hormonal system.

Stay tuned. DANI’s mind is waking up.

Thursday, 18 September 2025

The Go-pher's Guide to Messaging Simplicity (Why I Wrote Nexus)

I've had to take a brief step back from working on DANI recently as I needed to tackle some challenges for work projects. So, instead of leaving you all hanging, I thought I would give you an update on something I've been working on in my "day job" that I thought was an interesting side project.

In today's fast-paced digital world, connecting different services and applications is a challenge. Many organizations rely on complex, costly messaging platforms that introduce significant operational overhead and vendor lock-in. What if there was a better way? A messaging solution that was secure, easy to deploy, and gave you full control?


Welcome to Nexus, a secure, observable, and extensible event distribution platform designed to connect publishers and subscribers with minimal operational overhead. Built with simplicity and control at its core, Nexus is a compelling alternative to traditional cloud-managed services and heavyweight message brokers.

Key Value Propositions

Nexus stands out by focusing on a few core principles that deliver immense value:

  • Operational Simplicity: Nexus is a single Go binary with zero external dependencies by default. This simplifies deployment, which can be done in hours rather than weeks or months.

  • Security-First Design: It uses RSA-based authentication, constant-time cryptographic comparisons, and replay attack protection. Metrics are also protected with an optional token.

  • Observability: With built-in health checks (/healthz, /readyz) and Prometheus-compatible metrics, Nexus provides the necessary tools for monitoring and debugging.

  • Cost Control and Portability: Nexus offers a predictable infrastructure cost, a significant advantage over the pay-per-use model of many cloud services that can become expensive at scale. It is also cross-platform, working identically on Linux, Windows, and macOS.

Nexus vs. the Alternatives

From the work I've done, I've had the chance to see how Nexus stacks up against other systems.

  • Compared to Cloud-Managed Services (AWS SNS/SQS, GCP Pub/Sub): While these services offer massive global scalability and immediate access, they come with high vendor lock-in and can get pretty pricey at scale. With Nexus, you get full control over your deployment and data, a predictable cost structure, and zero vendor lock-in as it is open-source.

  • Compared to Enterprise Message Brokers (Apache Kafka, RabbitMQ): These platforms are powerful but have a steep learning curve and high operational complexity. Deploying a complex cluster can take weeks to months. Nexus, with its single-binary deployment and low operational complexity, offers a gentle learning curve and a time-to-market measured in hours.

Behind the Scenes: Technical Highlights

Nexus is a scalable pub/sub messaging system with both HTTP and WebSocket interfaces. It uses a pluggable persistence layer (SQLite by default) to store client and message state.

For high availability and scale, Nexus nodes can be run in a multi-node cluster with a load balancer. Each node maintains its own local database and synchronizes client registry changes with peer nodes through a lightweight cluster sync endpoint. This architecture provides graceful degradation and error handling.

Effortless Management with Built-in Tooling

I'm a big believer in good tooling, and Nexus comes with a suite of command-line tools to simplify common tasks:

  • nexus-add-client: To provision a new client.

  • nexus-add-cluster: To register a new node with the cluster.

  • nexus-list-clients and nexus-list-clusters: To view existing clients and nodes.

  • nexus-serve: To start the Nexus service.

These tools, combined with comprehensive documentation, make managing your Nexus deployment straightforward.

Conclusion

Nexus offers a pragmatic approach to event distribution, balancing simplicity with enterprise-grade features. It is a solid foundation for reliable messaging systems without the complexity overhead of larger platforms. For organizations seeking a middle ground between custom solutions and heavyweight message brokers, Nexus provides a compelling combination of features, security, and operational simplicity.

It’s been an interesting journey, and now that I've gotten this out there, maybe I can get back to DANI's digital hormones and see what kind of wacky emotions he's developed.

As always, feel free to leave a comment.

Friday, 12 September 2025

Vectorizing Memory

Hello, fellow explorers of the digital frontier!


You know how it is when you're building an AI, especially one destined for the real world, embodied in a robot head (and maybe a mobile platform, wink wink)? You need a brain, and that brain needs a memory. But not just any memory – it needs a memory that understands meaning, not just keywords. And that, my friends, is where the humble, yet mighty, Vector Database comes into play.

For those of you following my DANI project, you'll know I'm all about pushing intelligence to the edge, directly onto Single Board Computers (SBCs) like our beloved Raspberry Pis. This week, I want to dive into why vector databases are absolutely crucial for this vision, and how I'm tackling the challenge of making them lightweight enough for our resource-constrained little friends.

What in the World is a Vector Database, Anyway?

Forget your traditional spreadsheets and relational tables for a moment. A vector database is a special kind of database built from the ground up to store, index, and query vector embeddings efficiently. Think of these embeddings as multi-dimensional numerical representations of anything unstructured: text, images, audio, even your cat's purr. The magic? Semantically similar items are positioned closer to each other in this high-dimensional space.

Unlike a traditional database that looks for exact matches (like finding "apple" in a list), a vector database looks for similar meanings (like finding "fruit" when you search for "apple"). This is absolutely foundational for modern AI, especially with the rise of Large Language Models (LLMs). Vector databases give LLMs a "memory" beyond their training data, allowing them to pull in real-time or proprietary information to avoid those pesky "hallucinations" and give us truly relevant answers.

The process involves: Embedding (turning your data into a vector using an AI model), Indexing (organizing these vectors for fast searching, often using clever Approximate Nearest Neighbor (ANN) algorithms like HNSW or IVF), and Querying (finding the "closest" vectors using metrics like Cosine Similarity). It's all about finding the semantic buddies in a vast sea of data!

SBCs: The Tiny Titans of the Edge

Now, here's the rub. While big cloud servers can throw endless CPU and RAM at vector databases, our beloved SBCs (like the Raspberry Pi) are a bit more... frugal. They have limited CPU power, often less RAM than your phone, and slower storage (those pesky microSD cards!). This creates what I call the "Accuracy-Speed-Memory Trilemma." You can have two, but rarely all three, without some serious wizardry.

For my DANI project, the goal is to have intelligence on the device, reducing reliance on constant cloud connectivity. This means our vector database needs to be incredibly lightweight and efficient. Running a full-blown client-server database daemon just isn't going to cut it.

My Go-To for Go: github.com/trustingasc/vector-db

This is where the Go ecosystem shines for embedded systems. While there are powerful vector databases like Milvus or Qdrant, their full versions are too heavy. What we need is an embedded solution – something that runs as a library within our application's process, cutting out all that pesky network latency and inter-process communication overhead.

My current favourite for this is github.com/trustingasc/vector-db. It's a pure Go-native package designed for efficient similarity search. It supports common distance measures like Cosine Similarity (perfect for semantic search!) and aims for logarithmic time search performance. Being Go-native means seamless integration and leveraging Go's fantastic concurrency model.

Here's a simplified peek at how we'd get it going in Go (no calculus required, I promise!):


package main

import (
  "fmt"
  "log"
  "github.com/trustingasc/vector-db/pkg/index"
)

func main() {
  numberOfDimensions := 2 // Keep it simple for now!
  distanceMeasure := index.NewCosineDistanceMeasure()
  vecDB, err := index.NewVectorIndex[string](2, numberOfDimensions, 
    5, nil, distanceMeasure)
  if err != nil { log.Fatalf("Failed to init DB: %v", err) }
  fmt.Println("Vector database initialized!")
  // Add some data points (your AI's memories!)
  vecDB.AddDataPoint(index.NewDataPoint("hello", []float64{0.1, 0.9}))
  vecDB.AddDataPoint(index.NewDataPoint("world", []float64{0.05, 0.85}))
  vecDB.Build()
  fmt.Println("Index built!")
  // Now, search for similar memories!
  queryVector := []float64{0.12, 0.92}
  results, err := vecDB.SearchByVector(queryVector, 1, 1.0)
  if err != nil { log.Fatalf("Search error: %v", err) }
  for _, res := range *results {
    fmt.Printf("Found: %s (Distance: %.4f)\n", res.ID, res.Distance)
  }
}


This little snippet shows the core operations: initializing the database, adding your AI's "memories" (vector embeddings), and then searching for the most similar ones. Simple, elegant, and perfect for keeping DANI's brain sharp!

Optimizing for Tiny Brains: The Trilemma is Real!

The "Accuracy-Speed-Memory Trilemma" is our constant companion on SBCs. We can't just pick the fastest or most accurate index; we have to pick one that fits. This often means making strategic compromises:

Indexing Algorithms: While HNSW is great for speed and recall, it's a memory hog. For truly constrained environments, techniques like Product Quantization (PQ) are game-changers. They compress vectors into smaller codes, drastically reducing memory usage, even if it means a tiny trade-off in accuracy. It's about getting the most bang for our limited memory buck!

Memory Management: Beyond compression, we're looking at things like careful in-memory caching for "hot" data and reducing dimensionality (e.g., using PCA) to make vectors smaller. Every byte counts!

Data Persistence: MicroSD cards are convenient, but they're slow and have limited write endurance. For embedded Go libraries, this means carefully serializing our index or raw data to disk and loading it on startup. We want to avoid constant writes that could wear out our precious storage.

It's a constant dance between performance and practicality, ensuring DANI can learn and remember without needing a supercomputer in its head.

The Road Ahead: Intelligent Edge and DANI's Future

Vector databases are more than just a cool piece of tech; they're foundational for the kind of intelligent, autonomous edge applications I'm building with DANI. By enabling local vector generation and similarity search, we can power real-time, context-aware AI without constant reliance on the cloud. Imagine DANI performing on-device anomaly detection, localized recommendations, or processing commands without a hiccup, even if the internet decides to take a nap!

This journey is all about pushing the boundaries of what's possible with limited resources, making AI smarter and more independent. It's challenging, exciting, and occasionally involves me talking to a Raspberry Pi as if it understands me (it probably does, actually).

What are your thoughts on running advanced AI components on tiny machines? Have you dabbled in vector databases or edge computing? Let me know in the comments below!

Friday, 11 April 2025

Diving Deep into Efficient Messaging Systems: My Journey with Polestar

I thought it was time to share some insights into a key part of a project I’ve been working on. It's not the whole codebase – I wouldn't want to bore you to tears! But I do want to talk about some of the core concepts I've implemented.   


One of the critical requirements of this project was building a messaging system capable of handling a potentially massive throughput of messages and ensuring they reach their intended destinations efficiently.   


Now, I could have gone with off-the-shelf solutions like ROS (Robot Operating System). However, I'm a bit of a control freak and enjoy crafting things from the ground up.   


That's how Polestar was born.   

Polestar


Polestar is a custom library designed to handle messages composed of maps (or dictionaries) containing primitive data types. Think strings, integers, floats, and booleans. These messages are published to Polestar with a specific topic, and any application subscribed to that topic receives a copy.   


My initial attempt at building this system was decent enough, achieving a throughput of about 800 messages per second. But I started thinking about how I could push the boundaries, enhance the throughput, and make the system even more robust.   


And guess what? I did it! I managed to crank up the throughput to an impressive 16,000 messages per second. That's more than sufficient for any scenario I can currently envision.   


To maintain efficiency, if the message queue is full when a new message arrives, the message is dropped to prevent blocking the processes.  Considering the queue's substantial capacity of 1,000,000 messages, this scenario should be quite rare.   


The Queue Conundrum


But here's where it gets interesting.  Recently, I started pondering: what if, instead of dropping the newest message when the queue is full, we dropped the oldest message?  How difficult would that be to implement?    


Go's channels, in their default state, don't offer this specific behavior. However, as is often the case in programming, there are multiple ways to achieve it.   


One approach involves creating a struct that encapsulates a queue (as a slice) and uses a single channel. But this felt like overkill for such a small feature. Plus, I'd lose the inherent speed advantages of Go's channels.   


So, I devised what I believe is a more elegant solution. It leverages the fundamental nature of channels and preserves the ability to iterate over them in the standard way.   


Go's flexibility allows you to create a new type based on an existing type, even a primitive one. In this case, I created a new type called ch based on a channel of strings:   


type ch chan string


This opens the door to using Go's method functionality to add a custom behavior to our new type.  I created a Send method with the following logic:   


// Send attempts to send a message to the channel.

// If the channel is full,

// it drops the oldest message and tries again.

// Returns a boolean indicating

// whether a message was dropped (true) and an error if the operation failed.

// The error is non-nil only if the channel remains full after attempting to

// drop the oldest message.

func (c ch) Send(msg string) (bool, error) {

  select {

  case c <- msg:

    return false, nil

  default:

    // Channel is full, drop the oldest and try again

    <-c // Discard oldest

    select {

    case c <- msg:

      // Message sent after dropping oldest

      return true, nil

    default:

      //This should rarely, if ever, happen.

      //Handle error/log message.

      return true, errors.New("Error: Channel still full after dropping oldest.")

    }

  }

}

This Send method replaces the typical channel send operation:


chVar <- “hello”


with:


chVar.Send(“hello”)


The beauty of this is that if you've created a buffered channel, the oldest item in the queue is dropped when the queue is full. This can be incredibly useful in scenarios like robotics, where outdated messages might lose their relevance, and prioritizing the latest information is crucial.   


I haven't integrated this into Polestar just yet. I'm still weighing the pros and cons of dropping the newest versus the oldest message.  Ideally, of course, no messages would be dropped at all.   


To give you a glimpse of Polestar's speed, here's a short video of one of the test runs:




My original plan involved using a hardware hub for this project. However, I don't believe I could have achieved this level of performance with a microcontroller (MCU), especially considering the queue size.  Polestar's heavy use of concurrency would also pose a challenge for microcontrollers.   


The trade-off is that all communication now relies on TCP instead of serial. Serial communication might have offered faster data transmission with less overhead, but the routing complexities would have been a significant hurdle.   


I hope this deep dive into my process provides some food for thought, especially for fellow developers. And for those who aren't knee-deep in code, I hope it offers a little peek into how my mind works.   


I welcome any comments or questions you might have. Please feel free to leave them in the comments section below!    

Sunday, 23 February 2025

My Journey into Independent AI and Robotics: A Personal Exploration

 Welcome to this space where I'll be documenting my ongoing exploration into the fascinating world of artificial intelligence and robotics. My aim is to provide you with clear, accessible insights into my projects and the challenges I encounter, all while keeping a grounded perspective on the current state of these rapidly evolving fields. I'll do my best to avoid getting bogged down in overly technical jargon, but some terminology is inevitable. Similarly, I'm committed to presenting a realistic view, steering clear of the sensationalism that often surrounds discussions about AI and robotics.


You might be wondering, with so many existing blogs and resources dedicated to these topics, why start another one? It's a fair question. Many of those blogs are written by individuals with far more formal qualifications and experience than I possess. So, what makes this one different? What can I offer that you won't find elsewhere?


To begin, let's address the elephant in the room: I'm not a scientist, nor am I a professional or student in the fields of AI or robotics. My formal education in these areas is limited. In fact, my academic journey was brief, lasting only a few months when I attempted college in my thirties. It simply wasn't the right fit for me.


However, what I do possess is a long history of software development. I began teaching myself to code at the age of 11, back in 1983. Since then, I've written software on a wide range of topics, in various programming languages, primarily for my own personal projects. Professionally, I've worked on enterprise resource planning (ERP) software, physical security systems, and numerous database and web-based applications. Personally, I've developed tools, games, databases, open-source libraries, and just about anything else that piqued my interest.


This extensive, self-taught background provides me with a unique perspective. My lack of formal training has often compelled me to find innovative and unconventional ways to solve problems. This ability to think outside the box is what I hope to bring to my exploration of AI and robotics.


Furthermore, I am neuro-divergent, specifically on the autistic spectrum. While this realization was initially a surprise, I've come to recognize it as a significant asset. My cognitive style allows me to perceive patterns and connections that might be overlooked by others. It's like having a different lens through which I view the world, enabling me to identify novel solutions and approaches.


This is a solo endeavor. I don't have a team of researchers or engineers working alongside me. It's just me, my computers, and my two 3D printers. However, I do have a valuable partner in the form of AI. I use AI as a sounding board, a research assistant, and a collaborator. If I need to quickly understand the capabilities of a component, I ask AI. If I need to assess the feasibility of a concept, I ask AI. If I need to ensure that this blog post is readable and engaging, I ask AI.


Another significant differentiator is my budget. While companies like Google, Tesla, and Unitree have access to millions or billions of dollars in funding, I'm working with a very limited budget. I'm funding this project myself, and my available capital is minimal. This constraint presents a unique challenge, forcing me to be resourceful and creative. To keep research and development costs down, I'll be relying heavily on off-the-shelf components and open-source software. I don't want to reinvent the wheel; instead, I aim to find new and innovative ways to combine existing technologies, perhaps adding my own unique contributions along the way.


I believe I have some promising ideas and concepts, and I'm eager to bring my unique perspective to life. However, I also recognize that success is not guaranteed. We'll see where this journey takes us.


I won't be detailing any specific project plans in this initial post. I prefer to reveal each aspect of my work as it develops, allowing you to follow along in real time.


Like many people of my generation, my fascination with robots began with the release of "Star Wars" in 1977. The character of R2-D2, in particular, captured my imagination. Decades later, in 2020, I acquired an Anki Vector. This small, interactive robot provided a tangible experience with personal robotics. While Vector is an impressive piece of technology, it lacked a certain spark, a quality that I couldn't quite define.


This led me to explore other consumer-level robotics solutions, including kits from companies like Adeept, Freenove, and Sunfounder. These kits provided valuable hands-on experience with single-board computers and microcontrollers, and I began designing my own robotic projects. However, these projects still fell short of my vision.


I realized that the key to unlocking the potential of these robots lay in their intelligence, specifically in the field of deep learning. I began to delve into the intricacies of neural networks, machine learning algorithms, and the ways in which AI can enable robots to learn and adapt. The prospect of creating robots that could not only perform tasks but also understand and interact with their environment on a deeper level was incredibly compelling.


A Note on Coding Choices


For the coding aspects of this project, I'll primarily be using Go and C++. I have a strong preference for compiled languages, and while Python is a popular choice in the AI and robotics community, I find its interpreted nature less appealing. I understand that using Go might limit my access to some readily available libraries, particularly in the machine learning domain, but I see this as a stimulating challenge. Adapting and potentially creating my own solutions is part of the exploration. C++ will come into play where performance is critical, and for low level hardware interaction.


I plan to post updates on this blog once a week, providing insights into my progress, challenges, and discoveries. However, please understand that there may be occasional delays. If I'm deeply immersed in a project or if there's nothing substantial to report, I may skip a week. I also intend to share videos whenever I have something visual to demonstrate.


This blog is intended to document my personal exploration of AI and robotics. It's not intended to replace formal scientific research. I invite you to follow along as I embark on this journey of discovery and innovation. And please, feel free to comment with your suggestions, ideas, or even just to let me know what you think. Your input is always welcome!

Aiming for Jarvis, Creating D.A.N.I.