<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Petko D. Petkov</title>
    <description>The latest articles on DEV Community by Petko D. Petkov (@pdparchitect).</description>
    <link>https://dev.to/pdparchitect</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3871546%2F4330b8ec-4a06-4342-9f7a-cf6dd1635e12.jpg</url>
      <title>DEV Community: Petko D. Petkov</title>
      <link>https://dev.to/pdparchitect</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pdparchitect"/>
    <language>en</language>
    <item>
      <title>How to Build for AI Agents</title>
      <dc:creator>Petko D. Petkov</dc:creator>
      <pubDate>Thu, 16 Jul 2026 11:43:21 +0000</pubDate>
      <link>https://dev.to/pdparchitect/how-to-build-for-ai-agents-35f</link>
      <guid>https://dev.to/pdparchitect/how-to-build-for-ai-agents-35f</guid>
      <description>&lt;p&gt;Traditionally APIs were designed for a browser or a client library. Something on the other end was going to parse the response and turn it into a screen or an object. However, the thing calling your API now is a model in a loop, with a token budget and a terminal, and it does not render your JSON anymore. Instead, it reads it.&lt;/p&gt;

&lt;p&gt;I have argued before that you should &lt;a href="https://chatbotkit.com/reflections/build-for-who-is-not-human" rel="noopener noreferrer"&gt;build for who is not human&lt;/a&gt;. That was the why. This is the how, and it lands almost entirely on your API, because the API is the only surface an agent ever touches.&lt;/p&gt;

&lt;p&gt;Most of what follows I learned the slow way, building &lt;a href="https://github.com/crmkit/crmkit" rel="noopener noreferrer"&gt;crmkit&lt;/a&gt;, a deliberately experimental CRM designed for agents to drive. It is headless, there is no UI at all, and the agent is the only user it has. That turns out to be a good teacher, because there is nowhere to hide a bad decision behind a screen. We run it internally on a few projects. It is not perfect, but I think it is pointing the right way.&lt;/p&gt;

&lt;p&gt;Start with the format. &lt;strong&gt;Text is the default interface.&lt;/strong&gt; Not JSON. JSON is a serialisation format for programs that are going to parse it, and an agent is not parsing anything. It is reading. Every brace, quote, repeated key name is a token you charge the agent for on every single response, and none of it carries meaning to the reader. Return text first, and let structured output be the fallback through the &lt;code&gt;accept&lt;/code&gt; header. There is nothing wrong with adding other ways in either, like &lt;code&gt;?format=json&lt;/code&gt;, when a real program is on the other end. Just stop making that the default, because it is not going to be the common case in the future.&lt;/p&gt;

&lt;p&gt;Then &lt;strong&gt;make it grepable.&lt;/strong&gt; Surface as much useful information in a single line as you can. The agent's first instinct with your output is to pipe it into grep, because that is the environment it lives in. A record spread across fifteen lines of pretty printed JSON cannot be sliced. The same record on one line can be filtered, counted, cut and sorted, without a round trip back to you or another pass through the model. In crmkit a contact comes back like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contact_k7m2q  name="Jane Doe"  email=jane@acme.com  stage=lead  updated=2026-06-04T09:13Z
# 1 contact(s)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One labeled line per record, and anything that is not a record rides on a comment line the agent can read or ignore. Pagination is the same trick, a &lt;code&gt;# next: &amp;lt;cursor&amp;gt;&lt;/code&gt; at the end of the page. One line per thing is not an aesthetic preference. Not at all. It is what makes your output composable with everything else the agent already knows how to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identifiers must be short.&lt;/strong&gt; Short enough to describe in five tokens, but not so short that you introduce collisions or confusion. A UUID is thirty six characters of noise that the agent carries through every subsequent call, and you pay for it every time. Prefixes help a lot here, like &lt;code&gt;cus_abc123&lt;/code&gt; or &lt;code&gt;token_xxx&lt;/code&gt;. The prefix does double duty. It tells the agent what kind of thing it is holding even when the identifier shows up with no context around it, and it stops it from handing you a customer id where a token belongs. Stripe worked this out for humans long before agents existed. It turns out the reasoning holds even better for a reader that pays by the token. One thing I would add from practice, take the identifier back in both forms. crmkit accepts &lt;code&gt;GET /contacts/k7m2q&lt;/code&gt; and &lt;code&gt;GET /contacts/contact_k7m2q&lt;/code&gt; and treats them as the same thing, because an agent will guess, and being right about the record while wrong about the punctuation is not a mistake worth a round trip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Surface what is likely to be needed next.&lt;/strong&gt; If the agent asks for a list of resources, do not just return the list. Return the information around those resources that helps it understand what it is looking at. Without this a single question turns into a fan out of ten follow up calls, each one a round trip, each one more tokens and more latency, and every one of them was predictable. You know what the next question is going to be. Answer it now, in the same response. Every contact line in crmkit carries &lt;code&gt;activities=N&lt;/code&gt; and &lt;code&gt;last_activity&lt;/code&gt; for exactly this reason. The agent can tell a live record from a dormant one by reading the list it already has, instead of fetching each row to find out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add bulk operations.&lt;/strong&gt; Inserting ten records in one request is a lot easier than performing ten separate requests. For a human client that is a nice optimisation. For an agent it is the difference between one decision and ten, because the model has to think between every call, and thinking is the expensive. This is the same instinct I wrote about in &lt;a href="https://chatbotkit.com/reflections/compress-dont-expand" rel="noopener noreferrer"&gt;compress, don't expand&lt;/a&gt;. Give the agent one call that does the whole job instead of ten that each do a tenth of it.&lt;/p&gt;

