I did not wake up one morning and think, “You know what the world needs? Another agent framework.” I am not that spiritually damaged. I wanted something personal. Something sharp. Something that matched the way I build systems, not the way a conference demo wants me to clap.
Every Agent Library Has Its Own Religion
After you spend enough time around agent libraries, you notice something uncomfortable. They all have opinions. Loud ones. Some want everything to be a graph. Some want everything to be a chain. Some want your entire application to become a shrine where JSON schemas are offered daily to the transformer gods.
And honestly, fine. Different libraries solve different pain. Different people have different scars. One engineer wants orchestration. Another wants memory. Another wants browser automation because apparently we are all one bad sprint away from rebuilding Selenium with vibes.
But I wanted my own thing. Not because I believe every developer should build a framework, although apparently that is how we summon seniority now. I wanted a set of small Elixir libraries that treat agents like production systems, not magical interns with API keys.
This article is the first piece of that series. The Spectre series. It starts with Spectre Kinetic, because before an agent remembers anything, browses anything, or pretends to reason about your business logic, it has to face the oldest and dumbest question in agent land: how should it use tools?
The MCP Decision Was Bad Architecture Wearing a Nice Jacket
Let’s say the rude part softly, like adults. Creating MCP as the default mental model for agent tools was, in many cases, a bad decision. Not evil. Not useless. Just architecturally awkward in the way only software can be awkward: very confident, very documented, and somehow still making your simple app feel like it needs diplomatic immunity.
MCP makes sense when you need an external protocol. Remote tools. Shared integrations. A standard bridge between systems that do not know each other. Great. Have fun. Bring snacks. But using it inside your own application, where your tools already live, where your runtime already knows permissions, types, modules, retries, supervision, and consequences? That starts to feel like ordering a shipping container to move a sandwich across the table.
The strange part is that we looked at language models, these beautiful expensive engines for generating and interpreting human-ish text, and decided the sacred interface should be more ceremony. More schemas. More transport. More protocol. More moving pieces to explain to the model before it can do the one thing it is actually good at.
The model should not hold the keys. It should fill out the request form.
That is the whole wound. We keep trying to turn the model into the runtime. We make it select tools, obey schemas, satisfy validation, remember policies, understand side effects, and somehow never hallucinate a field name while running on vibes and compressed context. Sounds great, right? Here’s the catch: production does not care about vibes.
Let the Agent Do the Thing It Is Best At
A language model is good at language. This should not be controversial, but somehow it is. It can turn messy user intent into a compact expression. It can read “tell ops the deploy failed and include the log link” and produce something close to “send mail to ops with this message.” That is useful. That is natural. That is the part where the model earns its GPU bill.
What it should not do is become your execution environment. It should not be trusted to decide whether the email module exists, whether the recipient is allowed, whether the body is complete, whether the action is safe, whether the request needs confirmation, or whether your database should be politely dropped because the prompt had a spicy afternoon.
In theory, you give the agent tools and it uses them. In reality, every tool call is a tiny production demon. It has intent, arguments, missing fields, policy risk, confidence, side effects, and consequences. A tool call is not an action. It is an intention wearing JSON, or in this case, an intention wearing a smaller action language because cursed JSON has already taken enough from us.
So the better split is simple. Let the model generate English-like action intent. Let Elixir inspect that intent. Let the runtime map it to real functions. Let your application decide what is allowed to happen. The model proposes. The runtime disposes. OTP supervises. Everybody sleeps better, or at least fails with logs.
That Is Where Spectre Kinetic Enters
Spectre Kinetic is not trying to be the agent. That is important. It does not want to orchestrate your life, invent workflows, execute side effects, or become a tiny god inside your Phoenix app. It sits between model intent and Elixir execution, which is exactly where this kind of library should sit.
You define tools in Elixir. Real modules. Real functions. Real parameter names. Real typespecs. Then Kinetic extracts that tool knowledge into a registry. The model does not need to carry a giant live catalog of schemas in its context window like some cursed backpack full of bureaucracy. It can emit compact Action Language instead, and Kinetic can map that language to the function your app already owns.
Here is the part that made me grin like an engineer who just deleted 400 lines of integration glue: you can teach the tool through normal Elixir documentation. The function stays a function. The examples live near the function. The agent learns the shape of the action from the same place a human would look.
defmodule MyApp.Emailer do
use SpectreKinetic
@al ~s(SEND EMAIL TO=email@gmail.com BODY=text)
@doc """
Send an email to a recipient.
AL: SEND EMAIL TO="dev@example.com" BODY="hello"
AL: SEND MAIL TO="ops@example.com" BODY="pager"
"""
@spec send(email :: String.t(), text :: String.t()) :: {:ok, String.t()} | {:error, term()}
def send(email, text) do
{:ok, "#{email}:#{text}"}
end
end
Yes. That is the nice feature. Not “nice” as in cute. Nice as in finally respecting the shape of the codebase. The action examples are not floating in some external prompt swamp. They sit beside the function they describe. The function has a spec. The docs contain real Action Language examples. Kinetic can read that and build planning knowledge from the thing you already maintain.
For example, the agent says something like “send mail to ops@example.com body pager.” Kinetic can plan that into a structured action candidate pointing at your email function, with mapped arguments, status, scores, warnings, and missing fields when something is not complete. Then it stops. Beautifully. Deliberately. It does not execute the function, because execution belongs to your app, where permissions, transactions, retries, audit logs, and human regret already live.
This is the sacred boundary. Imagination on one side. Consequences on the other.
The Runtime Should Be Calm Even When the Model Is Dramatic
The best thing about Spectre Kinetic is not that it makes agents more powerful. Power is cheap. Give a raccoon root access and technically you have created a powerful system. The useful thing is that Kinetic makes the boundary visible. It turns vague model intent into something your Elixir application can inspect like a civilized object.
That means you can score tool matches. You can map slots. You can detect missing arguments. You can add classifier plugs for confidence, slot quality, safety, or custom policy. You can say “this plan looks fine,” “this needs clarification,” or “absolutely not, little poet, we are not deleting anything today.”
This is why I started building Spectre as a series of libraries instead of buying fully into one grand agent religion. Agents need different organs. Planning is not memory. Memory is not perception. Perception is not execution. When a framework blends all of that into one glowing abstraction, it feels convenient for twelve minutes, then you are debugging a haunted vending machine at 2:17 AM.
Spectre Kinetic is the planning organ. Small, local, Elixir-first, and suspicious of drama. The agent gets to be creative. Elixir gets to be boring. And boring, in production, is not an insult. Boring is the monk state. Boring is the system returning the same result twice. Boring is the difference between an agent that helps you ship and an agent that confidently invents a function called “send_urgent_business_magic” because the prompt had emotional momentum.
Send via.chat
Receive form leads, send login codes, and route important alerts through WhatsApp or Telegram.
Get in Touch
Have a question or want to work together? Drop a message below.