&lt;p&gt;The half of this I underrated is bulk on the way out. Reads fan out just as badly as writes, and nobody thinks to fix it because on a screen you would have paginated the problem away. crmkit takes a list of handles on the activity endpoint, so an agent holding a page of companies pulls the history for all of them in one call and groups the result locally. It is the N+1 problem, except every extra query also costs a thinking step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error messages should be descriptive.&lt;/strong&gt; An error is not a status code. When a human hits a 400 they go and search for it, read a forum thread, find the manual. An agent cannot do any of that. It has exactly what you handed it and nothing else. So point it at the actual docs and manuals it can go and read, and return hints about what to do differently. Every error in crmkit is two lines, an &lt;code&gt;ERROR&lt;/code&gt; code and a &lt;code&gt;HINT&lt;/code&gt; that names the next move. Ask for a record that is not there and it says to list them first and gives you the endpoint. Filter on a field that does not exist and it prints the fields that do, so the agent is never guessing at a vocabulary you could have just handed it.&lt;/p&gt;

&lt;p&gt;The part I only learned by watching it fail is that the hint has to say whether to try again at all. An agent's default response to a wall is another attempt, and some walls do not move. When crmkit rejects a write because the plan is full it says so in words and tells the agent to stop and talk to a human, because no amount of retrying is going to buy more room. A retryable error and a terminal one that look alike will burn a budget in a loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ship the manual inside the API.&lt;/strong&gt; Not a docs site, not a portal with a search box, an endpoint. crmkit answers &lt;code&gt;GET /help&lt;/code&gt; with its whole operating manual on one page, written for the agent rather than for a developer evaluating us, with a copy at &lt;code&gt;/.well-known/agent.md&lt;/code&gt; for anything that comes looking. It opens with a few lines of YAML saying what the service is, what it can do and how auth works, so a machine can decide whether to keep reading before it spends tokens on the prose.&lt;/p&gt;

&lt;p&gt;The consequence took me a while to appreciate. The way into crmkit is a single URL. You paste one line into an agent, read &lt;code&gt;https://api.crmkit.ai/start&lt;/code&gt; and follow it, and it signs the user in, puts a real contact in and sets a reminder, with nobody touching a UI. That page is not documentation. It is written in the imperative, as instructions to carry out rather than reference to explain. No SDK to publish, no client library to keep in sync, no onboarding funnel to maintain. Distribution collapses into a link, and the manual becomes the product surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assume every write happens twice.&lt;/strong&gt; An agent retries. It times out and tries again, loses the thread, starts over, or just decides it is not sure the first one landed. So make the writes safe to repeat. Posting a contact to crmkit upserts on the email address, so sending the same person twice updates them instead of growing a second copy, and the response says plainly whether it created or updated. Attaching something that is already attached is a free no-op rather than an error. Idempotency is not an advanced feature when your caller is a probabilistic process with a timeout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assume it is not the only one.&lt;/strong&gt; Several agents work the same records at the same time, and so do the people around them. Every record in crmkit carries a version. Send it back with your update and a stale write is rejected instead of quietly flattening what another agent just did. Leave it out and last write wins, but at least you chose that. It also stamps every row with who wrote it, agent or human, and keeps the field level diff, because "who changed this, and to what" stops being a rhetorical question the moment a fleet is working your data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate what cannot be undone.&lt;/strong&gt; A delete in crmkit does not delete. The first call comes back asking for confirmation and hands over a token, and the agent has to go and ask a human before repeating the call with that token attached. Promoting someone to admin or dropping a workspace emails a code that has to come back. The check lives in the API, not in the agent's judgement and not in a stern sentence in a prompt. This is the honest version of what I meant by &lt;a href="https://chatbotkit.com/reflections/human-in-the-loop-just-not-like-this" rel="noopener noreferrer"&gt;human in the loop, just not like this&lt;/a&gt;. You do not ask the model to be careful. You make the irreversible call impossible to complete in one step, so the pause happens whether the agent thought to pause or not.&lt;/p&gt;

&lt;p&gt;The thread running through all of it is the same. Your reader has no second reader behind it. There is no developer who will look at the payload, no client library that will smooth over the rough parts, no browser doing the rendering. Whatever you put on the wire is the whole experience, and it gets paid for in tokens, on every call, forever. That reframes API design as a budget exercise more than an aesthetic one.&lt;/p&gt;

&lt;p&gt;None of it is exotic. Most of it is the stuff you would have done anyway if the caller had been a junior engineer with a shell, no patience, and a bill running the whole time.&lt;/p&gt;

&lt;p&gt;Your API has a new reader. It works in a terminal and it pays by the token. So write for it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>api</category>
      <category>development</category>
    </item>
    <item>
      <title>Stop prompting. Start shipping._</title>
      <dc:creator>Petko D. Petkov</dc:creator>
      <pubDate>Tue, 16 Jun 2026 11:33:42 +0000</pubDate>
      <link>https://dev.to/pdparchitect/stop-prompting-start-shipping-51fc</link>
      <guid>https://dev.to/pdparchitect/stop-prompting-start-shipping-51fc</guid>
      <description>&lt;p&gt;I built zot.im — an open-source autonomous coding agent.&lt;/p&gt;

&lt;p&gt;No chat box. No constant prompting. No babysitting.&lt;/p&gt;

&lt;p&gt;Brief it once, and it runs the loop: plan, edit, execute, observe, verify, and stop when the job is done.&lt;/p&gt;

&lt;p&gt;It is a minimal CLI/TUI for developers who want an agent that can actually work through coding tasks end-to-end.&lt;/p&gt;

&lt;p&gt;It is early, MIT licensed, and actively evolving.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/openzot/openzot" rel="noopener noreferrer"&gt;https://github.com/openzot/openzot&lt;/a&gt;&lt;br&gt;
Website: &lt;a href="https://zot.im" rel="noopener noreferrer"&gt;https://zot.im&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback from anyone experimenting with coding agents or autonomous dev workflows.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>coding</category>
      <category>ai</category>
    </item>
    <item>
      <title>A CRM made for AI agents not for Humans</title>
      <dc:creator>Petko D. Petkov</dc:creator>
      <pubDate>Tue, 16 Jun 2026 11:30:06 +0000</pubDate>
      <link>https://dev.to/pdparchitect/a-crm-made-for-ai-agents-not-for-humans-g6b</link>
      <guid>https://dev.to/pdparchitect/a-crm-made-for-ai-agents-not-for-humans-g6b</guid>
      <description>&lt;p&gt;I built crmkit.ai — an open-source, headless CRM designed for AI agents.&lt;/p&gt;

&lt;p&gt;No dashboard. No SDK. No UI assumptions.&lt;/p&gt;

&lt;p&gt;Just a simple HTTP API that agents can use to manage contacts, companies, deals, tasks, tickets, activities, reminders, and audit history.&lt;/p&gt;

&lt;p&gt;The idea is that your AI agent becomes the CRM interface.&lt;/p&gt;

&lt;p&gt;It is early, MIT licensed, and built for people experimenting with agentic workflows, personal CRMs, sales automation, support automation, or structured agent memory.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/crmkit/crmkit" rel="noopener noreferrer"&gt;https://github.com/crmkit/crmkit&lt;/a&gt;&lt;br&gt;
Website: &lt;a href="https://crmkit.ai" rel="noopener noreferrer"&gt;https://crmkit.ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback from anyone building with AI agents.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>crm</category>
      <category>agents</category>
    </item>
    <item>
      <title>Security Agents Need a Thinner Harness</title>
      <dc:creator>Petko D. Petkov</dc:creator>
      <pubDate>Fri, 10 Apr 2026 10:37:01 +0000</pubDate>
      <link>https://dev.to/pdparchitect/security-agents-need-a-thinner-harness-1e3j</link>
      <guid>https://dev.to/pdparchitect/security-agents-need-a-thinner-harness-1e3j</guid>
      <description>&lt;p&gt;Today's AI coding agent harnesses (tools like Codex and Claude Code) are heavy-duty applications. They come bundled with dependency trees, runtime requirements, and a security surface area that grows with every update. The common argument is that writing them in a systems language like Rust solves the problem. It doesn't. The bloat isn't in the language per-se. No matter what you write it in, a fat client that needs to manage state, models, tools, memory, and orchestration locally will always be difficult to maintain, difficult to port, and difficult to trust.&lt;/p&gt;

&lt;p&gt;The real problem becomes clear when you think about where AI agents are headed next, which is the topic for this week, i.e computer security.&lt;/p&gt;

&lt;p&gt;We are approaching a future where AI agents will be the first line of defense (and offense) across every network. Defensive agents will sit on individual nodes, monitoring traffic, watching for anomalies, acting on threats in real time. They won't wait for a human to review a dashboard. They will detect, decide, and respond. Meanwhile, attackers will deploy their own agents for probing, scanning, attempting lateral movement, trying to stay undetected. It will be agent versus agent, operating at machine speed, 24 hours a day.&lt;/p&gt;

&lt;p&gt;In that world, the agent harness matters enormously. You can't deploy a 500MB application with a Python or a Node runtime and a dozen transitive dependencies to every node in your network. You can't afford for that agent to carry local state that could be exfiltrated if compromised. You can't manage updates across thousands of instances when each one requires a complex installation procedure. You need the opposite. You need a single binary, the smallest footprint possible, with no local state worth stealing.&lt;/p&gt;

&lt;p&gt;The only way to achieve this is to shift the complexity back to the server. The agent on the node should be a thin client that has just enough code to receive instructions, execute actions, and report results. The reasoning, the conversation state, the model inference belong on a managed plane where it can be secured, monitored, and updated without touching the deployed agents.&lt;/p&gt;

&lt;p&gt;This is exactly where &lt;a href="https://chatbotkit.com" rel="noopener noreferrer"&gt;ChatBotKit&lt;/a&gt; core technology has shown its strength since day one. The ChatBotKit agent builder provides the control plane. You design, configure, and update your agents from a single interface. The ChatBotKit Go SDK compiles down to a single binary, roughly 6MB, that contains everything the agent needs to operate. No runtime dependencies. No package manager. No supply chain to compromise. No secrets in .env files. Just a static binary that talks to the ChatBotKit conversation API.&lt;/p&gt;

&lt;p&gt;The architecture has a critical security property. Because the stateful conversation API handles reasoning server-side, the agent's intelligence runs remotely and only the results are served locally. There is no model running on the node and there is no state. There are no credentials stored in the agent's memory or in files. The worst an attacker can do is stop the agent from working. They cannot extract the reasoning chain, the system prompt, or any secrets managed by the platform. The agent is, in a sense, hollow. Useful but not exploitable.&lt;/p&gt;

&lt;p&gt;There is another advantage that matters at scale. The same agent definition can run across multiple instances (on the same node or across an entire fleet) without coordination overhead. Each instance connects independently to the ChatBotKit conversation API and operates autonomously. The entire agent network can be reconfigured from the control plane. Need to change detection rules? Update the agent definition once. Need to redeploy across a new subnet? Ship the same binary. Need to rotate credentials? The credentials never lived on the node in the first place.&lt;/p&gt;

&lt;p&gt;The future of computer security is not bigger firewalls or faster SIEM dashboards, but a mesh of lightweight AI agents where some are defending, some hunting but all operating at a speed and scale that humans alone cannot sustain. The question is whether those agents will be bloated, fragile, and dependent on whatever toolchain was trending when they were built, or whether they will be thin, disposable, and controlled from a secure plane.&lt;/p&gt;

&lt;p&gt;I know which architecture I'd bet on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;A practical note:&lt;/strong&gt; &lt;a href="https://github.com/chatbotkit/go-sdk" rel="noopener noreferrer"&gt;ChatBotKit's Go SDK&lt;/a&gt; is open source and produces a single static binary with no external dependencies. Combined with the stateful conversation API, it gives you a deployment model where the agent harness is thin enough to run anywhere and the intelligence stays where it belongs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
    </item>
  </channel>
</rss>
